Jump to content
  • Advertisement
  • entries
    146
  • comments
    436
  • views
    198489

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

Sign in to follow this  
Washu

3498 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


Screenshot%202014-12-29%2021.04.24.png



For rendering UI to the screen you have a few options:

  1. 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.
  2. You can render quads to the screen placed where your buttons will be.
  3. 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(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 #include #include #include #include #include #include #include #include #include #pragma comment(lib, "d3d11.lib")#pragma comment(lib, "awesomium.lib")#include #include #include #include #include #include #include #ifdef UNICODEtypedef wchar_t tchar;typedef std::wstring tstring;#elsetypedef char tchar;typedef std::string tstring;#endifvoid 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 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(m_viewport.Width), static_cast(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 m_depthBuffer; CComPtr m_depthView; CComPtr m_texture; CComPtr 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 backBuffer; result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&backBuffer)); ThrowIfFailed(result, "Failed to retrieve back buffer surface."); m_renderTarget = std::make_unique(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 m_device; CComPtr m_context; CComPtr m_swapChain; std::unique_ptr m_renderTarget;};class MainWindow : public CWindowImpl {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(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(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 vs(std::istreambuf_iterator(std::ifstream("VertexShader.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator()); auto result = m_device->GetDevice()->CreateVertexShader(&vs.front(), vs.size(), nullptr, &m_vertexShader); ThrowIfFailed(result, "Could not create vertex shader."); std::vector ps(std::istreambuf_iterator(std::ifstream("PixelShader.cso", std::ios_base::in | std::ios_base::binary)), std::istreambuf_iterator()); 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 m_device; Awesomium::WebCore * m_webCore; std::unique_ptr m_view; CComPtr m_pixelShader; CComPtr m_vertexShader; CComPtr m_sampler; CComPtr m_texture; CComPtr 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();}
Sign in to follow this  


5 Comments


Recommended Comments

I wanted to use Awesomium as a gui for my project, do you have experiences in such directions ? is it good for a typical gui usage , think of the titanium sdk, with its sleek graphics, and did you notice speed slowing down due to the rendering offscreen and uploading a big texture in realtime on the screen ???

Share this comment


Link to comment

I am really enjoying the sweet snippets series of articles - they are well written, have interesting topics, and will be useful to many people.  Great job!

Share this comment


Link to comment

I wanted to use Awesomium as a gui for my project, do you have experiences in such directions ? is it good for a typical gui usage , think of the titanium sdk, with its sleek graphics, and did you notice speed slowing down due to the rendering offscreen and uploading a big texture in realtime on the screen ???

I'm actually going to handle part of your questions in another snippet. But lets talk a bit about the rendering bit:

 

Awesomium (and most HTML rendering engines) use another process entirely to do the rendering, then there's some IPC that happens to move the data over to your process (which is done invisibly in the m_webCore->Update() calls).

 

As for updating a full screen texture: Yes, there is a reasonable amount of lag, but your UI rarely takes up the entire screen, so there are two options:

 

1. if you're rendering full screen, provide a custom Surface which captures the dirty rects that need upating and only updates those portions of the texture (which is what I'm going to show in the next snippet).

2. Only render to a texture of an appropriate size for the piece of the UI you wish to show. For instance if you have a menu, you might only render to a texture of an appropriate size for the menu (512x512, for instance).

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!