Jump to content

  • Log In with Google      Sign In   
  • Create Account

There is no escape from the Washu



Sweet Snippets - Handling Input and Callbacks with Awesomium

Posted by , 03 January 2015 - - - - - - · 808 views

In our previous entry we started rendering a UI overlay on our application. We added some basic interactivity in that we can update a health bar by sending javascript commands. However, what if we want the UI to be able to notify us of actions? That's what we're going to cover in this entry. In addition, we're going to add mouse input forwarding to Awesomium, so that it can respond to events that can be triggered through mouse input.

Introduction
Posted Image
Adding mouse input forwarding is fairly simple, you simply need to trap the WM_MOUSEMOVE, along with the WM_LBUTTONDOWN and WM_LBUTTONUP Windows messages. These messages cover pretty much our entire use case, which is the ability to click buttons and detect mouse over events. Handling these events is fairly trivial, as you can see from the snippet below for WM_MOUSEMOVE:
LRESULT OnMouseMove(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
    int xPos = GET_X_LPARAM(lParam);
    int yPos = GET_Y_LPARAM(lParam);

    if (m_view) {
        if (wParam && MK_LBUTTON)
            m_view->InjectMouseDown(Awesomium::kMouseButton_Left);

        m_view->InjectMouseMove(xPos, yPos);
    }
    return 0;
}
If you've been using an nice UI stylesheet, then it should automatically start highlighting things now, and you should be able to tell when you have given focus to things like buttons. More importantly, you can now click various links and if you're using the HTML in the previous post, you can now show and hide the quest tracker.

Getting Feedback From the UI
One of our goals is to allow the UI to tell the game things. For instance, if the user clicks on the skill button at the bottom, we expect it to execute whatever skill is bound to that button. To do this we need to bind a global javascript object to the Awesomium WebView, and then map the C++ functions we desire to call onto javascript functions we add to the global object.

We do this fairly simply, using a map of ID and javascript function name to std::function objects:
m_jsApp = m_view->CreateGlobalJavascriptObject(Awesomium::WSLit("app"));

Awesomium::JSObject & appObject = m_jsApp.ToObject();
appObject.SetCustomMethod(Awesomium::WSLit("skill"), false);

JsCallerKey key(appObject.remote_id(), Awesomium::WSLit("skill"));
m_jsFunctions[key] = std::bind(&MainWindow::OnSkill, this, std::placeholders::_1, std::placeholders::_2);
In this case we're binding the OnSkill non-static member function to the javascript function "skill" in the "app" object. We could have also used a lambda here, or a static function as well.

Of course, since there's no actual relationship between the javascript function name and the C++ function, we need to build a binding system. Thankfully, Awesomium comes with a method handler which allows it to notify us whenever a javascript function is invoked on our global object. In our case, for simplicity, we implement the interface on the MainWindow class, however in general I would actually recommend implementing this on a separate object entirely.
m_view->set_js_method_handler(this);
After this we just have to implement the two methods it requires, OnMethodCall and OnMethodCallWithReturnValue, and have them query our map for any functions that match the object ID and function name specified. If the function is found, we invoke it with the expected parameters:
void OnMethodCall(Awesomium::WebView * caller, unsigned remoteObjectId, Awesomium::WebString const & methodName, Awesomium::JSArray const & args) {
    JsCallerKey key(remoteObjectId, methodName);
    auto itor = m_jsFunctions.find(key);

    if (itor != m_jsFunctions.end()) {
        itor->second(caller, args);
    }
}
With this in place, and our app.skill function bound, our HTML can trivially invoke it:
<button id="button" onclick="javascript:app.skill(1)">
    A Skill Button
</button>
We now have the capability to allow the Awesomium UI to communicate with our game in a meaningful and event driven manner.

More Efficient Rendering
One of the other problems we're going to encounter is determining when input should be directed to the UI layer, and when input should be directed to the game systems.

Along with this we also find ourselves in a position to do a bit of optimization of our rendering. In our previous code we were using the UpdateSubresource call to update portions of our texture (created with D3D11_USAGE_DEFAULT). This has several issues:
  • It creates a copy of the memory passed into it.
  • We cannot later query for information about the UI overlay
  • The source and destination textures must be in the same format.
Now, we're not going to be changing the backing format (although you might want to for various reasons). However, by switching out to a better method we can reduce our overall overhead, allow us to query for information from the texture, and also give us the ability to alter change said formats.

Our methodology will be to use a staging texture with the D3D11_CPU_ACCESS_READ and D3D11_CPU_ACCESS_WRITE flags. Why read? The simple answer is: we will eventually want to know when a pixel is transparent to the UI. This way we can determine if the mouse is currently over a UI element, or if the mouse is in the gameplay view.

For updating the rendered texture, we simply map our staging resource, and then we run through a series of memcpy calls to copy each changed row of the texture over:
D3D11_MAPPED_SUBRESOURCE resource;
m_context->Map(m_staging, 0, D3D11_MAP_WRITE, 0, &resource);

auto srcStartingOffset = srcRowSpan * srcRect.y + srcRect.x * 4;
uint8_t * srcPtr = srcBuffer + srcStartingOffset;

auto dstStartingOffset = resource.RowPitch * destRect.y + destRect.x * 4;
uint8_t * dataPtr = reinterpret_cast<uint8_t *>(resource.pData) + dstStartingOffset;

for (int i = 0; i < destRect.height; ++i) {
    memcpy(dataPtr + resource.RowPitch * i, srcPtr + srcRowSpan * i, destRect.width * 4);
}
m_context->Unmap(m_staging, 0);
Once that's complete, we can simply ask Direct3D11 to copy the updated portion of the staging texture over to our rendered texture:
m_context->CopySubresourceRegion(m_texture, 0, destRect.x, destRect.y, 0, m_staging, 0, &box);
With this in hand, we can also map our staging texture in for reading, and simply ask it if a particular pixel (at an X,Y position) is fully:
bool IsUIPixel(unsigned x, unsigned y) {
    D3D11_MAPPED_SUBRESOURCE resource;
    m_context->Map(m_staging, 0, D3D11_MAP_READ, 0, &resource);

    auto startingOffset = (m_width * y + x) * 4;
    uint8_t * dataPtr = reinterpret_cast<uint8_t *>(resource.pData) + startingOffset;
    bool result = *dataPtr != 0;

    m_context->Unmap(m_staging, 0);

    return result;
}
This function will return true if the pixel queried has any opaqueness to it (i.e. partial transparency).

Full Sample
#define NOMINMAX

#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atlapp.h>

#include <d3d11.h>
#include <dxgi.h>
#include <DirectXMath.h>

#include <Awesomium/WebCore.h>
#include <Awesomium/BitmapSurface.h>
#include <Awesomium/STLHelpers.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "awesomium.lib")

#include <string>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <fstream>
#include <vector>
#include <map>
#include <functional>

#ifdef UNICODE
typedef wchar_t tchar;
typedef std::wstring tstring;

template<typename T>
tstring to_string(T t) {
    return std::to_wstring(t);
}
#else
typedef char tchar;
typedef std::string tstring;

template<typename T>
tstring to_string(T t) {
    return std::to_string(t);
}
#endif

struct Vertex {
    float position[4];
    float color[4];
    float texCoord[2];

    static const unsigned Stride = sizeof(float) * 10;
    static const unsigned Offset = 0;
};

void ThrowIfFailed(HRESULT result, std::string const & text) {
    if (FAILED(result))
        throw std::runtime_error(text + "");
}

class RenderTarget {
public:
    RenderTarget(ID3D11Texture2D * texture, bool hasDepthBuffer) : m_texture(texture) {
        CComPtr<ID3D11Device> device;
        texture->GetDevice(&device);

        auto result = device->CreateRenderTargetView(m_texture, nullptr, &m_textureRTV);
        ThrowIfFailed(result, "Failed to create back buffer render target.");

        m_viewport = CD3D11_VIEWPORT(m_texture, m_textureRTV);

        result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_D32_FLOAT, static_cast<int>(m_viewport.Width), static_cast<int>(m_viewport.Height), 1, 1, D3D11_BIND_DEPTH_STENCIL), nullptr, &m_depthBuffer);
        ThrowIfFailed(result, "Failed to create depth buffer.");

        result = device->CreateDepthStencilView(m_depthBuffer, nullptr, &m_depthView);
        ThrowIfFailed(result, "Failed to create depth buffer render target.");
    }

    void Clear(ID3D11DeviceContext * context, float color[4], bool clearDepth = true) {
        context->ClearRenderTargetView(m_textureRTV, color);
        if (clearDepth && m_depthView)
            context->ClearDepthStencilView(m_depthView, D3D11_CLEAR_DEPTH, 1.0f, 0);
    }

    void SetTarget(ID3D11DeviceContext * context) {
        context->OMSetRenderTargets(1, &m_textureRTV.p, m_depthView);
        context->RSSetViewports(1, &m_viewport);
    }

private:
    D3D11_VIEWPORT					m_viewport;

    CComPtr<ID3D11Texture2D>		m_depthBuffer;
    CComPtr<ID3D11DepthStencilView> m_depthView;

    CComPtr<ID3D11Texture2D>		m_texture;
    CComPtr<ID3D11RenderTargetView> m_textureRTV;
};

class GraphicsDevice {
public:
    GraphicsDevice(HWND window, int width, int height) {
        D3D_FEATURE_LEVEL levels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };

        DXGI_SWAP_CHAIN_DESC desc = {
            {
                width,
                height,
                { 1, 60 },
                DXGI_FORMAT_R8G8B8A8_UNORM,
                DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
                DXGI_MODE_SCALING_UNSPECIFIED
            },
            { 1, 0 },
            DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
            1,
            window,
            TRUE,
            DXGI_SWAP_EFFECT_DISCARD,
            0
        };

        auto result = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            levels,
            sizeof(levels) / sizeof(D3D_FEATURE_LEVEL),
            D3D11_SDK_VERSION,
            &desc,
            &m_swapChain,
            &m_device,
            &m_featureLevel,
            &m_context
            );
        ThrowIfFailed(result, "Failed to create D3D11 device.");
    }

    void Resize(int width, int height) {
        if (m_renderTarget)
            m_renderTarget.reset();

        auto result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_UNKNOWN, 0);
        ThrowIfFailed(result, "Failed to resize back buffer.");

        CComPtr<ID3D11Texture2D> backBuffer;

        result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void * *>(&backBuffer));
        ThrowIfFailed(result, "Failed to retrieve back buffer surface.");

        m_renderTarget = std::make_unique<RenderTarget>(backBuffer, true);

    }

    void SetAndClearTarget() {
        static float color[] { 0, 0, 0, 0};
        if (!m_renderTarget)
            return;

        m_renderTarget->Clear(m_context, color);
        m_renderTarget->SetTarget(m_context);
    }

    void Present() {
        m_swapChain->Present(0, 0);
    }

    ID3D11Device * GetDevice() { return m_device; }
    ID3D11DeviceContext * GetDeviceContext() { return m_context; }

private:
    D3D_FEATURE_LEVEL				m_featureLevel;

    CComPtr<ID3D11Device>			m_device;
    CComPtr<ID3D11DeviceContext>	m_context;
    CComPtr<IDXGISwapChain>			m_swapChain;

    std::unique_ptr<RenderTarget>   m_renderTarget;
};

class D3DSurface : public Awesomium::Surface {
public:
    D3DSurface(ID3D11DeviceContext * context, Awesomium::WebView * view, int width, int height) : m_context(context), m_view(view), m_width(width), m_height(height) {
        CComPtr<ID3D11Device> device;
        context->GetDevice(&device);

        auto result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1), nullptr, &m_texture);
        result = device->CreateShaderResourceView(m_texture, nullptr, &m_textureView);

        result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1, 0, D3D11_USAGE_STAGING, D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE), nullptr, &m_staging);
    }

    virtual void Paint(unsigned char *srcBuffer, int srcRowSpan, const Awesomium::Rect &srcRect, const Awesomium::Rect &destRect) {
        auto box = CD3D11_BOX(destRect.x, destRect.y, 0, destRect.x + destRect.width, destRect.y + destRect.height, 1);

        D3D11_MAPPED_SUBRESOURCE resource;
        m_context->Map(m_staging, 0, D3D11_MAP_WRITE, 0, &resource);

        auto srcStartingOffset = srcRowSpan * srcRect.y + srcRect.x * 4;
        uint8_t * srcPtr = srcBuffer + srcStartingOffset;

        auto dstStartingOffset = resource.RowPitch * destRect.y + destRect.x * 4;
        uint8_t * dataPtr = reinterpret_cast<uint8_t *>(resource.pData) + dstStartingOffset;

        for (int i = 0; i < destRect.height; ++i) {
            memcpy(dataPtr + resource.RowPitch * i, srcPtr + srcRowSpan * i, destRect.width * 4);
        }
        m_context->Unmap(m_staging, 0);

        m_context->CopySubresourceRegion(m_texture, 0, destRect.x, destRect.y, 0, m_staging, 0, &box);
    }

    virtual void Scroll(int dx, int dy, const Awesomium::Rect &clipRect) {
        auto box = CD3D11_BOX(clipRect.x, clipRect.y, 0, clipRect.x + clipRect.width, clipRect.y + clipRect.height, 1);

        m_context->CopySubresourceRegion(m_texture, 0, clipRect.x + dx, clipRect.y + dy, 0, m_texture, 0, &box);
    }

    void Bind() {
        m_context->PSSetShaderResources(0, 1, &m_textureView.p);
    }

    bool IsUIPixel(unsigned x, unsigned y) {
        D3D11_MAPPED_SUBRESOURCE resource;
        m_context->Map(m_staging, 0, D3D11_MAP_READ, 0, &resource);

        auto startingOffset = (m_width * y + x) * 4;
        uint8_t * dataPtr = reinterpret_cast<uint8_t *>(resource.pData) + startingOffset;
        bool result = *dataPtr != 0;

        m_context->Unmap(m_staging, 0);

        return result;
    }

    virtual ~D3DSurface() { }

private:
    CComPtr<ID3D11ShaderResourceView> m_textureView;
    CComPtr<ID3D11Texture2D>          m_texture;
    CComPtr<ID3D11Texture2D>          m_staging;

    ID3D11DeviceContext *             m_context;
    Awesomium::WebView *              m_view;
    int                               m_width;
    int                               m_height;
};

class D3DSurfaceFactory : public Awesomium::SurfaceFactory {
public:
    D3DSurfaceFactory(ID3D11DeviceContext * context) : m_context(context) {

    }

    virtual Awesomium::Surface * CreateSurface(Awesomium::WebView * view, int width, int height) {
        return new D3DSurface(m_context, view, width, height);
    }

    virtual void DestroySurface(Awesomium::Surface * surface) {
        delete surface;
    }

private:
    ID3D11DeviceContext * m_context;
};

class MainWindow : public CWindowImpl<MainWindow, CWindow, CFrameWinTraits>, public Awesomium::JSMethodHandler {
public:
    DECLARE_WND_CLASS_EX(ClassName, CS_OWNDC | CS_HREDRAW | CS_VREDRAW, COLOR_BACKGROUND + 1);

    MainWindow(Awesomium::WebCore * webCore) : m_webCore(webCore), m_view(nullptr, [](Awesomium::WebView * ptr) { ptr->Destroy(); }), m_isMaximized(false), m_surface(nullptr) {
        RECT rect = { 0, 0, 800, 600 };
        AdjustWindowRectEx(&rect, GetWndStyle(0), FALSE, GetWndExStyle(0));
        Create(nullptr, RECT{ 0, 0, rect.right - rect.left, rect.bottom - rect.top }, WindowName);
        ShowWindow(SW_SHOW);
        UpdateWindow();
    }

    void Run() {
        MSG msg;
        while (true) {
            if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                if (msg.message == WM_QUIT)
                    break;

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else {
                Update();
            }
        }
    }

    void Update() {
        auto context = m_device->GetDeviceContext();
        m_webCore->Update();

        if (m_view->IsLoading()) {
            m_isLoading = true;
        }
        else if (m_isLoading) {
            m_isLoading = false;
            UpdateBossHealth();
            m_webCore->Update();
            
            m_surface = static_cast<D3DSurface *>(m_view->surface());
        }

        m_device->SetAndClearTarget();
        context->OMSetBlendState(m_blendState, nullptr, ~0);
        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        context->IASetIndexBuffer(nullptr, (DXGI_FORMAT)0, 0);

        context->IASetVertexBuffers(0, 1, &m_vertexBuffer.p, &Vertex::Stride, &Vertex::Offset);
        context->IASetInputLayout(m_inputLayout);
        context->VSSetShader(m_triangleVS, nullptr, 0);
        context->PSSetShader(m_trianglePS, nullptr, 0);

        context->Draw(3, 0);

        context->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
        context->IASetInputLayout(nullptr);

        context->VSSetShader(m_vertexShader, nullptr, 0);
        context->PSSetShader(m_pixelShader, nullptr, 0);
        context->PSSetSamplers(0, 1, &m_sampler.p);
        if (m_surface)
            m_surface->Bind();

        context->Draw(3, 0);
        
        m_device->Present();
    }

    void OnMethodCall(Awesomium::WebView * caller, unsigned remoteObjectId, Awesomium::WebString const & methodName, Awesomium::JSArray const & args) {
        JsCallerKey key(remoteObjectId, methodName);
        auto itor = m_jsFunctions.find(key);

        if (itor != m_jsFunctions.end()) {
            itor->second(caller, args);
        }
    }

    Awesomium::JSValue Awesomium::JSMethodHandler::OnMethodCallWithReturnValue(Awesomium::WebView * caller, unsigned remoteObjectId, Awesomium::WebString const & methodName, Awesomium::JSArray const & args) {
        JsCallerKey key(remoteObjectId, methodName);
        auto itor = m_jsFunctionsWithRetValue.find(key);

        if (itor != m_jsFunctionsWithRetValue.end()) {
            return itor->second(caller, args);
        }

        return Awesomium::JSValue();
    }
private:
    BEGIN_MSG_MAP(MainWindow)
        MESSAGE_HANDLER(WM_DESTROY, [](unsigned messageId, WPARAM wParam, LPARAM lParam, BOOL & handled) { PostQuitMessage(0); return 0; });
        MESSAGE_HANDLER(WM_CREATE, OnCreate);
        MESSAGE_HANDLER(WM_SIZE, OnSize);
        MESSAGE_HANDLER(WM_EXITSIZEMOVE, OnSizeFinish);
        MESSAGE_HANDLER(WM_KEYUP, OnKeyUp);
        MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove);
        MESSAGE_HANDLER(WM_LBUTTONDOWN, OnMouseLButtonDown);
        MESSAGE_HANDLER(WM_LBUTTONUP, OnMouseLButtonUp);
    END_MSG_MAP()

private:
    LRESULT OnCreate(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT rect;
            GetClientRect(&rect);
            m_device = std::make_unique<GraphicsDevice>(m_hWnd, rect.right, rect.bottom);

            tstring filename(MAX_PATH, 0);
            GetModuleFileName(GetModuleHandle(nullptr), &filename.front(), filename.length());
            filename = filename.substr(0, filename.find_last_of('\\'));
            SetCurrentDirectory(filename.c_str());

            CreateD3DResources();

            m_surfaceFactory = std::make_unique<D3DSurfaceFactory>(m_device->GetDeviceContext());
            m_webCore->set_surface_factory(m_surfaceFactory.get());

            CreateWebView(rect.right, rect.bottom);

            m_device->Resize(rect.right, rect.bottom);

        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
            return -1;
        }

        return 0;
    }

    LRESULT OnSizeFinish(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT clientRect;
            GetClientRect(&clientRect);

            m_device->Resize(clientRect.right, clientRect.bottom);

            if (m_view->IsLoading())
                m_view->Stop();

            m_surface = nullptr;

            CreateWebView(clientRect.right, clientRect.bottom);
        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
        }

        return 0;
    }

    LRESULT OnSize(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (wParam == SIZE_MAXIMIZED) {
            m_isMaximized = true;
            return OnSizeFinish(message, wParam, lParam, handled);
        }
        else {
            if (m_isMaximized) {
                m_isMaximized = false;
                return OnSizeFinish(message, wParam, lParam, handled);
            }
        }

        return 0;
    }

    LRESULT OnKeyUp(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (wParam == 'A' && m_view) {
            --m_bossHealth;
            UpdateBossHealth();
        }

        return DefWindowProc(message, wParam, lParam);
    }

    LRESULT OnMouseMove(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        int xPos = GET_X_LPARAM(lParam);
        int yPos = GET_Y_LPARAM(lParam);

        if (m_view) {
            if (wParam && MK_LBUTTON)
                m_view->InjectMouseDown(Awesomium::kMouseButton_Left);

            m_view->InjectMouseMove(xPos, yPos);
        }
        return 0;
    }

    LRESULT OnMouseLButtonDown(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (m_view) {
            m_view->InjectMouseDown(Awesomium::kMouseButton_Left);
        }
        return 0;
    }

    LRESULT OnMouseLButtonUp(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (m_view) {
            m_view->InjectMouseUp(Awesomium::kMouseButton_Left);
        }
        return 0;
    }

private:
    void CreateD3DResources() {
        auto device = m_device->GetDevice();

        std::vector<char> vs(std::istreambuf_iterator<char>(std::ifstream("FullScreenTriangleVS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
        auto result = device->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_vertexShader);
        ThrowIfFailed(result, "Could not create vertex shader.");

        std::vector<char> ps(std::istreambuf_iterator<char>(std::ifstream("FullScreenTrianglePS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
        result = device->CreatePixelShader(&ps.front(), ps.size(), nullptr, &m_pixelShader);
        ThrowIfFailed(result, "Could not create pixel shader.");

        result = device->CreateSamplerState(&CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()), &m_sampler);
        ThrowIfFailed(result, "Could not create sampler state.");

        vs.assign(std::istreambuf_iterator<char>(std::ifstream("TriangleVS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
        result = device->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_triangleVS);
        ThrowIfFailed(result, "Could not create vertex shader.");

        ps.assign(std::istreambuf_iterator<char>(std::ifstream("TrianglePS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
        result = device->CreatePixelShader(&ps.front(), ps.size(), nullptr, &m_trianglePS);
        ThrowIfFailed(result, "Could not create pixel shader.");

        std::vector<D3D11_INPUT_ELEMENT_DESC> inputElementDesc = {
            { "SV_POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0 },
            { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 4 * sizeof(float) },
        };

        result = device->CreateInputLayout(&inputElementDesc.front(), inputElementDesc.size(), &vs.front(), vs.size(), &m_inputLayout);
        ThrowIfFailed(result, "Unable to create input layout.");

        // Hard coded triangle. Tis a silly idea, but works for the sample.
        Vertex vertices[] = {
            { { 0.0f, 0.5f, 0.5f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.5f, 1.0f } },
            { { 0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } },
            { { -0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 0.0f } },
        };

        D3D11_BUFFER_DESC desc = {
            sizeof(vertices),
            D3D11_USAGE_DEFAULT,
            D3D11_BIND_VERTEX_BUFFER
        };

        D3D11_SUBRESOURCE_DATA data = {
            vertices
        };

        result = device->CreateBuffer(&desc, &data, &m_vertexBuffer);
        ThrowIfFailed(result, "Failed to create vertex buffer.");

        D3D11_BLEND_DESC blendDesc;

        blendDesc.AlphaToCoverageEnable = false;
        blendDesc.IndependentBlendEnable = false;
        blendDesc.RenderTarget[0] = {
            true,
            D3D11_BLEND_SRC_ALPHA,
            D3D11_BLEND_INV_SRC_ALPHA,
            D3D11_BLEND_OP_ADD,
            D3D11_BLEND_ONE,
            D3D11_BLEND_ZERO,
            D3D11_BLEND_OP_ADD,
            D3D11_COLOR_WRITE_ENABLE_ALL
        };

        device->CreateBlendState(&blendDesc, &m_blendState);
    }

    void UpdateBossHealth() {
        auto javascript = std::string("$('#progressbar').progressbar({ value: ") + std::to_string(m_bossHealth) + "}); ";
        m_view->ExecuteJavascript(Awesomium::ToWebString(javascript), Awesomium::WSLit(""));
    }

    void CreateWebView(int width, int height) {
        m_view.reset(m_webCore->CreateWebView(width, height, nullptr, Awesomium::kWebViewType_Offscreen));
        CreateAndSetJSFunctions();

        m_view->SetTransparent(true);
        Awesomium::WebURL url(Awesomium::WSLit(URL));

        m_view->LoadURL(url);
    }

    void CreateAndSetJSFunctions() {
        m_view->set_js_method_handler(this);
        m_jsApp = m_view->CreateGlobalJavascriptObject(Awesomium::WSLit("app"));

        Awesomium::JSObject & appObject = m_jsApp.ToObject();
        appObject.SetCustomMethod(Awesomium::WSLit("skill"), false);

        JsCallerKey key(appObject.remote_id(), Awesomium::WSLit("skill"));
        m_jsFunctions[key] = std::bind(&MainWindow::OnSkill, this, std::placeholders::_1, std::placeholders::_2);
    }

    Awesomium::JSValue OnSkill(Awesomium::WebView * view, Awesomium::JSArray const & args) {
        if (args.size() == 0)
            return Awesomium::JSValue();

        Awesomium::JSValue const & arg = args[0];
        if (!arg.IsInteger())
            return Awesomium::JSValue();

        switch (arg.ToInteger())
        {
        case 1:
            --m_bossHealth;
            UpdateBossHealth();
            break;
        default:
            break;
        }

        return Awesomium::JSValue();
    }
private:
    typedef std::pair<unsigned, Awesomium::WebString> JsCallerKey;
    typedef std::function<Awesomium::JSValue (Awesomium::WebView *, Awesomium::JSArray const &)> JsFunction;

    std::unique_ptr<GraphicsDevice> m_device;

    std::unique_ptr<D3DSurfaceFactory> m_surfaceFactory;
    std::unique_ptr<Awesomium::WebView, void(*)(Awesomium::WebView *)> m_view;
    Awesomium::WebCore *            m_webCore;
    D3DSurface *                    m_surface;

    CComPtr<ID3D11PixelShader>  m_pixelShader;
    CComPtr<ID3D11VertexShader> m_vertexShader;
    CComPtr<ID3D11SamplerState> m_sampler;
    
    CComPtr<ID3D11BlendState>   m_blendState;

    CComPtr<ID3D11Buffer>       m_vertexBuffer;
    CComPtr<ID3D11PixelShader>  m_trianglePS;
    CComPtr<ID3D11VertexShader> m_triangleVS;
    CComPtr<ID3D11InputLayout>  m_inputLayout;

    Awesomium::JSValue          m_jsApp;

    std::map<JsCallerKey, JsFunction> m_jsFunctions;
    std::map<JsCallerKey, JsFunction> m_jsFunctionsWithRetValue;

    int m_bossHealth = 100;

    bool m_isLoading;
    bool m_isMaximized;

    
private:
    static const tchar * ClassName;
    static const tchar * WindowName;
    static const char * URL;
};

const tchar * MainWindow::WindowName    = _T("DX Window");
const tchar * MainWindow::ClassName     = _T("GameWindowClass");
const char * MainWindow::URL            = "file : ///./Resources/UIInterface.html";

int main() {
    Awesomium::WebCore * webCore = Awesomium::WebCore::Initialize(Awesomium::WebConfig());
    {
        MainWindow window(webCore);

        window.Run();
    }
    Awesomium::WebCore::Shutdown();
}



Sweet Snippets - More Using Awesomium and Direct3D

Posted by , 01 January 2015 - - - - - - · 584 views

In the previous entry we built up a basic sample that loads a web page and uploads it to a texture, which we then rendered to a full screen triangle. In this entry we're going to work on optimizing that process a bit, and making it so that our texture updates whenever the source updates.

Introduction
Posted Image
One of the problems with our current mechanism is that we are not handling page updates. I.e. if the page has animations, images that load after some time, and other similar conditions then our image will not be similarly updated with this new information. This poses a problem for us if we're going to use something like Awesomium for a game UI.

The solution is to not stop updating the web client, and to update the image every time it changes. There are, however, a few problems with this:
  • Updating an entire image is slow, especially if the image takes up the entire screen.
  • Rarely does the entire page change, so why update the entire image when only a small portion of it has changed?
We can solve this easily enough by simply knowing which parts of the image need to be changed. Which is where the Awesomium::Surface and Awesomium::SurfaceFactory come into play.

The Surface Factory
Awesomium provides us with the ability to provide it with a custom surface to render to. The rendering code then simply calls to the surface and asks it to blit certain rectangles of data, which we can then translate into the appropriate texture updates. In order for Awesomium to construct one of our surfaces it needs us to provide it with a factory instance capable of constructing the surface. This is where our D3DSurfaceFactory comes in.

The D3DSurfaceFactory is a simple factory which we create an instance of and passing it the appropriate Direct3D context to use for updating textures created from the factory. As you can see below, the implementation of the creation and release methods are fairly trivial, being mostly there to simply pass through any necessary state:
virtual Awesomium::Surface * CreateSurface(Awesomium::WebView * view, int width, int height) {
    return new D3DSurface(m_context, view, width, height);
}

virtual void DestroySurface(Awesomium::Surface * surface) {
    delete surface;
}
With this state passed through we can move onto the meat of our changes... the D3DSurface.

The D3DSurface
The D3DSurface is our implementation of the Awesomium::Surface interface. The Surface interface expects us to provide two methods: Paint, which is called whenever a rectangle of the surface needs to be updated, and Scroll, which is used to scroll a portion of the view. In our case we actually don't care about scrolling, and so we'll leave this method blank. Our paint method, as can be seen below, is fairly trivial:
virtual void Paint(unsigned char *srcBuffer, int srcRowSpan, const Awesomium::Rect &srcRect, const Awesomium::Rect &destRect) {
    auto box = CD3D11_BOX(destRect.x, destRect.y, 0, destRect.x + destRect.width, destRect.y + destRect.height, 1);

    // 4 bytes per pixel, srcRowSpan is already in bytes.
    auto startingOffset = srcRowSpan * srcRect.y + srcRect.x * 4;

    m_context->UpdateSubresource(m_texture, 0, &box, srcBuffer + startingOffset, srcRowSpan, 0);
}
All this really does is convert the destination rectangle into a box, and then calculate the appropriate starting byte in the source buffer. We then simply pass this on through to UpdateSubresource, which does the bulk work of copying our data and uploading it to the GPU.

Miscellaneous other Bits
As a final set of pieces for this demo, we want to render our HTML over somethimg. In this case, that colorful triangle from the first of this series.

To do this we need to change how our Awesomium renders, and also setup a blend state so that our back buffer is blending data instead of overwriting it. Configuring Awesomium views to render transparent turns out to be trivial:
m_view->SetTransparent(true);
Configuring a blendstate is equally as simple:
blendDesc.AlphaToCoverageEnable = false;
blendDesc.IndependentBlendEnable = false;
blendDesc.RenderTarget[0] = {
    true,
    D3D11_BLEND_SRC_ALPHA,
    D3D11_BLEND_INV_SRC_ALPHA,
    D3D11_BLEND_OP_ADD,
    D3D11_BLEND_ONE,
    D3D11_BLEND_ZERO,
    D3D11_BLEND_OP_ADD,
    D3D11_COLOR_WRITE_ENABLE_ALL
};

device->CreateBlendState(&blendDesc, &m_blendState);
With these in place we can now render our colorful triangle:
context->OMSetBlendState(m_blendState, nullptr, ~0);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetIndexBuffer(nullptr, (DXGI_FORMAT)0, 0);

context->IASetVertexBuffers(0, 1, &m_vertexBuffer.p, &Vertex::Stride, &Vertex::Offset);
context->IASetInputLayout(m_inputLayout);
context->VSSetShader(m_triangleVS, nullptr, 0);
context->PSSetShader(m_trianglePS, nullptr, 0);

context->Draw(3, 0);
and then our UI:
context->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
context->IASetInputLayout(nullptr);

context->VSSetShader(m_vertexShader, nullptr, 0);
context->PSSetShader(m_pixelShader, nullptr, 0);
context->PSSetSamplers(0, 1, &m_sampler.p);
if (m_surface)
    m_surface->Bind();

context->Draw(3, 0);
Simple enough eh?

An Example Game UI
With our triangle rendered and our UI being displayed over it, it is time to come up with a UI. For this particular case I'm using the jquery-ui kit, and jquery. I've built a pretty simple HTML web page to showcase what you can do... at the top of it is a progress bar that indicates something, perhaps the health of a boss monster. To the right is a set of quest goals for the currently selected quest. And lastly at the bottom we have a button. It doesn't do anything yet, and perhaps we'll touch on getting that working next time.
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>jQuery UI Progressbar - Default functionality</title>
    <link rel="stylesheet" href="http://code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css">
    <script src="http://code.jquery.com/jquery-1.10.2.js"></script>
    <script src="http://code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
    <link rel="stylesheet" href="jquery-ui.css">
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            height: 100%;
        }

        #wrapper {
            min-height: 100%;
            position: relative;
        }

        #content {
            padding: 10px;
            padding-bottom: 80px; /* Height of the footer element */
        }

        #footer {
            width: 100%;
            height: 80px;
            position: absolute;
            bottom: 0;
            left: 0;
        }

        .center {
            width: 100%;
            text-align: center;

        }
    </style>
    <script language="javascript">
        $(function () {
            $('#progressbar').progressbar({ value: 100 });
            $("#button").button();
            $("#quest-button").button();
            $('#quest-tracker').menu();
            $('#quest-show-hide').menu();
        })
        
    </script>
</head>
<body style="background-color: transparent; min-height: 100%; margin: 0; padding: 0;">
    <div id="wrapper" style="position:relative; min-height: 100%;">
        <div class="center" style="padding-top: 10px;">
            <div id="progressbar" style="max-width: 512px; width: 100%; margin: 0 auto;"/>
        </div>
        <div id="content" style="text-align: right">
            <div style="float: right; color: white">
                <ul id="quest-show-hide" style="width: 200px;">
                    <li><a href="#" onclick="javascript:$('#quest-tracker').toggle(300)">Show/Hide Quests</a></li>
                </ul>
                <ul id="quest-tracker" style="width: 200px;">
                    <li>A quest goal!</li>
                    <li>Another quest goal!</li>
                    
                </ul>
            </div>
        </div>
        <div id="footer">
            <div class="center">
                <button id="button" onclick="javascript:app.skill(1)">
                    A Skill Button
                </button>
            </div>
        </div>
    </div>
</body>
</html>
Lastly, we can send a message to our view anytime we desire using javascript. This allows us to update our boss health as the player hits a key, and the progress bar will change it's value, redrawing only that portion of the screen:
LRESULT OnKeyUp(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
    if (wParam == 'A' && m_view) {
        --m_bossHealth;
        UpdateBossHealth();
    }

    return DefWindowProc(message, wParam, lParam);
}

void UpdateBossHealth() {
    auto javascript = std::string("$('#progressbar').progressbar({ value: ") + std::to_string(m_bossHealth) + "}); ";
    m_view->ExecuteJavascript(Awesomium::ToWebString(javascript), Awesomium::WSLit(""));
}
Now, every time we hit the 'A' key, the boss's health will decrease.

Full Sample
#define NOMINMAX

#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atlapp.h>

#include <d3d11.h>
#include <dxgi.h>
#include <DirectXMath.h>

#include <Awesomium/WebCore.h>
#include <Awesomium/BitmapSurface.h>
#include <Awesomium/STLHelpers.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "awesomium.lib")

#include <string>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <fstream>
#include <vector>

#ifdef UNICODE
typedef wchar_t tchar;
typedef std::wstring tstring;

template<typename T>
tstring to_string(T t) {
    return std::to_wstring(t);
}
#else
typedef char tchar;
typedef std::string tstring;

template<typename T>
tstring to_string(T t) {
    return std::to_string(t);
}
#endif

struct Vertex {
    float position[4];
    float color[4];
    float texCoord[2];

    static const unsigned Stride = sizeof(float) * 10;
    static const unsigned Offset = 0;
};

void ThrowIfFailed(HRESULT result, std::string const & text) {
    if (FAILED(result))
        throw std::runtime_error(text + "");
}

class RenderTarget {
public:
    RenderTarget(ID3D11Texture2D * texture, bool hasDepthBuffer) : m_texture(texture) {
        CComPtr<ID3D11Device> device;
        texture->GetDevice(&device);

        auto result = device->CreateRenderTargetView(m_texture, nullptr, &m_textureRTV);
        ThrowIfFailed(result, "Failed to create back buffer render target.");

        m_viewport = CD3D11_VIEWPORT(m_texture, m_textureRTV);

        result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_D32_FLOAT, static_cast<int>(m_viewport.Width), static_cast<int>(m_viewport.Height), 1, 1, D3D11_BIND_DEPTH_STENCIL), nullptr, &m_depthBuffer);
        ThrowIfFailed(result, "Failed to create depth buffer.");

        result = device->CreateDepthStencilView(m_depthBuffer, nullptr, &m_depthView);
        ThrowIfFailed(result, "Failed to create depth buffer render target.");
    }

    void Clear(ID3D11DeviceContext * context, float color[4], bool clearDepth = true) {
        context->ClearRenderTargetView(m_textureRTV, color);
        if (clearDepth && m_depthView)
            context->ClearDepthStencilView(m_depthView, D3D11_CLEAR_DEPTH, 1.0f, 0);
    }

    void SetTarget(ID3D11DeviceContext * context) {
        context->OMSetRenderTargets(1, &m_textureRTV.p, m_depthView);
        context->RSSetViewports(1, &m_viewport);
    }

private:
    D3D11_VIEWPORT					m_viewport;

    CComPtr<ID3D11Texture2D>		m_depthBuffer;
    CComPtr<ID3D11DepthStencilView> m_depthView;

    CComPtr<ID3D11Texture2D>		m_texture;
    CComPtr<ID3D11RenderTargetView> m_textureRTV;
};

class GraphicsDevice {
public:
    GraphicsDevice(HWND window, int width, int height) {
        D3D_FEATURE_LEVEL levels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };

        DXGI_SWAP_CHAIN_DESC desc = {
            {
                width,
                height,
                { 1, 60 },
                DXGI_FORMAT_R8G8B8A8_UNORM,
                DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
                DXGI_MODE_SCALING_UNSPECIFIED
            },
            { 1, 0 },
            DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
            1,
            window,
            TRUE,
            DXGI_SWAP_EFFECT_DISCARD,
            0
        };

        auto result = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            levels,
            sizeof(levels) / sizeof(D3D_FEATURE_LEVEL),
            D3D11_SDK_VERSION,
            &desc,
            &m_swapChain,
            &m_device,
            &m_featureLevel,
            &m_context
            );
        ThrowIfFailed(result, "Failed to create D3D11 device.");
    }

    void Resize(int width, int height) {
        if (m_renderTarget)
            m_renderTarget.reset();

        auto result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_UNKNOWN, 0);
        ThrowIfFailed(result, "Failed to resize back buffer.");

        CComPtr<ID3D11Texture2D> backBuffer;

        result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void * *>(&backBuffer));
        ThrowIfFailed(result, "Failed to retrieve back buffer surface.");

        m_renderTarget = std::make_unique<RenderTarget>(backBuffer, true);

    }

    void SetAndClearTarget() {
        static float color[] { 0, 0, 0, 0};
        if (!m_renderTarget)
            return;

        m_renderTarget->Clear(m_context, color);
        m_renderTarget->SetTarget(m_context);
    }

    void Present() {
        m_swapChain->Present(0, 0);
    }

    ID3D11Device * GetDevice() { return m_device; }
    ID3D11DeviceContext * GetDeviceContext() { return m_context; }

private:
    D3D_FEATURE_LEVEL				m_featureLevel;

    CComPtr<ID3D11Device>			m_device;
    CComPtr<ID3D11DeviceContext>	m_context;
    CComPtr<IDXGISwapChain>			m_swapChain;

    std::unique_ptr<RenderTarget>   m_renderTarget;
};

class D3DSurface : public Awesomium::Surface {
public:
    D3DSurface(ID3D11DeviceContext * context, Awesomium::WebView * view, int width, int height) : m_context(context), m_view(view), m_width(width), m_height(height) {
        CComPtr<ID3D11Device> device;
        context->GetDevice(&device);

        auto result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1), nullptr, &m_texture);

        result = device->CreateShaderResourceView(m_texture, nullptr, &m_textureView);
    }

    virtual void Paint(unsigned char *srcBuffer, int srcRowSpan, const Awesomium::Rect &srcRect, const Awesomium::Rect &destRect) {
        auto box = CD3D11_BOX(destRect.x, destRect.y, 0, destRect.x + destRect.width, destRect.y + destRect.height, 1);

        // 4 bytes per pixel, srcRowSpan is already in bytes.
        auto startingOffset = srcRowSpan * srcRect.y + srcRect.x * 4;

        m_context->UpdateSubresource(m_texture, 0, &box, srcBuffer + startingOffset, srcRowSpan, 0);
    }

    virtual void Scroll(int dx, int dy, const Awesomium::Rect &clip_rect) {

    }

    void Bind() {
        m_context->PSSetShaderResources(0, 1, &m_textureView.p);
    }

    virtual ~D3DSurface() { }
private:
    CComPtr<ID3D11ShaderResourceView> m_textureView;
    CComPtr<ID3D11Texture2D>          m_texture;
    ID3D11DeviceContext *             m_context;
    Awesomium::WebView *              m_view;
    int                               m_width;
    int                               m_height;
};

class D3DSurfaceFactory : public Awesomium::SurfaceFactory {
public:
    D3DSurfaceFactory(ID3D11DeviceContext * context) : m_context(context) {

    }

    virtual Awesomium::Surface * CreateSurface(Awesomium::WebView * view, int width, int height) {
        return new D3DSurface(m_context, view, width, height);
    }

    virtual void DestroySurface(Awesomium::Surface * surface) {
        delete surface;
    }

private:
    ID3D11DeviceContext * m_context;
};

class MainWindow : public CWindowImpl<MainWindow, CWindow, CFrameWinTraits> {
public:
    DECLARE_WND_CLASS_EX(ClassName, CS_OWNDC | CS_HREDRAW | CS_VREDRAW, COLOR_BACKGROUND + 1);

    MainWindow(Awesomium::WebCore * webCore) : m_webCore(webCore), m_view(nullptr, [](Awesomium::WebView * ptr) { ptr->Destroy(); }), m_isMaximized(false), m_surface(nullptr) {
        RECT rect = { 0, 0, 800, 600 };
        AdjustWindowRectEx(&rect, GetWndStyle(0), FALSE, GetWndExStyle(0));
        Create(nullptr, RECT{ 0, 0, rect.right - rect.left, rect.bottom - rect.top }, WindowName);
        ShowWindow(SW_SHOW);
        UpdateWindow();
    }

    void Run() {
        MSG msg;
        while (true) {
            if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                if (msg.message == WM_QUIT)
                    break;

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else {
                Update();
            }
        }
    }

    void Update() {
        auto context = m_device->GetDeviceContext();
        m_webCore->Update();

        if (m_view->IsLoading()) {
            m_isLoading = true;
        }
        else if (m_isLoading) {
            m_isLoading = false;
            UpdateBossHealth();
            m_webCore->Update();
            
            m_surface = static_cast<D3DSurface *>(m_view->surface());
        }

        m_device->SetAndClearTarget();
        context->OMSetBlendState(m_blendState, nullptr, ~0);
        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        context->IASetIndexBuffer(nullptr, (DXGI_FORMAT)0, 0);

        context->IASetVertexBuffers(0, 1, &m_vertexBuffer.p, &Vertex::Stride, &Vertex::Offset);
        context->IASetInputLayout(m_inputLayout);
        context->VSSetShader(m_triangleVS, nullptr, 0);
        context->PSSetShader(m_trianglePS, nullptr, 0);

        context->Draw(3, 0);

        context->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
        context->IASetInputLayout(nullptr);

        context->VSSetShader(m_vertexShader, nullptr, 0);
        context->PSSetShader(m_pixelShader, nullptr, 0);
        context->PSSetSamplers(0, 1, &m_sampler.p);
        if (m_surface)
            m_surface->Bind();

        context->Draw(3, 0);
        
        m_device->Present();
    }

private:
    BEGIN_MSG_MAP(MainWindow)
        MESSAGE_HANDLER(WM_DESTROY, [](unsigned messageId, WPARAM wParam, LPARAM lParam, BOOL & handled) { PostQuitMessage(0); return 0; });
        MESSAGE_HANDLER(WM_CREATE, OnCreate);
        MESSAGE_HANDLER(WM_SIZE, OnSize);
        MESSAGE_HANDLER(WM_EXITSIZEMOVE, OnSizeFinish);
        MESSAGE_HANDLER(WM_KEYUP, OnKeyUp);
    END_MSG_MAP()

private:
    LRESULT OnCreate(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT rect;
            GetClientRect(&rect);
            m_device = std::make_unique<GraphicsDevice>(m_hWnd, rect.right, rect.bottom);

            auto device = m_device->GetDevice();

            tstring filename(MAX_PATH, 0);
            GetModuleFileName(GetModuleHandle(nullptr), &filename.front(), filename.length());
            filename = filename.substr(0, filename.find_last_of('\\'));
            SetCurrentDirectory(filename.c_str());

            std::vector<char> vs(std::istreambuf_iterator<char>(std::ifstream("FullScreenTriangleVS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            auto result = device->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_vertexShader);
            ThrowIfFailed(result, "Could not create vertex shader.");

            std::vector<char> ps(std::istreambuf_iterator<char>(std::ifstream("FullScreenTrianglePS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            result = device->CreatePixelShader(&ps.front(), ps.size(), nullptr, &m_pixelShader);
            ThrowIfFailed(result, "Could not create pixel shader.");

            result = device->CreateSamplerState(&CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()), &m_sampler);
            ThrowIfFailed(result, "Could not create sampler state.");

            vs.assign(std::istreambuf_iterator<char>(std::ifstream("TriangleVS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            result = device->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_triangleVS);
            ThrowIfFailed(result, "Could not create vertex shader.");

            ps.assign(std::istreambuf_iterator<char>(std::ifstream("TrianglePS.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            result = device->CreatePixelShader(&ps.front(), ps.size(), nullptr, &m_trianglePS);
            ThrowIfFailed(result, "Could not create pixel shader.");

            std::vector<D3D11_INPUT_ELEMENT_DESC> inputElementDesc = {
                { "SV_POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0 },
                { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 4 * sizeof(float) },
            };

            result = device->CreateInputLayout(&inputElementDesc.front(), inputElementDesc.size(), &vs.front(), vs.size(), &m_inputLayout);
            ThrowIfFailed(result, "Unable to create input layout.");

            // Hard coded triangle. Tis a silly idea, but works for the sample.
            Vertex vertices[] = {
                { { 0.0f, 0.5f, 0.5f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.5f, 1.0f } },
                { { 0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } },
                { { -0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 0.0f } },
            };

            D3D11_BUFFER_DESC desc = {
                sizeof(vertices),
                D3D11_USAGE_DEFAULT,
                D3D11_BIND_VERTEX_BUFFER
            };

            D3D11_SUBRESOURCE_DATA data = {
                vertices
            };

            result = device->CreateBuffer(&desc, &data, &m_vertexBuffer);
            ThrowIfFailed(result, "Failed to create vertex buffer.");

            D3D11_BLEND_DESC blendDesc;

            blendDesc.AlphaToCoverageEnable = false;
            blendDesc.IndependentBlendEnable = false;
            blendDesc.RenderTarget[0] = {
                true,
                D3D11_BLEND_SRC_ALPHA,
                D3D11_BLEND_INV_SRC_ALPHA,
                D3D11_BLEND_OP_ADD,
                D3D11_BLEND_ONE,
                D3D11_BLEND_ZERO,
                D3D11_BLEND_OP_ADD,
                D3D11_COLOR_WRITE_ENABLE_ALL
            };

            device->CreateBlendState(&blendDesc, &m_blendState);

            m_surfaceFactory = std::make_unique<D3DSurfaceFactory>(m_device->GetDeviceContext());
            m_webCore->set_surface_factory(m_surfaceFactory.get());

            m_view.reset(m_webCore->CreateWebView(rect.right, rect.bottom, nullptr, Awesomium::kWebViewType_Offscreen));
            m_view->SetTransparent(true);
            Awesomium::WebURL url(Awesomium::WSLit(URL));

            m_view->LoadURL(url);

            m_device->Resize(rect.right, rect.bottom);
        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
            return -1;
        }

        return 0;
    }

    LRESULT OnSizeFinish(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT clientRect;
            GetClientRect(&clientRect);

            m_device->Resize(clientRect.right, clientRect.bottom);

            if (m_view->IsLoading())
                m_view->Stop();

            m_surface = nullptr;

            m_view.reset(m_webCore->CreateWebView(clientRect.right, clientRect.bottom, nullptr, Awesomium::kWebViewType_Offscreen));
            m_view->SetTransparent(true);
            Awesomium::WebURL url(Awesomium::WSLit(URL));

            m_view->LoadURL(url);
        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
        }

        return 0;
    }

    LRESULT OnSize(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (wParam == SIZE_MAXIMIZED) {
            m_isMaximized = true;
            return OnSizeFinish(message, wParam, lParam, handled);
        }
        else {
            if (m_isMaximized) {
                m_isMaximized = false;
                return OnSizeFinish(message, wParam, lParam, handled);
            }
        }

        return 0;
    }

    LRESULT OnKeyUp(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (wParam == 'A' && m_view) {
            --m_bossHealth;
            UpdateBossHealth();
        }

        return DefWindowProc(message, wParam, lParam);
    }

    void UpdateBossHealth() {
        auto javascript = std::string("$('#progressbar').progressbar({ value: ") + std::to_string(m_bossHealth) + "}); ";
        m_view->ExecuteJavascript(Awesomium::ToWebString(javascript), Awesomium::WSLit(""));
    }
private:
    std::unique_ptr<GraphicsDevice> m_device;

    std::unique_ptr<D3DSurfaceFactory> m_surfaceFactory;
    std::unique_ptr<Awesomium::WebView, void(*)(Awesomium::WebView *)> m_view;
    Awesomium::WebCore *            m_webCore;
    D3DSurface *                    m_surface;

    CComPtr<ID3D11PixelShader>  m_pixelShader;
    CComPtr<ID3D11VertexShader> m_vertexShader;
    CComPtr<ID3D11SamplerState> m_sampler;
    
    CComPtr<ID3D11BlendState>   m_blendState;

    CComPtr<ID3D11Buffer>       m_vertexBuffer;
    CComPtr<ID3D11PixelShader>  m_trianglePS;
    CComPtr<ID3D11VertexShader> m_triangleVS;
    CComPtr<ID3D11InputLayout>  m_inputLayout;

    int m_bossHealth = 100;

    bool m_isLoading;
    bool m_isMaximized;
private:
    static const tchar * ClassName;
    static const tchar * WindowName;
    static const char * URL;
};

const tchar * MainWindow::WindowName    = _T("DX Window");
const tchar * MainWindow::ClassName     = _T("GameWindowClass");
const char * MainWindow::URL            = "<INSERT URL HERE>"; // Was: file : ///./Resources/UIInterface.html (remove spaces)

int main() {
    Awesomium::WebCore * webCore = Awesomium::WebCore::Initialize(Awesomium::WebConfig());
    {
        MainWindow window(webCore);

        window.Run();
    }
    Awesomium::WebCore::Shutdown();
}



Sweet Snippets - Rendering Web Pages to Texture using Awesomium and Direct3D

Posted by , 29 December 2014 - - - - - - · 1,414 views

With our familiarity with Direct2D now maximized, it's time to move on to other methods of rendering UI and text! At this juncture you're now familiar with Direct2D and it's power (well, maybe not. But let's pretend you are!). However, what if we wanted to render a UI to the screen? In this entry we'll take a look at Awesomium, a Chromium based HTML UI engine.

Introduction
Posted Image

For rendering UI to the screen you have a few options:
  • you can go the Direct2D route, drawing images to a buffer and then drawing those buffers to the screen, or drawing directly to the back buffer.
  • You can render quads to the screen placed where your buttons will be.
  • You can use some form of a UI library to assist you in drawing such things!
We're going to go for option 3. Now, the framework we're going to be using is called Awesomium. It is not great, but it will do what we need. Some of the problems with it includes a lack of x64 support, some weird behaviors relating to memory management, and some API quirks that are rather annoying. Another option, although a paid for one, is Coherent. However, that aside, Awesomium is fairly easy to get up and running in no time at all. It is based off of Chromium, and hence uses web-kit.

Initializing Awesomium
Initialization is a fairly trivial process. However, the pointer you get out of it needs to be made available to everyone who is going to be using Awesomium. Furthermore, Awesomium doesn't handle threading very well, as such, you should only access it from a single thread. Ever.
int main() {
    Awesomium::WebCore * webCore = Awesomium::WebCore::Initialize(Awesomium::WebConfig());
    {
        MainWindow window(webCore);

        window.Run();
    }
    Awesomium::WebCore::Shutdown();
}
As you can see, it's quite trivial.

Loading a Web Page
Our next step will be to load up some HTML and get it rendering. What better to render than the previous post, of course!
m_view.reset(m_webCore->CreateWebView(rect.right, rect.bottom, nullptr, Awesomium::kWebViewType_Offscreen));
Awesomium::WebURL url(Awesomium::WSLit("http://www.gamedev.net/blog/32/entry-2260630-sweet-snippets-more-text-rendering-with-directwritedirect2d-and-direct3d11/"));

m_view->LoadURL(url);

Now we're getting into one of those quirks. Awesomium has its own string class, and so you need to use the WSLit function to instantiate an instance of it. Additionally, each web view has a size associated with it, which means that any time we change our texture size we'll need to recreate the web view. We tell it to use an off screen view as we actually want to use this as a texture.

Rendering the Page
When the page is done rendering we can get the surface associated with it, and then using UpdateSubresource upload that data to our Direct3D texture.
if (m_view->IsLoading()) {
    m_isLoading = true;
    m_webCore->Update();
}
else if (m_isLoading) {
    m_isLoading = false;
    m_webCore->Update();

    Awesomium::BitmapSurface * surface = static_cast<Awesomium::BitmapSurface *>(m_view->surface());

    context->UpdateSubresource(m_texture, 0, nullptr, surface->buffer(), surface->width() * 4, 0);
}
We keep track of if it was previously loading, so as to note keep updating the texture every time we go through the update loop. After this we simply render a full screen triangle with the updated texture bound as a shader resource:
context->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
context->IASetIndexBuffer(nullptr, (DXGI_FORMAT)0, 0);
context->IASetInputLayout(nullptr);

context->VSSetShader(m_vertexShader, nullptr, 0);
context->PSSetShader(m_pixelShader, nullptr, 0);
context->PSSetSamplers(0, 1, &m_sampler.p);
context->PSSetShaderResources(0, 1, &m_textureView.p);

context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

context->Draw(3, 0);
This snippet uses a convenient trick of using the vertex ID to generate a full screen triangle (with texture coordinates) without the need of a vertex buffer nor index buffer. See this presentation for more information.

Other Things of Note
The only additional thing to note is our handling of WM_SIZE and WM_EXITSIZEMOVE. Due to the fact that Awesomium creates a new process for each view (although you can avoid this by using child views), resizing the window becomes a chore. As such, we ignore all WM_SIZE messages except SIZE_MAXIMIZED ones, or if the window was previously maximized. This allows us to only recreate the view when the window is maximized, restored from a maximized state, or resizing has finished.
LRESULT OnSize(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
    if (wParam == SIZE_MAXIMIZED) {
        m_isMaximized = true;
        return OnSizeFinish(message, wParam, lParam, handled);
    }
    else {
        if (m_isMaximized) {
            m_isMaximized = false;
            return OnSizeFinish(message, wParam, lParam, handled);
        }
    }

    return 0;
}
When this is the case we release our texture, shader view, and web-view, then we recreate them at the new size. Finally we issue a new load request to the same URL as before. When this completes the update loop will properly update the texture with the new data.

Full Sample
#define NOMINMAX

#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atlapp.h>

#include <d3d11.h>
#include <dxgi.h>
#include <DirectXMath.h>

#include <Awesomium/WebCore.h>
#include <Awesomium/BitmapSurface.h>
#include <Awesomium/STLHelpers.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "awesomium.lib")

#include <string>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <algorithm>
#include <fstream>
#include <vector>

#ifdef UNICODE
typedef wchar_t tchar;
typedef std::wstring tstring;
#else
typedef char tchar;
typedef std::string tstring;
#endif

void ThrowIfFailed(HRESULT result, std::string const & text) {
    if (FAILED(result))
        throw std::runtime_error(text + "");
}

class RenderTarget {
public:
    RenderTarget(ID3D11Texture2D * texture, bool hasDepthBuffer) : m_texture(texture) {
        CComPtr<ID3D11Device> device;
        texture->GetDevice(&device);

        auto result = device->CreateRenderTargetView(m_texture, nullptr, &m_textureRTV);
        ThrowIfFailed(result, "Failed to create back buffer render target.");

        m_viewport = CD3D11_VIEWPORT(m_texture, m_textureRTV);

        result = device->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_D32_FLOAT, static_cast<int>(m_viewport.Width), static_cast<int>(m_viewport.Height), 1, 1, D3D11_BIND_DEPTH_STENCIL), nullptr, &m_depthBuffer);
        ThrowIfFailed(result, "Failed to create depth buffer.");

        result = device->CreateDepthStencilView(m_depthBuffer, nullptr, &m_depthView);
        ThrowIfFailed(result, "Failed to create depth buffer render target.");
    }

    void Clear(ID3D11DeviceContext * context, float color[4], bool clearDepth = true) {
        context->ClearRenderTargetView(m_textureRTV, color);
        if (clearDepth && m_depthView)
            context->ClearDepthStencilView(m_depthView, D3D11_CLEAR_DEPTH, 1.0f, 0);
    }

    void SetTarget(ID3D11DeviceContext * context) {
        context->OMSetRenderTargets(1, &m_textureRTV.p, m_depthView);
        context->RSSetViewports(1, &m_viewport);
    }

private:
    D3D11_VIEWPORT					m_viewport;

    CComPtr<ID3D11Texture2D>		m_depthBuffer;
    CComPtr<ID3D11DepthStencilView> m_depthView;

    CComPtr<ID3D11Texture2D>		m_texture;
    CComPtr<ID3D11RenderTargetView> m_textureRTV;
};

class GraphicsDevice {
public:
    GraphicsDevice(HWND window, int width, int height) {
        D3D_FEATURE_LEVEL levels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };

        DXGI_SWAP_CHAIN_DESC desc = {
            {
                width,
                height,
                { 1, 60 },
                DXGI_FORMAT_R8G8B8A8_UNORM,
                DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
                DXGI_MODE_SCALING_UNSPECIFIED
            },
            { 1, 0 },
            DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
            1,
            window,
            TRUE,
            DXGI_SWAP_EFFECT_DISCARD,
            0
        };

        auto result = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            levels,
            sizeof(levels) / sizeof(D3D_FEATURE_LEVEL),
            D3D11_SDK_VERSION,
            &desc,
            &m_swapChain,
            &m_device,
            &m_featureLevel,
            &m_context
            );
        ThrowIfFailed(result, "Failed to create D3D11 device.");
    }

    void Resize(int width, int height) {
        if (m_renderTarget)
            m_renderTarget.reset();

        auto result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_UNKNOWN, 0);
        ThrowIfFailed(result, "Failed to resize back buffer.");

        CComPtr<ID3D11Texture2D> backBuffer;

        result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void * *>(&backBuffer));
        ThrowIfFailed(result, "Failed to retrieve back buffer surface.");

        m_renderTarget = std::make_unique<RenderTarget>(backBuffer, true);

    }

    void SetAndClearTarget() {
        static float color[] { 0, 0, 0, 0};
        if (!m_renderTarget)
            return;

        m_renderTarget->Clear(m_context, color);
        m_renderTarget->SetTarget(m_context);
    }

    void Present() {
        m_swapChain->Present(0, 0);
    }

    ID3D11Device * GetDevice() { return m_device; }
    ID3D11DeviceContext * GetDeviceContext() { return m_context; }

private:
    D3D_FEATURE_LEVEL				m_featureLevel;

    CComPtr<ID3D11Device>			m_device;
    CComPtr<ID3D11DeviceContext>	m_context;
    CComPtr<IDXGISwapChain>			m_swapChain;

    std::unique_ptr<RenderTarget>   m_renderTarget;
};

class MainWindow : public CWindowImpl<MainWindow, CWindow, CFrameWinTraits> {
public:
    DECLARE_WND_CLASS_EX(ClassName, CS_OWNDC | CS_HREDRAW | CS_VREDRAW, COLOR_BACKGROUND + 1);

    MainWindow(Awesomium::WebCore * webCore) : m_webCore(webCore), m_view(nullptr, [](Awesomium::WebView * ptr) { ptr->Destroy(); }), m_isMaximized(false) {
        RECT rect = { 0, 0, 800, 600 };
        AdjustWindowRectEx(&rect, GetWndStyle(0), FALSE, GetWndExStyle(0));
        Create(nullptr, RECT{ 0, 0, rect.right - rect.left, rect.bottom - rect.top }, WindowName);
        ShowWindow(SW_SHOW);
        UpdateWindow();
    }

    void Run() {
        MSG msg;
        while (true) {
            if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                if (msg.message == WM_QUIT)
                    break;

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else {
                Update();
            }
        }
    }

    void Update() {
        auto context = m_device->GetDeviceContext();

        if (m_view->IsLoading()) {
            m_isLoading = true;
            m_webCore->Update();
        }
        else if (m_isLoading) {
            m_isLoading = false;
            m_webCore->Update();

            Awesomium::BitmapSurface * surface = static_cast<Awesomium::BitmapSurface *>(m_view->surface());

            context->UpdateSubresource(m_texture, 0, nullptr, surface->buffer(), surface->width() * 4, 0);
        }

        m_device->SetAndClearTarget();

        context->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
        context->IASetIndexBuffer(nullptr, (DXGI_FORMAT)0, 0);
        context->IASetInputLayout(nullptr);

        context->VSSetShader(m_vertexShader, nullptr, 0);
        context->PSSetShader(m_pixelShader, nullptr, 0);
        context->PSSetSamplers(0, 1, &m_sampler.p);
        context->PSSetShaderResources(0, 1, &m_textureView.p);

        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        context->Draw(3, 0);

        m_device->Present();
    }

private:
    BEGIN_MSG_MAP(MainWindow)
        MESSAGE_HANDLER(WM_DESTROY, [](unsigned messageId, WPARAM wParam, LPARAM lParam, BOOL & handled) { PostQuitMessage(0); return 0; });
        MESSAGE_HANDLER(WM_CREATE, OnCreate);
        MESSAGE_HANDLER(WM_SIZE, OnSize);
        MESSAGE_HANDLER(WM_EXITSIZEMOVE, OnSizeFinish);
    END_MSG_MAP()

private:
    LRESULT OnCreate(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT rect;
            GetClientRect(&rect);
            m_device = std::make_unique<GraphicsDevice>(m_hWnd, rect.right, rect.bottom);

            tstring filename(MAX_PATH, 0);
            GetModuleFileName(GetModuleHandle(nullptr), &filename.front(), filename.length());
            filename = filename.substr(0, filename.find_last_of('\\'));
            SetCurrentDirectory(filename.c_str());

            std::vector<char> vs(std::istreambuf_iterator<char>(std::ifstream("VertexShader.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            auto result = m_device->GetDevice()->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_vertexShader);
            ThrowIfFailed(result, "Could not create vertex shader.");

            std::vector<char> ps(std::istreambuf_iterator<char>(std::ifstream("PixelShader.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator<char>());
            result = m_device->GetDevice()->CreatePixelShader(&ps.front(), ps.size(), nullptr, &m_pixelShader);
            ThrowIfFailed(result, "Could not create pixel shader.");

            result = m_device->GetDevice()->CreateSamplerState(&CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()), &m_sampler);
            ThrowIfFailed(result, "Could not create sampler state.");

            m_device->GetDevice()->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, rect.right, rect.bottom, 1, 1), nullptr, &m_texture);
            ThrowIfFailed(result, "Could not create web texture.");

            m_device->GetDevice()->CreateShaderResourceView(m_texture, nullptr, &m_textureView);
            ThrowIfFailed(result, "Could not create shader view.");

            m_view.reset(m_webCore->CreateWebView(rect.right, rect.bottom, nullptr, Awesomium::kWebViewType_Offscreen));
            Awesomium::WebURL url(Awesomium::WSLit("http://www.gamedev.net/blog/32/entry-2260630-sweet-snippets-more-text-rendering-with-directwritedirect2d-and-direct3d11/"));

            m_view->LoadURL(url);

            m_device->Resize(rect.right, rect.bottom);
        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
            return -1;
        }

        return 0;
    }

    LRESULT OnSizeFinish(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        try {
            RECT clientRect;
            GetClientRect(&clientRect);

            m_device->Resize(clientRect.right, clientRect.bottom);

            if (m_texture)
                m_texture.Release();

            if (m_textureView)
                m_textureView.Release();

            m_device->GetDevice()->CreateTexture2D(&CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_B8G8R8A8_UNORM, clientRect.right, clientRect.bottom, 1, 1), nullptr, &m_texture);

            m_device->GetDevice()->CreateShaderResourceView(m_texture, nullptr, &m_textureView);

            if (m_view->IsLoading())
                m_view->Stop();

            m_view.reset(m_webCore->CreateWebView(clientRect.right, clientRect.bottom, nullptr, Awesomium::kWebViewType_Offscreen));
            Awesomium::WebURL url(Awesomium::WSLit("http://www.gamedev.net/blog/32/entry-2260630-sweet-snippets-more-text-rendering-with-directwritedirect2d-and-direct3d11/"));

            m_view->LoadURL(url);
        }
        catch (std::runtime_error & ex) {
            std::cout << ex.what() << std::endl;
        }

        return 0;
    }

    LRESULT OnSize(unsigned message, WPARAM wParam, LPARAM lParam, BOOL & handled) {
        if (wParam == SIZE_MAXIMIZED) {
            m_isMaximized = true;
            return OnSizeFinish(message, wParam, lParam, handled);
        }
        else {
            if (m_isMaximized) {
                m_isMaximized = false;
                return OnSizeFinish(message, wParam, lParam, handled);
            }
        }

        return 0;
    }
private:
    std::unique_ptr<GraphicsDevice> m_device;
    Awesomium::WebCore *            m_webCore;
    std::unique_ptr<Awesomium::WebView, void(*)(Awesomium::WebView *)> m_view;

    CComPtr<ID3D11PixelShader>  m_pixelShader;
    CComPtr<ID3D11VertexShader> m_vertexShader;
    CComPtr<ID3D11SamplerState> m_sampler;

    CComPtr<ID3D11Texture2D>            m_texture;
    CComPtr<ID3D11ShaderResourceView>   m_textureView;

    bool m_isLoading;
    bool m_isMaximized;
private:
    static const tchar * ClassName;
    static const tchar * WindowName;
};

const tchar * MainWindow::WindowName = _T("DX Window");
const tchar * MainWindow::ClassName = _T("GameWindowClass");

int main() {
    Awesomium::WebCore * webCore = Awesomium::WebCore::Initialize(Awesomium::WebConfig());
    {
        MainWindow window(webCore);

        window.Run();
    }
    Awesomium::WebCore::Shutdown();
}



Sweet Snippets - More Text Rendering with DirectWrite/Direct2D and Direct3D11.

Posted by , 26 December 2014 - - - - - - · 1,172 views

Previously we built ourselves a short little application that displayed some text over a triangle. Rendered using Direct3D11, DirectWrite and Direct2D. There were a few problems with the sample though, and so I've decided to do a followup which shows some changes which fix those issues.

Introduction
Posted Image
When you initially get text rendering with Direct2D, using DrawText and DirectWrite, it feels rather powerful at first. You're able to render text, with a brush of your choosing, to a texture or the screen. But you will quickly find that DrawText is actually not that great of a function. Hence we have the IDWriteTextLayout interface.

This interface allows us the capability to build much more complex text objects, and in fact is used internally by DrawText. The interface provides a great deal of functionality, and so we shall now harness it to enhance the previous example.

But first, we need a goal. Goals are important in every field, including software development. Without an end goal in mind, code quickly begins to wander, and you soon find yourself in dark alleys best not trod. Thus our goal: To be able to render text that includes hyperlinks. These links will render in a fixed width font, with a different color, and when the mouse moves over them we expect our cursor to change from an arrow to a hand. Furthermore, when we click a link we expect it to open the default browser to the URL the link points to.

Using IDWriteTextLayout
The IDWriteTextLayout interface is fairly simple, you construct it by providing it with the text you desire to layout, the bounds of the text, and a default formatter (which provides font information).
auto result = m_factory->CreateTextLayout(m_text.c_str(), m_text.length(), m_defaultFormat, size.x, size.y, &m_textLayout);
As you can see from the snippet above, it's quite trivial to use. But this does raise the question? How does this help us to format our text with links? Well, in our sample we are actually building m_text from smaller bits of text. We use two functions, AppendText and AppendLink to fill the m_text string. However, each time we call AppendLink we also store a few other bits of information: The starting position of the link, and the length, along with the URL associated with this range. As can be seen below:
void AppendText(tstring const & str) {
    m_text.append(str);

    m_dirty = true;
}

void AppendLink(tstring const & str, tstring const & link) {
    DWRITE_TEXT_RANGE linkRange = { m_text.size(), str.length() };
    m_linkRanges.push_back(std::make_pair(linkRange, link));

    m_text.append(str);

    m_dirty = true;
}
We also set a dirty flag, which we use to determine if the text layout object needs to be recreated.

Once we've built up our text we "compile" it into a text layout object. Once we have our IDWriteTextLayout object created with our text, we need to tell it how to format the text to our liking. In our case, we need to tell it about the links in our text and how we desire to have them rendered.

To do this, we simply iterate over the previously saved ranges (from AppendLink) and tell the text layout interface that for those ranges of characters we desire them to be drawn differently. In our case we're going to render them as being a fixed width font (Consolas), underlined, and a nice powdered blue color:
for (auto const & p : m_linkRanges) {
    m_textLayout->SetFontFamilyName(_T("Consolas"), p.first);
    m_textLayout->SetUnderline(true, p.first);
    m_textLayout->SetDrawingEffect(m_linkBrush, p.first);
}
.
Drawing the Text and More
Drawing couldn't be simpler. In fact, it's actually simpler than drawing text using DrawText. Since we've done all the work up front to format our text, all we really have to do is pass it off to Direct2D, along with where on the screen (or texture) we desire to render it.
renderTarget->DrawTextLayout(pos, m_textLayout, m_defaultBrush);
Of course, this is not where we're going to stop, obviously. Now that we have our text rendering nicely to the screen, we obviously now want to be able to detect if the user has their mouse over the links in our text. To do this we need to perform two actions: The first is that we need to trap the WM_MOUSEMOVE and WM_LBUTTONUP Win32 mouse events, the second is that we need some way to detect where the cursor is in relation to our various links. Thankfully, DirectWrite helps out here too!

Since DirectWrite is a text layout engine, it can tell us a lot about the text it's laying out. Including such things as "where is a point in relation to the characters of this IDWriteTextLayout object." The test is quite trivial:
m_textLayout->HitTestPoint(pos.x, pos.y, &isTrailingHit, &isInside, &hitTestMetrics);
With the returned booleans from this function we know if it's hitting the trailing edge of a character, if it's inside the text area at all, and several other metrics from the hit test as well. In our case we will be using the isInside boolean to determine if we should be continuing our tests further, and then from the DWRITE_HIT_TEST_METRICS we'll be using the textPosition member to determine the nearest character to the cursor. WIth that information in hand it's a simple task to iterate over our links (that we stored previously from AppendLink) and check if the textPosition is within the range of characters represented by the link:
for (auto const & p : m_linkRanges) {
    if (hitTestMetrics.textPosition >= p.first.startPosition && hitTestMetrics.textPosition < p.first.startPosition + p.first.length) {
        *linkText = p.second;
        return true;
    }
}
We can then use the information from the hit test to perform various actions, such as using ShellExecute to open the browser to the link location:
LRESULT OnMouseUp(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
    tstring link;
    if (m_textSection->IsOverLink(D2D1::Point2F((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)), &link)) {
        ShellExecute(NULL, _T("open"), link.c_str(), NULL, NULL, SW_SHOWNORMAL);
    }

    return 0;
}
The rest of what you can do is really only limited by your imagination.

Full Sample
#define NOMINMAX

#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atlapp.h>

#include <dxgi.h>
#include <d3d11.h>
#include <d2d1.h>
#include <dwrite.h>
#include <d3dcompiler.h>

#include <memory>
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <string>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
#pragma comment(lib, "d3dcompiler.lib")

#ifdef UNICODE
typedef std::wstring    tstring;
typedef wchar_t         tchar;
#else
typedef std::string     tstring;
typedef char            tchar;
#endif

struct Vertex {
    float position[4];
    float color[4];
    float texCoord[2];
};

class Exception : public std::runtime_error {
public:
    Exception(std::string const & error, HRESULT result) : std::runtime_error(error + "\nError was: " + std::to_string(result)) {
    }
};

class TextSection {
    typedef std::pair<DWRITE_TEXT_RANGE, tstring> LinkPair;
public:
    TextSection(CComPtr<IDWriteFactory> factory) : m_factory(factory), m_dirty(true) {
        auto result = m_factory->CreateTextFormat(_T("Calibri"), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 14.0f, _T(""), &m_defaultFormat);

        if (FAILED(result))
            throw Exception("Failed to create text format.", result);
    }

    void SetDefaultColorBrush(CComPtr<ID2D1SolidColorBrush> defaultBrush) {
        m_defaultBrush = defaultBrush;
    }

    void SetLinkColorBrush(CComPtr<ID2D1SolidColorBrush> linkBrush) {
        m_linkBrush = linkBrush;
    }

    void AppendText(tstring const & str) {
        m_text.append(str);

        m_dirty = true;
    }

    void AppendLink(tstring const & str, tstring const & link) {
        DWRITE_TEXT_RANGE linkRange = { m_text.size(), str.length() };
        m_linkRanges.push_back(std::make_pair(linkRange, link));

        m_text.append(str);

        m_dirty = true;
    }

    void Compile(D2D1_POINT_2F const & size) {
        if (!m_defaultBrush || !m_linkBrush) {
            throw Exception("Default and link color brushes must be set first.", E_FAIL);
        }

        if (m_textLayout) {
            m_textLayout.Release();
        }

        auto result = m_factory->CreateTextLayout(m_text.c_str(), m_text.length(), m_defaultFormat, size.x, size.y, &m_textLayout);
        if (FAILED(result)) {
            throw Exception("Unable to create text layout.", result);
        }

        for (auto const & p : m_linkRanges) {
            m_textLayout->SetFontFamilyName(_T("Consolas"), p.first);
            m_textLayout->SetUnderline(true, p.first);
            m_textLayout->SetDrawingEffect(m_linkBrush, p.first);
        }

        m_dirty = false;
    }

    void Release() {
        m_defaultBrush.Release();
        m_linkBrush.Release();
        m_textLayout.Release();
    }

    void Draw(CComPtr<ID2D1RenderTarget> renderTarget, D2D1_POINT_2F const & pos) {
        if (m_dirty || !m_linkBrush || !m_defaultBrush || !m_textLayout) {
            return;
        }

        renderTarget->DrawTextLayout(pos, m_textLayout, m_defaultBrush);
    }

    bool IsOverLink(D2D1_POINT_2F const & pos, tstring * linkText) {
        BOOL isTrailingHit;
        BOOL isInside;
        DWRITE_HIT_TEST_METRICS hitTestMetrics;

        m_textLayout->HitTestPoint(pos.x, pos.y, &isTrailingHit, &isInside, &hitTestMetrics);

        if (!isInside)
            return false;

        for (auto const & p : m_linkRanges) {
            if (hitTestMetrics.textPosition >= p.first.startPosition && hitTestMetrics.textPosition < p.first.startPosition + p.first.length) {
                if (linkText != nullptr) {
                    *linkText = p.second;
                }
                return true;
            }
        }

        return false;
    }

private:
    CComPtr<IDWriteFactory>         m_factory;
    CComPtr<IDWriteTextFormat>      m_defaultFormat;
    CComPtr<IDWriteTextLayout>      m_textLayout;

    CComPtr<ID2D1SolidColorBrush>   m_defaultBrush;
    CComPtr<ID2D1SolidColorBrush>   m_linkBrush;

    tstring                         m_text;
    std::vector<LinkPair>           m_linkRanges;
    bool                            m_dirty;
};

class MainWindow : public CWindowImpl<MainWindow, CWindow, CFrameWinTraits>, public CIdleHandler {
public:
    MainWindow() {
        m_handCursor = LoadCursor(nullptr, IDC_HAND);
        m_arrowCursor = LoadCursor(nullptr, IDC_ARROW);

        RECT bounds = { 0, 0, 800, 600 };
        AdjustWindowRect(&bounds, WS_OVERLAPPEDWINDOW, false);

        bounds = { 0, 0, bounds.right - bounds.left, bounds.bottom - bounds.top };
        Create(nullptr, bounds, _T("D3DSample Window"), WS_OVERLAPPEDWINDOW);

        ShowWindow(SW_SHOW);
    }

    virtual BOOL OnIdle() {
        Present();

        return true;
    }

    void Present() {
        static float clearColor[] = { 0, 0, 0, 1 };
        {
            m_deviceContext->OMSetRenderTargets(1, &m_backBufferRTV.p, nullptr);
            m_deviceContext->ClearRenderTargetView(m_backBufferRTV, clearColor);

            size_t stride = sizeof(Vertex);
            size_t offsets = 0;
            m_deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer.p, &stride, &offsets);
            m_deviceContext->IASetInputLayout(m_inputLayout);
            m_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

            m_deviceContext->VSSetShader(m_vertexShader, nullptr, 0);
            m_deviceContext->PSSetShader(m_pixelShader, nullptr, 0);
        }
        {
            m_deviceContext->Draw(3, 0);
        }
        {
            m_d2dRenderTarget->BeginDraw();
            m_textSection->Draw(m_d2dRenderTarget, D2D1::Point2F(0, 0));
            m_d2dRenderTarget->EndDraw();
        }
        {
            m_swapChain->Present(0, 0);
        }
    }

public:
    BEGIN_MSG_MAP(MainWindow)
        MESSAGE_HANDLER(WM_DESTROY, [](unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
            PostQuitMessage(0);
            return 0;
        });
        MESSAGE_HANDLER(WM_SIZE, OnSize);
        MESSAGE_HANDLER(WM_CREATE, OnCreate);
        MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove);
        MESSAGE_HANDLER(WM_LBUTTONUP, OnMouseUp);
    END_MSG_MAP()

private:
    void CreateD3DVertexAndShaders() {
        tstring processFilename(MAX_PATH, _T('\0'));
        std::vector<char> vertexShader;
        std::vector<char> pixelShader;

        GetModuleFileName(GetModuleHandle(nullptr), &processFilename.front(), processFilename.length());
        SetCurrentDirectory(processFilename.substr(0, processFilename.find_last_of(_T("\\/"))).data());

        {
            std::ifstream vertexfin("VertexShader.cso", std::ios_base::binary | std::ios_base::in);
            std::copy(std::istreambuf_iterator<char>(vertexfin), std::istreambuf_iterator<char>(), std::back_inserter(vertexShader));
            auto result = m_device->CreateVertexShader(&vertexShader.front(), vertexShader.size(), nullptr, &m_vertexShader);
            if (FAILED(result)) {
                throw Exception("Failed to create vertex shader.", result);
            }
        }

        {
            std::ifstream pixelfin("PixelShader.cso", std::ios_base::binary | std::ios_base::in);
            std::copy(std::istreambuf_iterator<char>(pixelfin), std::istreambuf_iterator<char>(), std::back_inserter(pixelShader));
            auto result = m_device->CreatePixelShader(&pixelShader.front(), pixelShader.size(), nullptr, &m_pixelShader);
            if (FAILED(result)) {
                throw Exception("Failed to create pixel shader.", result);
            }
        }

        CComPtr<ID3DBlob> inputLayoutBlob;
        auto result = D3DGetInputSignatureBlob(&vertexShader.front(), vertexShader.size(), &inputLayoutBlob);
        if (FAILED(result)) {
            throw Exception("Failed to get input layout.", result);
        }

        // Hard coded triangle. Tis a silly idea, but works for the sample.
        Vertex vertices[] = {
            { { 0.0f, 0.5f, 0.5f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.5f, 1.0f } },
            { { 0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 0.0f, 0.0f } },
            { { -0.5f, -0.5f, 0.5f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 0.0f } },
        };

        D3D11_BUFFER_DESC desc = {
            sizeof(vertices),
            D3D11_USAGE_DEFAULT,
            D3D11_BIND_VERTEX_BUFFER
        };

        D3D11_SUBRESOURCE_DATA data = {
            vertices
        };

        result = m_device->CreateBuffer(&desc, &data, &m_vertexBuffer);
        if (FAILED(result)) {
            throw Exception("Failed to create vertex buffer.", result);
        }

        D3D11_INPUT_ELEMENT_DESC inputElementDesc[] = {
            { "SV_POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0 },
            { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 4 * sizeof(float) },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 4 * sizeof(float) },
        };

        result = m_device->CreateInputLayout(inputElementDesc, sizeof(inputElementDesc) / sizeof(D3D11_INPUT_ELEMENT_DESC), inputLayoutBlob->GetBufferPointer(), inputLayoutBlob->GetBufferSize(), &m_inputLayout);
        if (FAILED(result)) {
            throw Exception("Failed to create input layout.", result);
        }
    }

    void CreateD3DResources() {
        D3D_FEATURE_LEVEL featureLevels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };

        // We only want to draw to the portion of the window that is the client rect.
        // This will also work for dialog / borderless windows.
        RECT clientRect;
        GetClientRect(&clientRect);

        DXGI_SWAP_CHAIN_DESC swapChainDesc = {
            { clientRect.right, clientRect.bottom, { 60, 1 }, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_MODE_SCALING_UNSPECIFIED },
            { 1, 0 },
            DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
            1,
            m_hWnd,
            true,
            DXGI_SWAP_EFFECT_DISCARD,
            DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
        };

        // At the moment we don't actually care about what feature level we got back, so we don't keep this around just yet.
        D3D_FEATURE_LEVEL featureLevel;

        auto result = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            // BGRA Support is necessary for D2D functionality.
            D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG,
            // D2D works with all of our feature levels, so we don't actually care which oen we get. 
            featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
            D3D11_SDK_VERSION,
            &swapChainDesc,
            &m_swapChain,
            &m_device,
            &featureLevel,
            &m_deviceContext
            );

        if (FAILED(result)) {
            throw Exception("Failed to create D3D device and DXGI swap chain.", result);
        }

        // And lets create our D2D factory and DWrite factory at this point as well, that way if any of them fail we'll fail out completely.
        auto options = D2D1_FACTORY_OPTIONS();
        options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
        result = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, options, &m_d2dFactory);
        if (FAILED(result)) {
            throw Exception("Failed to create multithreaded D2D factory.", result);
        }

        result = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown * *>(&m_dwFactory));
        if (FAILED(result)) {
            throw Exception("Failed to create DirectWrite Factory.", result);
        }
    }

    void CreateBackBufferTarget() {
        CComPtr<ID3D11Texture2D> backBuffer;

        // Get a pointer to our back buffer texture.
        auto result = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
        if (FAILED(result)) {
            throw Exception("Failed to get back buffer.", result);
        }

        // We acquire a render target view to the entire surface (no parameters), with nothing special about it.
        result = m_device->CreateRenderTargetView(backBuffer, nullptr, &m_backBufferRTV);
        if (FAILED(result)) {
            throw Exception("Failed to create render target view for back buffer.", result);
        }
    }

    void CreateD2DResources() {
        CComPtr<IDXGISurface> bufferSurface;

        // Get a DXGI surface for D2D use.
        auto result = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&bufferSurface));
        if (FAILED(result)) {
            throw Exception("Failed to get DXGI surface for back buffer.", result);
        }

        // Proper DPI support is very important. Most applications do stupid things like hard coding this, which is why you,
        // can't use proper DPI on most monitors in Windows yet.
        float dpiX;
        float dpiY;
        m_d2dFactory->GetDesktopDpi(&dpiX, &dpiY);

        // DXGI_FORMAT_UNKNOWN will cause it to use the same format as the back buffer (R8G8B8A8_UNORM)
        auto d2dRTProps = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), dpiX, dpiY);

        // Wraps up our DXGI surface in a D2D render target.
        result = m_d2dFactory->CreateDxgiSurfaceRenderTarget(bufferSurface, &d2dRTProps, &m_d2dRenderTarget);
        if (FAILED(result)) {
            throw Exception("Failed to create D2D DXGI Render Target.", result);
        }

        result = m_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &m_defaultColorBrush);
        if (FAILED(result)) {
            throw Exception("Failed to create D2D color brush.", result);
        }

        result = m_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::PowderBlue), &m_linkColorBrush);
        if (FAILED(result)) {
            throw Exception("Failed to create D2D color brush.", result);
        }
    }

    void CreateTextResources() {
        m_textSection = std::make_unique<TextSection>(m_dwFactory);
        m_textSection->SetDefaultColorBrush(m_defaultColorBrush);
        m_textSection->SetLinkColorBrush(m_linkColorBrush);

        m_textSection->AppendText(_T("Tutorials are a horrible way to learn.\n\nI've covered that before though, and so have others, so I won't go into a great deal of depth on the subject, but suffice it to say that tutorials don't have the depth nor breadth to cover a subject in any sufficient detail to be terribly useful. If you don't learn to program, and if you don't learn to learn, then you'll always be stuck in ruts like this...\n\nThat being said, I have written a "));
        m_textSection->AppendLink(_T("sweet little snippet to demonstrate exactly how to render text to the screen using Direct2D"), _T("http://www.gamedev.net/blog/32/entry-2260628-sweet-snippets-rendering-text-with-directwritedirect2d-and-direct3d11/"));
        m_textSection->AppendText(_T("."));

        RECT clientRect;;
        GetClientRect(&clientRect);
        m_textSection->Compile(D2D1::Point2F(clientRect.right / 2.0f, clientRect.bottom / 2.0f));
    }
private:
    LRESULT OnMouseUp(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        tstring link;
        if (m_textSection->IsOverLink(D2D1::Point2F((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)), &link)) {
            ShellExecute(NULL, _T("open"), link.c_str(), NULL, NULL, SW_SHOWNORMAL);
        }

        return 0;
    }

    LRESULT OnMouseMove(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        if (m_textSection->IsOverLink(D2D1::Point2F((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)), nullptr)) {
            SetCursor(m_handCursor);
        }
        else {
            SetCursor(m_arrowCursor);
        }

        return 0;
    }

    LRESULT OnSize(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        // We need to release everything that may be holding a reference to the back buffer.
        // This includes D2D interfaces as well, as they hold a reference to the DXGI surface.
        m_textSection->Release();
        m_linkColorBrush.Release();
        m_defaultColorBrush.Release();

        m_d2dRenderTarget.Release();
        m_backBufferRTV.Release();

        // And we make sure that we do not have any render tarvets bound either, which could
        // also be holding references to the back buffer.
        m_deviceContext->ClearState();

        int width = LOWORD(lParam);
        int height = HIWORD(lParam);

        auto result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
        if (FAILED(result)) {
            std::cout << "Failed to resize swap chain." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return -1;
        }

        try {
            // We need to recreate those resources we disposed of above, including our D2D interfaces
            CreateBackBufferTarget();
            CreateD2DResources();
            m_textSection->SetDefaultColorBrush(m_defaultColorBrush);
            m_textSection->SetLinkColorBrush(m_linkColorBrush);

            m_textSection->Compile(D2D1::Point2F(width / 2.0f, height / 2.0f));
        }
        catch (Exception & ex) {
            std::cout << ex.what() << std::endl;
        }

        D3D11_VIEWPORT viewport = {
            0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f
        };

        // We setup our viewport here as the size of the viewport is known at this point, WM_SIZE will be sent after a WM_CREATE.
        m_deviceContext->RSSetViewports(1, &viewport);
        return 0;
    }

    LRESULT OnCreate(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        try {
            CreateD3DResources();
            CreateBackBufferTarget();
            CreateD3DVertexAndShaders();
            CreateD2DResources();
            CreateTextResources();
        }
        catch (Exception & ex) {
            std::cout << ex.what() << std::endl;
            return -1;
        }

        return 0;
    }

private:
    CComPtr<IDXGISwapChain>             m_swapChain;
    CComPtr<ID3D11Device>               m_device;
    CComPtr<ID3D11DeviceContext>        m_deviceContext;
    CComPtr<ID3D11RenderTargetView>     m_backBufferRTV;

    CComPtr<ID3D11Buffer>               m_vertexBuffer;
    CComPtr<ID3D11InputLayout>          m_inputLayout;
    CComPtr<ID3D11VertexShader>         m_vertexShader;
    CComPtr<ID3D11PixelShader>          m_pixelShader;

    CComPtr<ID2D1Factory>               m_d2dFactory;
    CComPtr<ID2D1RenderTarget>          m_d2dRenderTarget;
    CComPtr<ID2D1SolidColorBrush>       m_defaultColorBrush;
    CComPtr<ID2D1SolidColorBrush>       m_linkColorBrush;

    CComPtr<IDWriteFactory>             m_dwFactory;
    CComPtr<IDWriteTextFormat>          m_dwFormat;

    std::unique_ptr<TextSection>        m_textSection;

    HCURSOR                             m_handCursor;
    HCURSOR                             m_arrowCursor;
};

int main() {
    CAppModule appModule;
    CMessageLoop messageLoop;
    MainWindow window;

    appModule.Init(nullptr, GetModuleHandle(nullptr));
    appModule.AddMessageLoop(&messageLoop);

    messageLoop.AddIdleHandler(&window);
    messageLoop.Run();

    appModule.Term();
    return 0;
}



Sweet Snippets - Rendering Text with DirectWrite/Direct2D and Direct3D11.

Posted by , 24 December 2014 - - - - - - · 1,944 views

At one point there was a series called Sweet Snippets. I don't remember where, but I think it was in the C++ Magazine (when such a thing still existed). Anyways, this is not an attempt to resurrect that, however I feel that sometimes certain questions can be answered with a simple sweet snippet of code that demonstrates a simple concept in its entirety. Thus this post (and hopefully more followup ones).

If all you care about is the code (i.e. you're a copy and paste coder), please feel free to skip to the end where the full source is posted.

Introduction
Posted Image
DirectWrite is a Microsoft technology for rendering text and glyphs, as a replacement for GDI. Direct2D is a hardware accelerated 2D rendering technology which can be used in conjunction with DirectWrite to render text to the screen. The combination of the two provides a very powerful mechanism for rendering properly formatted text with minimal effort. Plugging it into your 3D application (game, level editor, etc), gives you a very powerful set of tools for producing text (and other 2d graphics) that can be rendered to a texture and presented in your game on virtual computer screens, projective textures, etc.

For the purposes of this snippet we will be rendering a basic triangle to the screen along with some text that will be drawn over it in a nice lime green.

Initializing Direct3D11
When you're initialing Direct3D11 with the idea of supporting Direct2D in mind you need to be sure to indicate during the device creation that you desire to support the surface formats Direct2D uses. Specifically, Direct2D requires BGRA support as that is the same format GDI uses. We can indicate this to Direct3D11 during device creation by passing in the D3D11_CREATE_DEVICE_BGRA_SUPPORT flag.

Other than that the device creation is quite straightforward:
auto result = D3D11CreateDeviceAndSwapChain(
    nullptr, 
    D3D_DRIVER_TYPE_HARDWARE, 
    nullptr,
    // BGRA Support is necessary for D2D functionality.
    D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG,
    // D2D works with all of our feature levels (10.0 - 11.1), so we don't actually care which oen we get. 
    featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),   
    D3D11_SDK_VERSION, 
    &swapChainDesc, 
    &m_swapChain, 
    &m_device,
    &featureLevel,              
    &m_deviceContext
);
.
Getting Going With Direct2D
Direct2D is not capable of directly talking to a Direct3D11 texture, instead you need to use a DXGI Surface. Thankfully, all D3D textures (since 10) are DXGI surfaces, thus we can simply QueryInterface for the an IDXGISurface on the appropriate texture, or in the case of the back buffer (as in this sample), we simply query for the IDXGISurface from the swap chain.

CComPtr<IDXGISurface> backBufferSurface;

// Get a DXGI surface for D2D use.
auto result = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBufferSurface));
if (FAILED(result)) {
    std::cout << "Failed to get DXGI surface for back buffer." << std::endl;
    std::cout << "Error was: " << std::hex << result << std::endl;
    return result;
}

// DXGI_FORMAT_UNKNOWN will cause it to use the same format as the back buffer (R8G8B8A8_UNORM)
auto d2dRTProps = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), dpiX, dpiY);

// Wraps up our DXGI surface in a D2D render target.
result = m_d2dFactory->CreateDxgiSurfaceRenderTarget(backBufferSurface, &d2dRTProps, &m_d2dRenderTarget);
if (FAILED(result)) {
    std::cout << "Failed to create D2D DXGI Render Target." << std::endl;
    std::cout << "Error was: " << std::hex << result << std::endl;
    return result;
}
At this point, with a Direct2D render target in our hands we're ready to do pretty much anything Direct2D can do, except render text. We can, however, create brushes, draw shapes, etc.

DirectWrite
DirectWrite is not specifically a standalone API. It works in conjunction with other APIs such as Direct2D to properly format text and glyphs for display. It has a great many tools, including the ability to build text layout objects which describe text that has multiple formatting characteristics, and then properly render that layout to the screen with such niceties as word wrapping and breaking (hyphenation), proper character spacing, Unicode handling, etc.

For us, and with such a simple sample in mind, we're going to do the bare minimum necessary to get text onto the screen. That calls for us to simply create a text format, which includes information about the font to use, font size, the weight and style, any stretching information, and the locale.
auto result = m_dwFactory->CreateTextFormat(L"Consolas", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 14.0f, L"", &m_dwFormat);
if (FAILED(result)) {
    std::cout << "Failed to create DirectWrite text format." << std::endl;
    std::cout << "Error was: " << std::hex << result << std::endl;
    return result;
}
.
Rendering Text
At this point we're ready to start rendering to our back buffer. The question is, do we want our text to render infront of whatever is on the screen, or behind it? This is actually something you would have to determine on a case by case basis depending on what the text actually is (for instance, if it's on the screen of a computer in the game, whatever you're holding might obscure it).

In our case we desire the text to be topmost, so we render our text as the last thing in the rendering chain before presenting.
{
    m_deviceContext->ClearRenderTargetView(m_backBufferRTV, clearColor);
    // Draw our triangle first
    m_deviceContext->Draw(3, 0);

    // Then render our text over it.
    m_d2dRenderTarget->BeginDraw();
    m_d2dRenderTarget->DrawText(m_text.c_str(), m_text.length(), m_dwFormat, D2D1::RectF(0, 0, 512, 512), m_d2dSolidBrush);
    m_d2dRenderTarget->EndDraw();

    m_swapChain->Present(0, 0);
}
.
Full Sample
#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <dxgi.h>
#include <d3d11.h>
#include <d2d1.h>
#include <dwrite.h>
#include <d3dcompiler.h>

#include <memory>
#include <iostream>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
#pragma comment(lib, "d3dcompiler.lib")

#ifdef UNICODE
typedef std::wstring    tstring;
typedef wchar_t         tchar;
#else
typedef std::string     tstring;
typedef char            tchar;
#endif

struct Vertex {
    float position[4];
    float color[4];
};

class MainWindow : public CWindowImpl<MainWindow, CWindow, CFrameWinTraits> {
public:
    MainWindow() {
        RECT bounds = { 0, 0, 800, 600 };
        AdjustWindowRect(&bounds, WS_OVERLAPPEDWINDOW, false);

        bounds = { 0, 0, bounds.right - bounds.left, bounds.bottom - bounds.top };
        Create(nullptr, bounds, _T("D3DSample Window"), WS_OVERLAPPEDWINDOW);

        ShowWindow(SW_SHOW);

        // A traditional text. For a traditional time.
        m_text = _T("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
    }

    bool ProcessMessages() {
        MSG msg;

        while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != 0) {
            if (msg.message == WM_QUIT)
                return false;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return true;
    }

    void Present() {
        static float clearColor[] = { 0, 0, 0, 1 };

        {
            m_deviceContext->OMSetRenderTargets(1, &m_backBufferRTV.p, nullptr);
            m_deviceContext->IASetInputLayout(m_inputLayout);
            m_deviceContext->VSSetShader(m_vertexShader, nullptr, 0);
            m_deviceContext->PSSetShader(m_pixelShader, nullptr, 0);
            m_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

            size_t stride = sizeof(Vertex);
            size_t offsets = 0;
            m_deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer.p, &stride, &offsets);
        }
        {
            m_deviceContext->ClearRenderTargetView(m_backBufferRTV, clearColor);
            // Draw our triangle first
            m_deviceContext->Draw(3, 0);

            // Then render our text over it.
            m_d2dRenderTarget->BeginDraw();
            m_d2dRenderTarget->DrawText(m_text.c_str(), m_text.length(), m_dwFormat, D2D1::RectF(0, 0, 512, 512), m_d2dSolidBrush);
            m_d2dRenderTarget->EndDraw();

            m_swapChain->Present(0, 0);
        }
    }
public:
    BEGIN_MSG_MAP(MainWindow)
        MESSAGE_HANDLER(WM_DESTROY, [](unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
            PostQuitMessage(0);
            return 0;
        });
        MESSAGE_HANDLER(WM_SIZE, OnSize);
        MESSAGE_HANDLER(WM_CREATE, OnCreate);
    END_MSG_MAP()

private:
    HRESULT CreateD3DVertexAndShaders() {
        // Hard coded shaders, not a great idea, but works for the sample.
        std::string vertexShader = "struct VS_IN { float4 pos : POSITION; float4 col : COLOR; }; struct PS_IN { float4 pos : SV_POSITION; float4 col : COLOR; }; PS_IN main( VS_IN input ) { PS_IN output = (PS_IN)0; output.pos = input.pos; output.col = input.col; return output; }";
        std::string pixelShader = "struct VS_IN { float4 pos : POSITION; float4 col : COLOR; }; struct PS_IN { float4 pos : SV_POSITION; float4 col : COLOR; }; float4 main( PS_IN input ) : SV_Target { return input.col; }";

        // If compilation fails, we don't report the errors, just that it failed.
        CComPtr<ID3DBlob> vsBlob;
        CComPtr<ID3DBlob> vsError;
        auto result = D3DCompile(vertexShader.c_str(), vertexShader.length() * sizeof(tchar), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vsBlob, &vsError);
        if (FAILED(result)) {
            std::cout << "Failed to compile vertex shader." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // If compilation fails, we don't report the errors, just that it failed.
        CComPtr<ID3DBlob> psBlob;
        CComPtr<ID3DBlob> psError;
        result = D3DCompile(pixelShader.c_str(), pixelShader.length() * sizeof(tchar), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &psBlob, &psError);
        if (FAILED(result)) {
            std::cout << "Failed to compile pixel shader." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        CComPtr<ID3DBlob> inputLayoutBlob;
        result = D3DGetInputSignatureBlob(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayoutBlob);
        if (FAILED(result)) {
            std::cout << "Failed to get input layout." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // Hard coded triangle. Tis a silly idea, but works for the sample.
        Vertex vertices[] = {
            { 0.0, 0.5, 0.5, 1.0, 1.0, 0.0, 0.0, 1.0 },
            { 0.5f, -0.5f, 0.5f, 1.0, 0.0, 1.0, 0.0, 1.0 },
            { -0.5f, -0.5f, 0.5f, 1.0, 0.0, 0.0, 1.0, 1.0 }
        };

        D3D11_BUFFER_DESC desc = {
            sizeof(vertices),
            D3D11_USAGE_DEFAULT,
            D3D11_BIND_VERTEX_BUFFER
        };

        D3D11_SUBRESOURCE_DATA data = {
            vertices
        };

        result = m_device->CreateBuffer(&desc, &data, &m_vertexBuffer);
        if (FAILED(result)) {
            std::cout << "Failed to create vertex buffer." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        D3D11_INPUT_ELEMENT_DESC inputElementDesc[] = {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0 },
            { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16 }
        };

        result = m_device->CreateInputLayout(inputElementDesc, sizeof(inputElementDesc) / sizeof(D3D11_INPUT_ELEMENT_DESC), inputLayoutBlob->GetBufferPointer(), inputLayoutBlob->GetBufferSize(), &m_inputLayout);
        if (FAILED(result)) {
            std::cout << "Failed to create input layout." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        result = m_device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &m_vertexShader);
        if (FAILED(result)) {
            std::cout << "Failed to create vertex shader." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        result = m_device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &m_pixelShader);
        if (FAILED(result)) {
            std::cout << "Failed to create pixel shader." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        return S_OK;
    }
    HRESULT CreateD3DResources() {
        D3D_FEATURE_LEVEL featureLevels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
        };

        // We only want to draw to the portion of the window that is the client rect.
        // This will also work for dialog / borderless windows.
        RECT clientRect;
        GetClientRect(&clientRect);

        DXGI_SWAP_CHAIN_DESC swapChainDesc = {
            { clientRect.right, clientRect.bottom, { 60, 1 }, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, DXGI_MODE_SCALING_UNSPECIFIED },
            { 1, 0 },
            DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
            1,
            m_hWnd,
            true,
            DXGI_SWAP_EFFECT_DISCARD,
            DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
        };

        // At the moment we don't actually care about what feature level we got back, so we don't keep this around just yet.
        D3D_FEATURE_LEVEL featureLevel;

        auto result = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            // BGRA Support is necessary for D2D functionality.
            D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG,
            // D2D works with all of our feature levels, so we don't actually care which oen we get. 
            featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
            D3D11_SDK_VERSION,
            &swapChainDesc,
            &m_swapChain,
            &m_device,
            &featureLevel,
            &m_deviceContext
            );

        if (FAILED(result)) {
            std::cout << "Failed to create D3D device and DXGI swap chain." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // And lets create our D2D factory and DWrite factory at this point as well, that way if any of them fail we'll fail out completely.
        auto options = D2D1_FACTORY_OPTIONS();
        options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
        result = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, options, &m_d2dFactory);
        if (FAILED(result)) {
            std::cout << "Failed to create multithreaded D2D factory." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        result = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown * *>(&m_dwFactory));
        if (FAILED(result)) {
            std::cout << "Failed to create DirectWrite Factory." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        return S_OK;
    }

    HRESULT CreateBackBufferTarget() {
        CComPtr<ID3D11Texture2D> backBuffer;

        // Get a pointer to our back buffer texture.
        auto result = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
        if (FAILED(result)) {
            std::cout << "Failed to get back buffer." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // We acquire a render target view to the entire surface (no parameters), with nothing special about it.
        result = m_device->CreateRenderTargetView(backBuffer, nullptr, &m_backBufferRTV);
        if (FAILED(result)) {
            std::cout << "Failed to create render target view for back buffer." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        return S_OK;
    }

    HRESULT CreateD2DResources() {
        CComPtr<IDXGISurface> backBufferSurface;

        // Get a DXGI surface for D2D use.
        auto result = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBufferSurface));
        if (FAILED(result)) {
            std::cout << "Failed to get DXGI surface for back buffer." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // Proper DPI support is very important. Most applications do stupid things like hard coding this, which is why you,
        // can't use proper DPI on most monitors in Windows yet.
        float dpiX;
        float dpiY;
        m_d2dFactory->GetDesktopDpi(&dpiX, &dpiY);

        // DXGI_FORMAT_UNKNOWN will cause it to use the same format as the back buffer (R8G8B8A8_UNORM)
        auto d2dRTProps = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), dpiX, dpiY);

        // Wraps up our DXGI surface in a D2D render target.
        result = m_d2dFactory->CreateDxgiSurfaceRenderTarget(backBufferSurface, &d2dRTProps, &m_d2dRenderTarget);
        if (FAILED(result)) {
            std::cout << "Failed to create D2D DXGI Render Target." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        // This is the brush we will be using to render our text, it does not need to be a solid color,
        // we could use any brush we wanted. In this case we chose a nice solid red brush.
        result = m_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::LimeGreen), &m_d2dSolidBrush);
        if (FAILED(result)) {
            std::cout << "Failed to create solid color brush." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        return S_OK;
    }

    HRESULT CreateDWriteResources() {
        auto result = m_dwFactory->CreateTextFormat(L"Consolas", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 14.0f, L"", &m_dwFormat);
        if (FAILED(result)) {
            std::cout << "Failed to create DirectWrite text format." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return result;
        }

        return S_OK;
    }

private:
    LRESULT OnSize(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        // We need to release everything that may be holding a reference to the back buffer.
        // This includes D2D interfaces as well, as they hold a reference to the DXGI surface.
        m_backBufferRTV.Release();
        m_d2dRenderTarget.Release();
        m_d2dSolidBrush.Release();

        // And we make sure that we do not have any render tarvets bound either, which could
        // also be holding references to the back buffer.
        m_deviceContext->ClearState();

        int width = LOWORD(lParam);
        int height = HIWORD(lParam);

        auto result = m_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
        if (FAILED(result)) {
            std::cout << "Failed to resize swap chain." << std::endl;
            std::cout << "Error was: " << std::hex << result << std::endl;
            return -1;
        }

        // We need to recreate those resources we disposed of above, including our D2D interfaces
        if (FAILED(CreateBackBufferTarget()))
            return -1;

        if (FAILED(CreateD2DResources())) {
            return -1;
        }

        D3D11_VIEWPORT viewport = {
            0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f
        };

        // We setup our viewport here as the size of the viewport is known at this point, WM_SIZE will be sent after a WM_CREATE.
        m_deviceContext->RSSetViewports(1, &viewport);
        return 0;
    }

    LRESULT OnCreate(unsigned msg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {
        if (FAILED(CreateD3DResources()))
            return -1;

        if (FAILED(CreateBackBufferTarget()))
            return -1;

        if (FAILED(CreateD3DVertexAndShaders()))
            return -1;

        if (FAILED(CreateD2DResources()))
            return -1;

        if (FAILED(CreateDWriteResources()))
            return -1;

        return 0;
    }

private:
    CComPtr<IDXGISwapChain>         m_swapChain;

    CComPtr<ID3D11Device>           m_device;
    CComPtr<ID3D11DeviceContext>    m_deviceContext;

    CComPtr<ID3D11RenderTargetView> m_backBufferRTV;
    CComPtr<ID3D11Buffer>           m_vertexBuffer;
    CComPtr<ID3D11InputLayout>      m_inputLayout;
    CComPtr<ID3D11VertexShader>     m_vertexShader;
    CComPtr<ID3D11PixelShader>      m_pixelShader;

    CComPtr<ID2D1Factory>           m_d2dFactory;
    CComPtr<ID2D1RenderTarget>      m_d2dRenderTarget;
    CComPtr<ID2D1SolidColorBrush>   m_d2dSolidBrush;

    CComPtr<IDWriteFactory>         m_dwFactory;
    CComPtr<IDWriteTextLayout>      m_dwLayout;
    CComPtr<IDWriteTextFormat>      m_dwFormat;

    tstring                         m_text;
};

int main() {
    MainWindow window;
    float clearColor[] = { 0, 0, 0, 0 };

    while (true) {
        if (!window.ProcessMessages())
            break;

        window.Present();
    }
    return 0;
}









PARTNERS