DX11 Without DirectX SDK--01 DirectX11初始化

来源:https://www.cnblogs.com/X-Jun/archive/2018/05/21/9069608.html
-Advertisement-
Play Games

回到 DirectX11 使用Windows SDK來進行開發: "http://www.cnblogs.com/X Jun/p/9028764.html" 由於個人覺得龍書裡面第4章提供的Direct3D 初始化項目封裝得比較好,而且DirectX SDK Samples裡面的初始化程式過於精簡, ...


回到 DirectX11--使用Windows SDK來進行開發:http://www.cnblogs.com/X-Jun/p/9028764.html

由於個人覺得龍書裡面第4章提供的Direct3D 初始化項目封裝得比較好,而且DirectX SDK Samples裡面的初始化程式過於精簡,不適合後續使用,故選擇了以Init Direct3D項目作為框架,然後還使用了微軟提供的示例項目,兩者結合到一起。建議下載項目配合閱讀。

項目源碼點此:https://github.com/MKXJun/DX11-Without-DirectX-SDK

項目結構

該項目包含了下麵這些文件

image

其中頭文件的具體功能

頭文件 功能
d3dApp.h Direct3D應用程式框架類
dxerr.h DirectX錯誤庫
GameApp.h 游戲應用程式擴展類,游戲邏輯在這裡實現,繼承自D3DApp類
GameTimer.h 游戲計時器類

其中d3dApp.h, d3dApp.cpp, GameTimer.h, GameTimer.cpp是龍書源碼提供的,我們可以搬運過來,但是對d3dApp框架類我們還需要進行大幅度修改,畢竟我們的最終目的就是要完全脫離舊的DirectX SDK,使用Windows SDK來實現DX11.

dxerr.h在Windows SDK是沒有提供的,我們需要尋找新的dxerr進行替代,在後續會提到

GameApp.h則是我們編寫游戲邏輯的地方,這裡需要進行逐幀的更新及繪製。

初期配置

鏈接靜態庫

這裡的每一個項目都需要包含靜態庫:d3d11.lib,dxgi.lib,dxguid.lib,D3DCompiler.libwinmm.lib。可以在d3dApp.h添加下麵的語句:

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "D3DCompiler.lib")
#pragma comment(lib, "winmm.lib")

也可以在項目屬性-鏈接器-輸入-附加依賴項 添加上面的庫。

移植新的dxerr.h和dxerr.cpp

directx-sdk-samples-master的GitHub地址:https://github.com/walbourn/directx-sdk-samples

在directx-sdk-samples-master\DXUT\Core中可以找到dxerr.hdxerr.cpp,把它們拉進我們的項目中。然後使用下麵的巨集來進行檢查(加在d3dApp.h

#if defined(DEBUG) | defined(_DEBUG)
#ifndef HR
#define HR(x)                                              \
{                                                          \
    HRESULT hr = (x);                                      \
    if(FAILED(hr))                                         \
    {                                                      \
        DXTrace(__FILEW__, (DWORD)__LINE__, hr, L#x, true);\
    }                                                      \
}
#endif

#else
#ifndef HR
#define HR(x) (x)
#endif
#endif 

由於新的dxerr.h僅提供了DXTrace的Unicode字元集版本,需要將原來的__FILE__替換為__FILEW__,併在項目屬性頁中將字元集設置為Unicode。

COM組件智能指針

考慮到DirectX11的API是由一系列的COM組件來管理的,我們可以使用智能指針來管理這些對象,而無需過多擔心記憶體的泄漏。所以該項目並不會用到介面類ID3D11Debug來協助檢查記憶體泄漏。

使用該智能指針需要包含頭文件wrl/client.h,並且智能指針類模板ComPtr位於名稱空間Microsoft::WRL內。我們主要關註下麵這幾個方法:

ComPtr<T>::Get方法返回T*,若需要賦值操作也可以使用重載的=運算符進行

ComPtr<T>::GetAddressOf方法返回T**,也可以用重載的&運算符來獲取

ComPtr<T>::Reset方法將對裡面的對象調用Release方法,並將指針置為nullptr

GameTimer類

GameTimer類是一個基於高精度時鐘頻率的計時器,主要用於獲取游戲時間和每一幀的間隔時間,併進行一些特殊的操作。下麵給出了GameTimer類的聲明部分:

class GameTimer
{
public:
    GameTimer();

    float TotalTime()const;     // 總游戲時間
    float DeltaTime()const;     // 幀間隔時間

    void Reset(); // 在消息迴圈之前調用
    void Start(); // 在取消暫停的時候調用
    void Stop();  // 在暫停的時候調用
    void Tick();  // 在每一幀的時候調用

private:
    double mSecondsPerCount;    // 一個時鐘周期經過的秒數
    double mDeltaTime;          // 幀間隔時間

    __int64 mBaseTime;          // 基準時間
    __int64 mPausedTime;        // 暫停的時間
    __int64 mStopTime;          // 停止的時間
    __int64 mPrevTime;          // 上一幀的時間
    __int64 mCurrTime;          // 當前時間

    bool mStopped;              // 是否停止計時
};

構造函數

Windows.h中,提供了QueryPerformanceFrequency函數用於獲取當前處理器的時鐘頻率(1秒經過的時鐘周期數),然後我們就可以求出1個時鐘周期經過的時間數目了。此時計時器為開啟狀態。觀看構造函數的代碼:

GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0), 
  mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
    __int64 countsPerSec;
    QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
    mSecondsPerCount = 1.0 / (double)countsPerSec;
}

GameTimer::Reset方法

GameTimer::Reset方法用於重置當前游戲用時為0,並開啟計時器,具體的做法如下:

void GameTimer::Reset()
{
    __int64 currTime;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

    mBaseTime = currTime;
    mPrevTime = currTime;
    mStopTime = 0;
    mStopped  = false;
}

其中,QueryPerformanceCounter函數用於獲取當前經過的時鐘周期數。當前我們是用它獲取的值作為基準時間,然後將暫停的總計時間設置為0,並將暫停狀態設置為否。

GameTimer::Start方法

GameTimer::Start方法用於開啟計時器計時(設置開始時間),並統計上次暫停的總時間:

void GameTimer::Start()
{
    __int64 startTime;
    QueryPerformanceCounter((LARGE_INTEGER*)&startTime);


    // Accumulate the time elapsed between stop and start pairs.
    //
    //                     |<-------d------->|
    // ----*---------------*-----------------*------------> time
    //  mBaseTime       mStopTime        startTime     

    if( mStopped )
    {
        mPausedTime += (startTime - mStopTime); 

        mPrevTime = startTime;
        mStopTime = 0;
        mStopped  = false;
    }
}

若之前曾經暫停過,則需要統計當前暫停經過的時間,並加進總的暫停用時。然後這時停止時間也要歸零,並將暫停狀態設置為否。

GameTimer::Stop方法

GameTimer::Stop方法用於暫停計時器計時,設置暫停時間點:

void GameTimer::Stop()
{
    if( !mStopped )
    {
        __int64 currTime;
        QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

        mStopTime = currTime;
        mStopped  = true;
    }
}

GameTimer::Tick方法

GameTimer::Tick方法在計時器開啟的時候返回距離上次Tick的間隔時間,若計時器沒有開啟或者間隔時間為負值,則設間隔時間為0:

void GameTimer::Tick()
{
    if( mStopped )
    {
        mDeltaTime = 0.0;
        return;
    }

    __int64 currTime;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
    mCurrTime = currTime;

    // Time difference between this frame and the previous.
    mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;

    // Prepare for next frame.
    mPrevTime = mCurrTime;

    if(mDeltaTime < 0.0)
    {
        mDeltaTime = 0.0;
    }
}

GameTimer::TotalTime方法

GameTimer::TotalTime方法返回的是距離上次Reset方法調用到現在,游戲運行的總時間(不包括所有暫停過的時間,單位為秒):

float GameTimer::TotalTime()const
{
    // If we are stopped, do not count the time that has passed since we stopped.
    // Moreover, if we previously already had a pause, the distance 
    // mStopTime - mBaseTime includes paused time, which we do not want to count.
    // To correct this, we can subtract the paused time from mStopTime:  
    //
    //                     |<--paused time-->|
    // ----*---------------*-----------------*------------*------------*------> time
    //  mBaseTime       mStopTime        startTime     mStopTime    mCurrTime

    if( mStopped )
    {
        return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);
    }

    // The distance mCurrTime - mBaseTime includes paused time,
    // which we do not want to count.  To correct this, we can subtract 
    // the paused time from mCurrTime:  
    //
    //  (mCurrTime - mPausedTime) - mBaseTime 
    //
    //                     |<--paused time-->|
    // ----*---------------*-----------------*------------*------> time
    //  mBaseTime       mStopTime        startTime     mCurrTime
    
    else
    {
        return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);
    }
}

GameTimer::DeltaTime方法

GameTimer::TotalTime方法返回當前Tick和上次Tick之間的時間間隔,單位為秒:

float GameTimer::DeltaTime()const
{
    return (float)mDeltaTime;
}

D3DApp框架類

D3DApp.h展示了框架類的聲明:

class D3DApp
{
public:
    D3DApp(HINSTANCE hInstance);    // 在構造函數的初始化列表應當設置好初始參數
    virtual ~D3DApp();
    
    HINSTANCE AppInst()const;       // 獲取應用實例的句柄
    HWND      MainWnd()const;       // 獲取主視窗句柄
    float     AspectRatio()const;   // 獲取屏幕寬高比
    
    int Run();                      // 運行程式,進行游戲主迴圈
 
    // 框架方法。客戶派生類需要重載這些方法以實現特定的應用需求
    virtual bool Init();            // 該父類方法需要初始化視窗和Direct3D部分
    virtual void OnResize();        // 該父類方法需要在視窗大小變動的時候調用
    virtual void UpdateScene(float dt)=0;   // 子類需要實現該方法,完成每一幀的更新
    virtual void DrawScene()=0;             // 子類需要實現該方法,完成每一幀的繪製
    virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    // 視窗的消息回調函數
protected:
    bool InitMainWindow();      // 視窗初始化
    bool InitDirect3D();        // Direct3D初始化

    void CalculateFrameStats(); // 計算每秒幀數併在視窗顯示

protected:

    HINSTANCE mhAppInst;        // 應用實例句柄
    HWND      mhMainWnd;        // 主視窗句柄
    bool      mAppPaused;       // 應用是否暫停
    bool      mMinimized;       // 應用是否最小化
    bool      mMaximized;       // 應用是否最大化
    bool      mResizing;        // 視窗大小是否變化
    UINT      m4xMsaaQuality;   // MSAA支持的質量等級

    GameTimer mTimer;           // 計時器

    // DX11
    Microsoft::WRL::ComPtr<ID3D11Device> md3dDevice;                    // D3D11設備
    Microsoft::WRL::ComPtr<ID3D11DeviceContext> md3dImmediateContext;   // D3D11設備上下文
    Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;                  // D3D11交換鏈
    // DX11.1
    Microsoft::WRL::ComPtr<ID3D11Device1> md3dDevice1;                  // D3D11.1設備
    Microsoft::WRL::ComPtr<ID3D11DeviceContext1> md3dImmediateContext1; // D3D11.1設備上下文
    Microsoft::WRL::ComPtr<IDXGISwapChain1> mSwapChain1;                // D3D11.1交換鏈
    // 常用資源
    Microsoft::WRL::ComPtr<ID3D11Texture2D> mDepthStencilBuffer;        // 深度模板緩衝區
    Microsoft::WRL::ComPtr<ID3D11RenderTargetView> mRenderTargetView;   // 渲染目標視圖
    Microsoft::WRL::ComPtr<ID3D11DepthStencilView> mDepthStencilView;   // 深度模板視圖
    D3D11_VIEWPORT mScreenViewport;                                     // 視口

    // 派生類應該在構造函數設置好這些自定義的初始參數
    std::wstring mMainWndCaption;                                       // 主視窗標題
    int mClientWidth;                                                   // 視口寬度
    int mClientHeight;                                                  // 視口高度
};

而在d3dApp.cpp中,可以看到有一個全局變數gd3dApp

namespace
{
    // This is just used to forward Windows messages from a global window
    // procedure to our member function window procedure because we cannot
    // assign a member function to WNDCLASS::lpfnWndProc.
    D3DApp* gd3dApp = 0;
}

設置該全局變數是因為在視窗創建的時候需要綁定一個回調函數,但是我們不可以綁定d3dApp::MainWndProc的成員方法,所以還需要實現一個全局函數用於回調函數的綁定:

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // Forward hwnd on because we can get messages (e.g., WM_CREATE)
    // before CreateWindow returns, and thus before mhMainWnd is valid.
    return gd3dApp->MsgProc(hwnd, msg, wParam, lParam);
}

構造函數

在構造函數中,這些參數通常只會設置一次,所以需要在初始化列表中進行修改:

D3DApp::D3DApp(HINSTANCE hInstance)
    : mhAppInst(hInstance),
    mMainWndCaption(L"DirectX11 Initialization"),
    mClientWidth(800),
    mClientHeight(600),
    mhMainWnd(nullptr),
    mAppPaused(false),
    mMinimized(false),
    mMaximized(false),
    mResizing(false),
    m4xMsaaQuality(0),

    md3dDevice(nullptr),
    md3dImmediateContext(nullptr),
    mSwapChain(nullptr),
    mDepthStencilBuffer(nullptr),
    mRenderTargetView(nullptr),
    mDepthStencilView(nullptr)
{
    ZeroMemory(&mScreenViewport, sizeof(D3D11_VIEWPORT));


    // 讓一個全局指針獲取這個類,這樣我們就可以在Windows消息處理的回調函數
    // 讓這個類調用內部的回調函數了
    gd3dApp = this;
}

D3DApp::Init方法--初始化

初始化主要完成視窗的創建和Direct3D 11的初始化:

bool D3DApp::Init()
{
    if (!InitMainWindow())
        return false;

    if (!InitDirect3D())
        return false;

    return true;
}

D3DApp::InitMainWindow方法--完成視窗的創建

該方法的代碼如下:

bool D3DApp::InitMainWindow()
{
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = mhAppInst;
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = L"D3DWndClassName";

    if (!RegisterClass(&wc))
    {
        MessageBox(0, L"RegisterClass Failed.", 0, 0);
        return false;
    }

    // Compute window rectangle dimensions based on requested client area dimensions.
    RECT R = { 0, 0, mClientWidth, mClientHeight };
    AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
    int width = R.right - R.left;
    int height = R.bottom - R.top;

    mhMainWnd = CreateWindow(L"D3DWndClassName", mMainWndCaption.c_str(),
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);
    if (!mhMainWnd)
    {
        MessageBox(0, L"CreateWindow Failed.", 0, 0);
        return false;
    }

    ShowWindow(mhMainWnd, SW_SHOW);
    UpdateWindow(mhMainWnd);

    return true;
}

視窗的創建這裡不做過多描述,因為這不是教程的重點部分。有興趣的可以去MSDN查閱這些函數和結構體的信息。

D3DApp::MsgProc方法--回調函數

D3DApp::MsgProc回調方法的定義如下:

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        // WM_ACTIVATE is sent when the window is activated or deactivated.  
        // We pause the game when the window is deactivated and unpause it 
        // when it becomes active.  
    case WM_ACTIVATE:
        if (LOWORD(wParam) == WA_INACTIVE)
        {
            mAppPaused = true;
            mTimer.Stop();
        }
        else
        {
            mAppPaused = false;
            mTimer.Start();
        }
        return 0;

        // WM_SIZE is sent when the user resizes the window.  
    case WM_SIZE:
        // Save the new client area dimensions.
        mClientWidth = LOWORD(lParam);
        mClientHeight = HIWORD(lParam);
        if (md3dDevice)
        {
            if (wParam == SIZE_MINIMIZED)
            {
                mAppPaused = true;
                mMinimized = true;
                mMaximized = false;
            }
            else if (wParam == SIZE_MAXIMIZED)
            {
                mAppPaused = false;
                mMinimized = false;
                mMaximized = true;
                OnResize();
            }
            else if (wParam == SIZE_RESTORED)
            {

                // Restoring from minimized state?
                if (mMinimized)
                {
                    mAppPaused = false;
                    mMinimized = false;
                    OnResize();
                }

                // Restoring from maximized state?
                else if (mMaximized)
                {
                    mAppPaused = false;
                    mMaximized = false;
                    OnResize();
                }
                else if (mResizing)
                {
                    // If user is dragging the resize bars, we do not resize 
                    // the buffers here because as the user continuously 
                    // drags the resize bars, a stream of WM_SIZE messages are
                    // sent to the window, and it would be pointless (and slow)
                    // to resize for each WM_SIZE message received from dragging
                    // the resize bars.  So instead, we reset after the user is 
                    // done resizing the window and releases the resize bars, which 
                    // sends a WM_EXITSIZEMOVE message.
                }
                else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
                {
                    OnResize();
                }
            }
        }
        return 0;

        // WM_EXITSIZEMOVE is sent when the user grabs the resize bars.
    case WM_ENTERSIZEMOVE:
        mAppPaused = true;
        mResizing = true;
        mTimer.Stop();
        return 0;

        // WM_EXITSIZEMOVE is sent when the user releases the resize bars.
        // Here we reset everything based on the new window dimensions.
    case WM_EXITSIZEMOVE:
        mAppPaused = false;
        mResizing = false;
        mTimer.Start();
        OnResize();
        return 0;

        // WM_DESTROY is sent when the window is being destroyed.
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

        // The WM_MENUCHAR message is sent when a menu is active and the user presses 
        // a key that does not correspond to any mnemonic or accelerator key. 
    case WM_MENUCHAR:
        // Don't beep when we alt-enter.
        return MAKELRESULT(0, MNC_CLOSE);

        // Catch this message so to prevent the window from becoming too small.
    case WM_GETMINMAXINFO:
        ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
        ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
        return 0;

    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
        return 0;
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
        return 0;
    case WM_MOUSEMOVE:
        return 0;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

WM_ACTIVATE事件處理視窗激活或無效的情況,若視窗激活,則啟動計時器;否則停止計時器。

WM_SIZE 事件處理視窗大小變化的情況。若視窗最小化,進行標記。若視窗已經發生變化,需要調用d3dApp::OnReSize方法處理視窗變化的情況。在視窗大小正在改變的過程中,我們不需要調用剛纔的方法(此時視窗邊緣可能正在被拖動),否則太影響運行效率。

WM_ENTERSIZEMOVE事件處理視窗正在移動或大小正在變化的情況。這個時候需要暫停計時器。

WM_EXITSIZEMOVE事件處理視窗移動完成或大小變化結束的情況。這個時候需要啟動計時器。

其餘事件這裡不做具體描述,有些事件當前沒有處理,但是後續的內容會對這裡進行修改。

D3DApp::InitDirect3D方法--初始化Direct3D

現在假定你的電腦已經支持DirectX 11,但同時也有可能支持DirectX 11.1。因此在該項目中使用的頭文件是d3d11_1.h

要初始化DirectX11,我們需要創建這三樣東西:D3D設備、D3D設備上下文和DXGI交換鏈。

D3D設備包含了創建各種所需資源的方法。

D3D設備上下文負責對緩衝區進行渲染,綁定D3D設備創建的各種資源到不同的渲染管線。

DXGI交換鏈可以包含兩個或多個緩衝區,通常一個用於前端顯示,其餘的用於後端渲染。前臺緩衝區通常是只讀的,而後備緩衝區則是我們主要進行渲染的場所。當後備緩衝區渲染完成後,通過呈現方式將前後臺緩衝區交換,在屏幕上顯示出原來剛繪製好的畫面。

這三樣東西對應的介面類為:ID3D11DeviceID3D11DeviceContextIDXGISwapChain

而如果支持DirectX11.1的話,則對應的介面類為:ID3D11Device1ID3D11DeviceContext1IDXGISwapChain1,它們分別繼承自上面的三個介面類,區別在於額外提供了少數新的介面,並且介面方法的實現可能會有所區別。

D3D11CreateDevice函數 創建D3D設備與D3D設備上下文

創建D3D設備、D3D設備上下文使用如下函數:

HRESULT WINAPI D3D11CreateDevice(
    IDXGIAdapter* pAdapter,         // [In_Opt]適配器
    D3D_DRIVER_TYPE DriverType,     // [In]驅動類型
    HMODULE Software,               // [In_Opt]若上面為D3D_DRIVER_TYPE_SOFTWARE則這裡需要提供程式模塊
    UINT Flags,                     // [In]使用D3D11_CREATE_DEVICE_FLAG枚舉類型
    D3D_FEATURE_LEVEL* pFeatureLevels,  // [In_Opt]若為nullptr則為預設特性等級,否則需要提供特性等級數組
    UINT FeatureLevels,             // [In]特性等級數組的元素數目
    UINT SDKVersion,                // [In]SDK版本,預設D3D11_SDK_VERSION
    ID3D11Device** ppDevice,        // [Out_Opt]輸出D3D設備
    D3D_FEATURE_LEVEL* pFeatureLevel,   // [Out_Opt]輸出當前應用D3D特性等級
    ID3D11DeviceContext** ppImmediateContext ); //[Out_Opt]輸出D3D設備上下文

該函數可以創建DirectX11.1或者DirectX11.0的設備與設備上下文,取決於最終應用的D3D特性等級。

首先需要創建驅動類型數組進行輪詢,不過通常大多數情況都會支持D3D_DRIVER_TYPE_HARDWARE,以享受硬體加速帶來的效益:

// 驅動類型數組
D3D_DRIVER_TYPE driverTypes[] =
{
    D3D_DRIVER_TYPE_HARDWARE,       // 硬體驅動
    D3D_DRIVER_TYPE_WARP,           // WARP驅動
    D3D_DRIVER_TYPE_REFERENCE,      // 軟體驅動
};
UINT numDriverTypes = ARRAYSIZE(driverTypes);

然後就是提供特性等級數組,這裡只考慮DirectX11:

// 特性等級數組
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
};
UINT numFeatureLevels = ARRAYSIZE(featureLevels);

最後就會可以創建D3D設備和設備上下文了:

HRESULT hr = S_OK;

// 創建D3D設備 和 D3D設備上下文
UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)  
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
// 驅動類型數組
D3D_DRIVER_TYPE driverTypes[] =
{
    D3D_DRIVER_TYPE_HARDWARE,
    D3D_DRIVER_TYPE_WARP,
    D3D_DRIVER_TYPE_REFERENCE,
};
UINT numDriverTypes = ARRAYSIZE(driverTypes);

// 特性等級數組
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
};
UINT numFeatureLevels = ARRAYSIZE(featureLevels);

D3D_FEATURE_LEVEL featureLevel;
D3D_DRIVER_TYPE d3dDriverType;
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
    d3dDriverType = driverTypes[driverTypeIndex];
    hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
        D3D11_SDK_VERSION, md3dDevice.GetAddressOf(), &featureLevel, md3dImmediateContext.GetAddressOf());
    
    if (hr == E_INVALIDARG)
    {
        // DirectX 11.0 平臺不承認D3D_FEATURE_LEVEL_11_1所以我們需要嘗試特性等級11.0
        hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
            D3D11_SDK_VERSION, md3dDevice.GetAddressOf(), &featureLevel, md3dImmediateContext.GetAddressOf());
    }

    if (SUCCEEDED(hr))
        break;
}

if (FAILED(hr))
{
    MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
    return false;
}

如果支持DirectX11.1的話,featureLevel的結果應該為D3D_FEATURE_LEVEL_11_1,並且md3dDevice指向的是一個包含ID3D11Device1介面的對象,以及md3dImmediateContext指向的是一個包含ID3D11DeviceContext1介面的對象;而如果只支持DirectX11.0的話則為D3D_FEATURE_LEVEL_11_0

IDXGIFactory2::CreateSwapChainForHwnd方法--DirectX11.1創建交換鏈

如果是DirectX11.1的話,需要先填充DXGI_SWAP_CHAIN_DESC1DXGI_SWAP_CHAIN_FULLSCREEN_DESC這兩個結構體:

typedef struct DXGI_SWAP_CHAIN_DESC1
{
    UINT Width;                     // 緩衝區寬度
    UINT Height;                    // 緩衝區高度
    DXGI_FORMAT Format;             // 緩衝區數據格式
    BOOL Stereo;                    // 忽略   
    DXGI_SAMPLE_DESC SampleDesc;    // 採樣描述
    DXGI_USAGE BufferUsage;         // 緩衝區用途
    UINT BufferCount;               // 緩衝區數目
    DXGI_SCALING Scaling;           // 忽略
    DXGI_SWAP_EFFECT SwapEffect;    // 交換效果
    DXGI_ALPHA_MODE AlphaMode;      // 忽略
    UINT Flags;                     // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型
} DXGI_SWAP_CHAIN_DESC1;

typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;                     // MSAA採樣數
    UINT Quality;                   // MSAA質量等級
} DXGI_SAMPLE_DESC;

typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
    DXGI_RATIONAL RefreshRate;                  // 刷新率
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 忽略
    DXGI_MODE_SCALING Scaling;                  // 忽略
    BOOL Windowed;                              // 是否視窗化
} DXGI_SWAP_CHAIN_FULLSCREEN_DESC;

typedef struct DXGI_RATIONAL
{
    UINT Numerator;                 // 刷新率分子
    UINT Denominator;               // 刷新率分母
} DXGI_RATIONAL;

填充好後,DirectX11.1使用的創建方法為IDXGIFactory2::CreateSwapChainForHwnd

HRESULT IDXGIFactory2::CreateSwapChainForHwnd(
    IUnknown *pDevice,                      // [In]D3D設備
    HWND hWnd,                              // [In]視窗句柄
    const DXGI_SWAP_CHAIN_DESC1 *pDesc,     // [In]交換鏈描述1
    const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, // [In]交換鏈全屏描述,可選
    IDXGIOutput *pRestrictToOutput,         // [In]忽略
    IDXGISwapChain1 **ppSwapChain);         // [Out]輸出交換鏈對象

具體操作在後面一併演示

IDXGIFactory::CreateSwapChain方法--DirectX11創建交換鏈

如果是DirectX11.0的話,需要先填充DXGI_SWAP_CHAIN_DESC結構體:

typedef struct DXGI_SWAP_CHAIN_DESC
{
    DXGI_MODE_DESC BufferDesc;      // 緩衝區描述
    DXGI_SAMPLE_DESC SampleDesc;    // 採樣描述
    DXGI_USAGE BufferUsage;         // 緩衝區用途
    UINT BufferCount;               // 後備緩衝區數目
    HWND OutputWindow;              // 輸出視窗句柄
    BOOL Windowed;                  // 視窗化?
    DXGI_SWAP_EFFECT SwapEffect;    // 交換效果
    UINT Flags;                     // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型
}   DXGI_SWAP_CHAIN_DESC;

typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;                     // MSAA採樣數
    UINT Quality;                   // MSAA質量等級
} DXGI_SAMPLE_DESC;

typedef struct DXGI_MODE_DESC
{
    UINT Width;                     // 緩衝區寬度
    UINT Height;                    // 緩衝區高度
    DXGI_RATIONAL RefreshRate;      // 刷新率分數表示法
    DXGI_FORMAT Format;             // 緩衝區數據格式
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 忽略
    DXGI_MODE_SCALING Scaling;      // 忽略
} DXGI_MODE_DESC;

typedef struct DXGI_RATIONAL
{
    UINT Numerator;                 // 刷新率分子
    UINT Denominator;               // 刷新率分母
} DXGI_RATIONAL;

DirectX11.0下使用的創建方法為IDXGIFactory::CreateSwapChain

HRESULT IDXGIFactory::CreateSwapChain(
    IUnknown *pDevice,                  // [In]D3D設備
    DXGI_SWAP_CHAIN_DESC *pDesc,        // [In]交換鏈描述
    IDXGISwapChain **ppSwapChain);      // [Out]輸出交換鏈對象

根據已有設備類型來創建合適的交換鏈

瞭解了前面的操作後,現在我們需要先拿到包含IDXGIFactory或者IDXGIFactory2介面的對象:

ComPtr<IDXGIDevice> dxgiDevice = nullptr;
ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr;

ComPtr<IDXGIDevice1> dxgiDevice1 = nullptr;
ComPtr<IDXGIAdapter1> dxgiAdapter1 = nullptr;
ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr;

// 為了正確創建 DXGI交換鏈,首先我們需要獲取創建 D3D設備 的 DXGI工廠,否則會引發報錯:
// "IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory."
// 從屬關係為 DXGI工廠-> DXGI適配器 -> DXGI設備 {D3D11設備}
HR(md3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(dxgiDevice.GetAddressOf())));
HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));

這時候可以確定dxgiFactory1包含介面IDXGIFactory1,然後檢查它是否包含介面IDXGIFactory2,包含的話就說明支持DirectX11.1,然後獲取ID3D11Device1ID3D11DeviceContext1介面對象並創建包含IDXGISwapChain1介面的對象,否則就創建IDXGISwapChain介面的對象:

// 如果包含,則說明支持DX11.1
if (dxgiFactory2 != nullptr)
{
    HR(md3dDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(md3dDevice1.GetAddressOf())));
    HR(md3dImmediateContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(md3dImmediateContext1.GetAddressOf())));
    // 填充各種結構體用以描述交換鏈
    DXGI_SWAP_CHAIN_DESC1 sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.Width = mClientWidth;
    sd.Height = mClientHeight;
    sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 1;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    sd.Flags = 0;

    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fd;
    fd.RefreshRate.Numerator = 60;
    fd.RefreshRate.Denominator = 1;
    fd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    fd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    fd.Windowed = TRUE;
    // 為當前視窗創建交換鏈
    HR(dxgiFactory2->CreateSwapChainForHwnd(md3dDevice.Get(), mhMainWnd, &sd, &fd, nullptr, mSwapChain1.GetAddressOf()));
    mSwapChain1->QueryInterface(__uuidof(IDXGISwapChain), reinterpret_cast<void**>(mSwapChain.GetAddressOf()));
}
else
{
    // 填充DXGI_SWAP_CHAIN_DESC用以描述交換鏈
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferDesc.Width = mClientWidth;
    sd.BufferDesc.Height = mClientHeight;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 1;
    sd.OutputWindow = mhMainWnd;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    sd.Flags = 0;
    HR(dxgiFactory1->CreateSwapChain(md3dDevice.Get(), &sd, mSwapChain.GetAddressOf()));
}

這時候,如果支持DirectX11.1的話,md3dDevicemd3dDevice1其實都指向同一個對象,md3dImmediateContextmd3dImmediateContext1mSwapChainmSwapChain1也是一樣的,區別僅僅在於後者實現了額外的一些介面,問題不大。因此不管是DirectX11.1還是DirectX11.0,後續都主要使用md3dDevicemd3dImmediateContextmSwapChain來進行操作。

設置全屏

預設情況下按ALT+ENTER可以切換成全屏,如果不想要這種操作,可以使用剛纔創建的dxgiFactory1,按照下麵的方式來調用即可:

dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);

這樣DXGI就不會監聽Windows消息隊列,並且屏蔽掉了對接收到ALT+ENTER消息的處理。

在上述操作完成後,最後還調用了D3DApp::OnReSize方法。

D3DApp::OnReSize方法--視窗調整後的操作

無論是初始化操作,還是視窗大小變化後的操作,參與繪製的後備緩衝區和深度模板緩衝區大小都需要重新設置,因此需要重新創建。

ID3D11Resource資源類型

Direct3D 11的資源可以主要分為四個大類,它們都派生自ID3D11Resource

ID3D11Buffer通常用於頂點緩衝區、索引緩衝區等

ID3D11Texture1D通常用於創建1維紋理資源

ID3D11Texture2D通常用於創建2維紋理資源,可用於後備緩衝區

ID3D11Texture2D通常用於創建3維紋理資源

ID3D11View資源視圖類型

Direct3D 11的資源視圖也可以分為四個大類,它們都派生自ID3D11View

ID3D11RenderTargetView 渲染目標視圖通常會綁定一個ID3D11Texture2D的資源,而且通常綁定的是交換鏈指向的一個後備緩衝區。該視圖還需要綁定到渲染管線的輸出合併階段,輸出的結果將會寫入到所綁定的資源。

ID3D11DepthStencilView 深度模板視圖通常會綁定一個ID3D11Texture2D的資源,該資源用於存儲深度和模板信息。該視圖還需要綁定到渲染管線的輸出合併階段,輸出的結果將會寫入到所綁定的資源。

ID3D11ShaderResourceView 著色資源視圖可以綁定資源,然後將該視圖綁定到渲染管線的著色器階段,使得著色器代碼可以訪問綁定的資源。

ID3D11UnorderedAccessView 目前還不瞭解該視圖的作用,可能會在後續進行更新補充說明。

IDXGISwapChain::GetBuffer方法--獲取後備緩衝區

由於此前我們創建好的交換鏈已經包含1個後備緩衝區了,在創建渲染目標視圖之前我們還需要獲取該後備緩衝區:

HRESULT IDXGISwapChain::GetBuffer( 
    UINT Buffer,        // [In]緩衝區索引號,從0到BufferCount - 1
    REFIID riid,        // [In]緩衝區的介面類型ID
    void **ppSurface);  // [Out]獲取到的緩衝區

ID3D11Device::CreateRenderTargetView方法--創建渲染目標視圖

使用下麵的方法來獲取渲染目標視圖:

HRESULT ID3D11Device::CreateRenderTargetView( 
    ID3D11Resource *pResource,                      // [In]緩衝區資源
    const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,     // 忽略
    ID3D11RenderTargetView **ppRTView);             // [Out]獲取渲染目標視圖

因此D3DApp::OnReSize方法前面可以這樣寫:

assert(md3dImmediateContext);
assert(md3dDevice);
assert(mSwapChain);

if (md3dDevice1 != nullptr)
{
    assert(md3dImmediateContext1);
    assert(md3dDevice1);
    assert(mSwapChain1);
}

// 釋放交換鏈的相關資源
mRenderTargetView.Reset();
mDepthStencilView.Reset();
mDepthStencilBuffer.Reset();

// 重設交換鏈並且重新創建渲染目標視圖
ComPtr<ID3D11Texture2D> backBuffer;
HR(mSwapChain->ResizeBuffers(1, mClientWidth, mClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
HR(mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));
HR(md3dDevice->CreateRenderTargetView(backBuffer.Get(), 0, mRenderTargetView.GetAddressOf()));
    
    
backBuffer.Reset();

ID3D11Device::CreateTexture2D--創建一個2D紋理

除了渲染目標視圖外,我們還需要創建深度模板緩衝區用於深度測試。通過D3D設備可以新建一個緩衝區,但在此之前我們需要先描述該緩衝區的信息:

typedef struct D3D11_TEXTURE2D_DESC
{
    UINT Width;         // 緩衝區寬度
    UINT Height;        // 緩衝區高度
    UINT MipLevels;     // Mip等級
    UINT ArraySize;     // 紋理數組中的紋理數量,預設1
    DXGI_FORMAT Format; // 緩衝區數據格式
    DXGI_SAMPLE_DESC SampleDesc;    // 忽略
    D3D11_USAGE Usage;  // 數據的CPU/GPU訪問許可權
    UINT BindFlags;     // 使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型
    UINT CPUAccessFlags;    // 使用D3D11_CPU_ACCESS_FLAG枚舉來決定CPU訪問許可權
    UINT MiscFlags;     // 使用D3D11_RESOURCE_MISC_FLAG枚舉,這裡預設0
}   D3D11_TEXTURE2D_DESC;   

填充好後,這時我們就可以用方法ID3D11Device::CreateTexture2D來創建2D紋理:

HRESULT ID3D11Device::CreateTexture2D( 
    const D3D11_TEXTURE2D_DESC *pDesc,          // [In] 2D紋理描述信息
    const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用於初始化的資源
    ID3D11Texture2D **ppTexture2D);             // [Out] 獲取到的2D紋理

ID3D11Device::CreateDepthStencilView方法--創建深度模板視圖

HRESULT ID3D11Device::CreateDepthStencilView( 
    ID3D11Resource *pResource,                      // [In] 需要綁定的資源
    const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc,     // [In] 深度緩衝區描述
    ID3D11DepthStencilView **ppDepthStencilView);   // [Out] 獲取到的深度模板視圖

ID3D11DeviceContext::OMSetRenderTargets方法--輸出合併階段綁定渲染目標視圖和深度模板視圖

void ID3D11DeviceContext::OMSetRenderTargets( 
    UINT NumViews,                                      // [In] 視圖數目
    ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目標視圖數組
    ID3D11DepthStencilView *pDepthStencilView) = 0;     // [In] 深度模板視圖

下麵演示瞭如何創建深度模板視圖,並將渲染目標視圖和深度模板視圖綁定到渲染管線的輸出合併階段:

D3D11_TEXTURE2D_DESC depthStencilDesc;

depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;

// 要使用 4X MSAA? --需要給交換鏈設置MASS參數

depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;

depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;

// 創建深度緩衝區以及深度模板視圖
HR(md3dDevice->CreateTexture2D(&depthStencilDesc, 0, mDepthStencilBuffer.GetAddressOf()));
HR(md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), 0, mDepthStencilView.GetAddressOf()));


// 將渲染目標視圖和深度/模板緩衝區結合到管線
md3dImmediateContext->OMSetRenderTargets(1, mRenderTargetView.GetAddressOf(), mDepthStencilView.Get());

ID3D11DeviceContext::RSSetViewports方法--光柵化階段設置視口區域

最終我們還需要決定將整個視圖輸出到視窗特定的範圍。因此我們需要使用D3D11_VIEWPORT來設置視口

typedef struct D3D11_VIEWPORT
{
    FLOAT TopLeftX;     // 屏幕左上角起始位置X
    FLOAT TopLeftY;     // 屏幕左上角起始位置Y
    FLOAT Width;        // 寬度
    FLOAT Height;       // 高度
    FLOAT MinDepth;     // 最小深度,必須為0.0f
    FLOAT MaxDepth;     // 最大深度,必須為1.0f
}   D3D11_VIEWPORT;

ID3D11DeviceContext::RSSetViewports方法將設置1個或多個視口:

void ID3D11DeviceContext::RSSetViewports(
    UINT  NumViewports,                     // 視口數目
    const D3D11_VIEWPORT *pViewports);      // 視口數組

將視圖輸出到整個屏幕需要進行下麵的操作:

mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width    = static_cast<float>(mClientWidth);
mScreenViewport.Height   = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;

md3dImmediateContext->RSSetViewports(1, &mScreenViewport);

這些就是D3DApp框架類最主要的部分了,在後續的部分,該框架的代碼基本上不會有什麼太大的變動。因此後續代碼的添加主要在GameApp類實現。

GameApp類

對於一個初始化應用程式來說,目前GameApp類的非常簡單:

class GameApp : public D3DApp
{
public:
    GameApp(HINSTANCE hInstance);
    ~GameApp();

    bool Init();
    void OnResize();
    void UpdateScene(float dt);
    void DrawScene();
};

GameApp::DrawScene方法--每幀畫面的繪製

ID3D11DeviceContext::ClearRenderTargetView方法--清空需要繪製的緩衝區

在每一幀畫面繪製的操作中,我們需要清理一遍渲染目標視圖綁定的緩衝區

void ID3D11DeviceContext::ClearRenderTargetView(
    ID3D11RenderTargetView *pRenderTargetView,  // [In]渲染目標視圖
    const FLOAT  ColorRGBA[4]);                 // [In]指定覆蓋顏色

ID3D11DeviceContext::ClearDepthStencilView方法--清空深度模板緩衝區

同樣在進行渲染之前,我們也要清理一遍深度模板緩衝區

void ID3D11DeviceContext::ClearDepthStencilView(
    ID3D11DepthStencilView *pDepthStencilView,  // [In]深度模板視圖
    UINT ClearFlags,    // [In]D3D11_CLEAR_FLAG枚舉
    FLOAT Depth,        // [In]深度
    UINT8 Stencil);     // [In]模板初始值

IDXGISwapChain::Present方法--前後臺緩衝區交換並呈現

完成一切繪製操作後就可以調用該方法了

HRESULT ID3D11DeviceContext::STDMETHODCALLTYPE Present( 
    UINT SyncInterval,  // [In]通常為0
    UINT Flags);   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原創 上一篇博客寫了先進先出演算法(FIFO)——頁面置換:http://www.cnblogs.com/chiweiming/p/9058438.html 此篇介紹最近最少使用演算法(LRU)——頁面置換,與上一篇的代碼大同小異,只是用了不同的方法從頁面隊列 中選出需要淘汰出的頁面。 還是辣個慄子: ...
  • IntelliJ IDEA之UML類圖 生成方法 Show Diagrams 選中需要的類,右鍵單擊 ,之後點擊 ,或者快捷鍵 生成類圖,將類圖顯示在編輯器視窗中 Show Diagrams PopUp 選中所需要類,右鍵單擊 ,之後點擊 ,或者快捷鍵 生成類圖,將類圖顯示在彈出視窗中 工具欄(To ...
  • 在類unix操作系統下,可以用 os.fork() 創建一個新的進程,windows系統不可以: 在執行了 os.fork() 這一句之後,會有兩個進程同時向下執行, 返回的 ret 分別是 0(子進程)的 和 大於0(父進程)的 , getpid() 是獲取當前進程的pid getppid() 是 ...
  • 一、項目目錄結構: 案例採用MVC三層模式開發: 二、源代碼: domain層:User .java dao層:UserDaoImpl .java RegisterForm.java:封裝並校驗註冊表單提交的數據 LoginServlet.java:處理登陸請求 LogoutServlet.java ...
  • 遠程代理模式-Remote Proxy 服務端通過rmi將對象註冊到遠程服務, 客戶端使用時, 只需要通過rmi協議獲取即可, 只要介面統一, 即可不需要知道內部具體實現, 直接調用使用. CompareHelper介面 這裡就是客戶端和服務端統一的介面, 只需要服務端根據這個介面實現相應的功能, ...
  • 如何合理地估算線程池大小? 這個問題雖然看起來很小,卻並不那麼容易回答。大家如果有更好的方法歡迎賜教,先來一個天真的估算方法:假設要求一個系統的TPS(Transaction Per Second或者Task Per Second)至少為20,然後假設每個Transaction由一個線程完成,繼續假 ...
  • 觀察者模式-Observer 當一個對象變化時,其它依賴該對象的對象都會收到通知, 並且會隨著變化!對象之間是一種一對多的關係。 換一種表達方式: 有很多觀察者們依賴於(觀察)同一個對象(主題), 當這個被依賴的對象(主題)變化時, 觀察者們也會隨之變化. 本文中的例子如下: 有一個進度生成器, 用 ...
  • from:從0開始,構建前後端分離應用 1. 一些基本概念 1.1 為什麼要進行單元測試?我自己的理解是 1、能夠快速發現問題。避免衍生BUG的出現 在對一些現有代碼進行修改時,或者修改現有BUG的時候。都有可能對已有的代碼產生影響,產生新的問題。那麼怎麼能避免新問題的產生呢?那就是執行回歸測試,但 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...