上一章结束时 我们的 gameapp是这样的,

struct GameApp:public Win32App
{
    GameApp()
    {
        GetMainWnd()->Init();
    }
    ~GameApp()
    {
        GetMainWnd()->Destroy();
    }
    virtual void Tick() override
    {

    }
};

注意到 Tick函数 还是空的,要给窗口上画点东西,要在每次tick时把窗口清空然后再画.为了兼容不同的渲染api,我们先抽象一个图形类,


class IRenderer
{
public:
    //初始化时需要窗口句柄,
    virtual bool Init(void *) = 0;
    //游戏清理时调用Destroy
    virtual void Destroy() = 0;
    //窗口大小改变时更改后置缓冲
    virtual void Resize(void*,uint_t width, uint_t height) = 0;
    //渲染
    virtual void Render(const RenderData* data) = 0;
};

extern IRenderer* g_pRenderer;

RenderData里放我们要画的东西的数据, 这里的内容是怎么确定呢,应该是在Tick函数中根据玩家的输入,和上下文确定的,

目前我们没有渲染数据,先用d3d11的接口实现一;

这里有d3d11的官方示例, Direct3D11Tutorials是它的基础部分,其中的Tutorial01讲解了初始化的方法,我们参照它实现一下 d3d11的 IRendere; 新建一个d3d11的静态库,别忘记添加属性表, 建一个renderer.h的头文件,

#pragma once
#include <engine/renderer.h>

namespace d3d11
{
    class Renderer :public graphics::IRenderer
    {
        virtual bool Init(void *) override;
        virtual void Destroy() override;

        virtual void Resize(void*, uint32_t width, uint32_t height) override;
        //渲染
        virtual void Render(const graphics::RenderData* data) override;
    };
}

这里增加一个 d3d11的命名空间用以区别其他的渲染api,使功能一样的渲染api可以用同一个类名.

d3d11的初始化可以分为两步

  1. ID3D11Device 和 ID3D11DeviceContext 的初始化,
  2. IDXGISwapChain 的初始化

我们把它们抽到到类里去

// device.h
#include <d3d11.h>

#include <wrl/client.h>
using Microsoft::WRL::ComPtr;

namespace dx11
{
    class Device
    {
    public:

        virtual bool Init();
        virtual void Destroy();


        ID3D11Device* GetDevice() { return m_pDevice.Get(); }
        ID3D11DeviceContext* GetImmediateContext() { return m_pImmediateContext.Get(); }

    private:
        ComPtr<ID3D11Device> m_pDevice;
        ComPtr<ID3D11DeviceContext> m_pImmediateContext;
    };

    ID3D11Device* GetDevice();
    ID3D11DeviceContext* GetImmediateContext();
}

//device.cpp

#include "device.h"

namespace dx11
{
    Device g_Device;
    bool Device::Init()
    {
        HRESULT hr = S_OK;
        UINT createDeviceFlags = 0;

#ifdef _DEBUG
        //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };
        D3D_FEATURE_LEVEL featureLevel;
        UINT numFeatureLevels = ARRAYSIZE(featureLevels);

        hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
            D3D11_SDK_VERSION, m_pDevice.GetAddressOf(), &featureLevel, m_pImmediateContext.GetAddressOf());

        return hr == S_OK;
    }

    void Device::Destroy()
    {
    }


    ID3D11Device* GetDevice()
    {
        return g_Device.GetDevice();
    }

    ID3D11DeviceContext* GetImmediateContext()
    {
        return g_Device.GetImmediateContext();
    }

}

这里用到了comptr智能指针,可以安全的管理device资源

然后是交换链的代码

//renderwnd.h
#pragma once
#include <stdint.h>
#include <d3d11.h>

#include <wrl/client.h>
using Microsoft::WRL::ComPtr;

namespace d3d11
{
    class RenderWindow
    {
    public:
        bool Create(void* hwnd);
        void Build();
        void Resize(uint32_t width, uint32_t height);

        bool Begin();
        bool End(bool bVsync);
    private:
        void* m_hWnd;
        uint32_t m_nWidth, m_nHeight;

        ComPtr<IDXGISwapChain>         m_pSwapChain;
        ComPtr<ID3D11RenderTargetView> m_pRenderTargetView;
        ComPtr<ID3D11Texture2D>        m_pDepthStencilTex;
        ComPtr<ID3D11DepthStencilView> m_pDepthStencilView;

        D3D11_VIEWPORT m_vp;
    };
}

create的时候需要主窗口的句柄, build函数从后备缓冲中读取出ID3D11RenderTargetView,resize函数在窗口改变大小时调用,改变缓冲区的大小.Begin 和 End函数在 Renderer类的render函数中成对调用, Begin清屏,end交换缓冲区.下面是具体实现.

//renderwnd.cpp

#include "device.h"
#include "renderwnd.h"
#include <d3d11.h>
#include <windows.h>
#include <DirectXMath.h>

template<typename T>
void SafeRelease(T*& p)
{
    if (p)
    {
        p->Release();
        p = nullptr;
    }
}

namespace d3d11
{
    RenderWindow g_RenderWindow;
    RenderWindow * GetRenderWnd()
    {
        return &g_RenderWindow;
    }
    bool RenderWindow::Create(void * hWnd)
    {
        m_hWnd = hWnd;
        HRESULT hr = S_OK;
        RECT rc;
        GetClientRect((HWND)m_hWnd, &rc);
        UINT width = rc.right - rc.left;
        UINT height = rc.bottom - rc.top;


        DXGI_SWAP_CHAIN_DESC sd = {};
        sd.BufferCount = 2;
        sd.BufferDesc.Width = width;
        sd.BufferDesc.Height = height;
        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.BufferDesc.RefreshRate.Numerator = 60;
        sd.BufferDesc.RefreshRate.Denominator = 1;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.OutputWindow = (HWND)hWnd;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.Windowed = TRUE;

        ID3D11Device* pD3dDevice = GetD3dDevice();

        IDXGIFactory1* dxgiFactory = nullptr;
        {
            IDXGIDevice* dxgiDevice = nullptr;
            hr = pD3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
            if (SUCCEEDED(hr))
            {
                IDXGIAdapter* adapter = nullptr;
                hr = dxgiDevice->GetAdapter(&adapter);
                if (SUCCEEDED(hr))
                {
                    hr = adapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&dxgiFactory));
                    adapter->Release();
                }
                dxgiDevice->Release();
            }
        }
        if (FAILED(hr))
            return false;

        hr = dxgiFactory->CreateSwapChain(GetD3dDevice(), &sd, m_pSwapChain.GetAddressOf());

        dxgiFactory->MakeWindowAssociation((HWND)m_hWnd, DXGI_MWA_NO_ALT_ENTER);

        SafeRelease(dxgiFactory);

        m_vp.Width = (FLOAT)width;
        m_vp.Height = (FLOAT)height;
        m_vp.MinDepth = 0.0f;
        m_vp.MaxDepth = 1.0f;
        m_vp.TopLeftX = 0;
        m_vp.TopLeftY = 0;

        Build();
        return true;
    }

    void RenderWindow::Build()
    {
        HRESULT hr = S_OK;

        ID3D11Device* pD3dDevice = GetD3dDevice();
        ID3D11Texture2D* pBackBuffer = nullptr;
        hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&pBackBuffer));
        if (FAILED(hr))
            return;

        hr = pD3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, m_pRenderTargetView.GetAddressOf());
        if (FAILED(hr))
            return;

        D3D11_TEXTURE2D_DESC backbuffer_desc;
        pBackBuffer->GetDesc(&backbuffer_desc);
        SafeRelease(pBackBuffer);

        D3D11_TEXTURE2D_DESC descDepth = {};
        descDepth.Width = backbuffer_desc.Width;
        descDepth.Height = backbuffer_desc.Height;
        descDepth.MipLevels = 1;
        descDepth.ArraySize = 1;
        descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
        descDepth.SampleDesc.Count = 1;
        descDepth.SampleDesc.Quality = 0;
        descDepth.Usage = D3D11_USAGE_DEFAULT;
        descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
        descDepth.CPUAccessFlags = 0;
        descDepth.MiscFlags = 0;
        hr = pD3dDevice->CreateTexture2D(&descDepth, nullptr, m_pDepthStencilTex.ReleaseAndGetAddressOf());
        if (FAILED(hr))
            return;
        // Create the depth stencil view
        D3D11_DEPTH_STENCIL_VIEW_DESC descDSV = {};
        descDSV.Format = descDepth.Format;
        descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
        descDSV.Texture2D.MipSlice = 0;

        m_pDepthStencilView = nullptr;
        hr = pD3dDevice->CreateDepthStencilView(m_pDepthStencilTex.Get(), &descDSV, m_pDepthStencilView.ReleaseAndGetAddressOf());
        if (FAILED(hr))
            return;

    }
    void RenderWindow::Resize(uint32_t width, uint32_t height)
    {
        m_pRenderTargetView.Reset();
        m_pDepthStencilView.Reset();
        m_pDepthStencilTex.Reset();

        HRESULT hr = m_pSwapChain->ResizeBuffers(1,
            width,
            height,
            DXGI_FORMAT_R8G8B8A8_UNORM,
            0);

        Build();

        m_vp.Width = (FLOAT)width;
        m_vp.Height = (FLOAT)height;
        m_vp.MinDepth = 0.0f;
        m_vp.MaxDepth = 1.0f;
        m_vp.TopLeftX = 0;
        m_vp.TopLeftY = 0;
    }

    bool RenderWindow::Begin()
    {

        ID3D11DeviceContext* pImmediateContext = GetImmediateContext();

        pImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());
        pImmediateContext->RSSetViewports(1, &m_vp);

        DirectX::XMVECTORF32 color = { 0.45f, 0.55f, 0.60f, 1.00f };
        pImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), color);
        pImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
        return true;
    }

    bool RenderWindow::End(bool bVsync)
    {
        HRESULT hr = m_pSwapChain->Present(bVsync ? 1 : 0, 0);

        return SUCCEEDED(hr);
    }
}

然后在d3d11的renderer里面调用device和renderwindow.

//renderer.cpp
#include "renderer.h"
#include "device.h"
#include "renderwnd.h"

namespace d3d11
{
    Renderer g_Renderer;
    bool Renderer::Init(void *hwnd)
    {
        GetDevice()->Init();
        GetRenderWnd()->Create(hwnd);
        return false;
    }

    void Renderer::Destroy()
    {
    }

    void Renderer::Resize(void *, uint32_t width, uint32_t height)
    {
        GetRenderWnd()->Resize(width, height);
    }

    oid Renderer::Render(const graphics::RenderData * data)
    {
        GetRenderWnd()->Begin();
        //具体渲染的内容
        GetRenderWnd()->End(true);
    }

}

graphics::IRenderer* GetIRenderer()
{
    return &d3d11::g_Renderer;
}

然后在gameapp中把d3d11::renderer初始化, tick中调用render 就可以把窗口清空了. 另外 还需要在 mainwnd的 消息处理函数里收到改变窗口大小时 调用一下 renderer.resize函数.

直接在gameapp::tick()函数中渲染

void GameApp::Tick()
{
    GetIRenderer()->Render(nullptr);
}

现在还没有可以画的东西 所以直接传一个nullptr进去. 现在就可以得到一个被d3d11设置为纯色的窗口了. Snipaste_2019-03-12_16-03-17

现在还没有东西可画,那么如果想画点东西,这些东西从哪儿来呢,在画之前应该先得到当前要画的内容

//...
void GameApp::Tick()
{
    //防止第一次tick 时 lastTick为0,
    static auto lastTick = std::chrono::steady_clock::now();
    auto now = std::chrono::steady_clock::now();
    //计算距离上次tick的间隔时间
    auto dt = now - lastTick;
    lastTick = now;

    typedef std::chrono::duration<float> dtType;
    float _dt = duration_cast<dtType>(dt).count();

    Update(_dt);
    Render();
}

这里的update(_dt) 和 Render() 函数可以直接在GameApp里定义,也可以专门定义一个类来管理它们, 比如,现在是一次Update 一次 Render, 我们也可以 一次update 多次 render ,每次render做插值,这样可以提高渲染的流畅度

//gameloop.h
class GameLoop
{
public:
    bool Init(int fps);
    void Tick();

    float GetLogicFps();
    float GetRenderFps();
private:
    void Update(float dt);
    void Render(float dt);

    float m_logicStep;
    float m_fLogicFps;
    float m_fRenderFps;
};

GameLoop* GetGameLoop();

//gameloop.cpp

GameLoop g_GameLoop;
GameLoop* GetGameLoop()
{
    return &g_GameLoop;
}

完整的代码可以在 这里查看和下载.

下一章 我们想办法画点东西出来