4.13 How to render a text on Direct3D12?

 We have written the following two classes:

1. Direct3D11On12Device to create an ID3D11Resource (ID3D11 wrapped resource).

2. Direct3D11On12WrappedResource to represent the ID3d11 wrapped resource.

This example based on SOL DirectWrite(DXGI), Direct2D1, Direct3D12 classes shows how to draw a text string on a torus rendered on Direct3D12.

Object
ImageFileReader
ComIUnknown
Direct3D12Object
Direct3D11On12Device
Direct3D11On12WrappedResource
  The following Direct3D11On12TextLayout is a slightly complicated sample program to render a text string on a torus which can be rotated by the left and right keys.
In this example, to avoid chaos of APIs of DirectX, we have implemented an inner class DirectXTextRenderer to contain Direct3D11On12WrappedResource , which is a subclass of DirectXGIDevice1, to render a text string.




/*
 * Direct3D11On12TextLayout.cpp 
 * Copyright (c) 2015 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 */


// 2016/11/29
// This is based on the sample program in
// https://github.com/Microsoft/DirectX-Graphics-Samples
// See: DirectX-Graphics-Samples-master\Samples\Desktop\D3D1211On12

// You can rotate a torus by the left and right keys, without changing a text string.
  

#define COMMONCONTROLS_V6
#define WIN10

#include <sol/direct3d12/DirectX3D12MainView.h>
#include <sol/direct3d12/DirectXMatrix.h>
#include <sol/direct3d12/Direct3D11On12Device.h>
#include <sol/direct3d12/Direct3D11On12WrappedResource.h>
#include <sol/direct3d12/Direct3D12CommandAllocator.h>
#include <sol/direct3d12/Direct3D12RenderTargetView.h>
#include <sol/direct3d12/Direct3D12DepthStencilView.h>
#include <sol/direct3d12/Direct3D12TransformConstantBufferView.h>
#include <sol/direct3d12/Direct3D12RootSignature.h>
#include <sol/direct3d12/Direct3D12BarrieredGraphicsCommandList.h>
#include <sol/direct3d12/Direct3D12PipelineState.h>
#include <sol/direct3d12/D3D12RasterizerDesc.h>
#include <sol/direct3d12/D3D12BlendDesc.h>
#include <sol/direct3d12/D3D12GraphicsPipelineStateDesc.h>
#include <sol/direct3d12/D3D12Transform.h>
#include <sol/direct3d12/D3D12GraphicsPipelineStateDesc.h>
#include <sol/direct3d12/Direct3D12Synchronizer.h>

#include <sol/direct3d12/Direct3DX12Torus.h>
#include <sol/direct3d12/DirectXTransform.h>
#include <sol/direct3d12/Direct3D12CommonDescriptorHeap.h>

#include <sol/directx/Direct2D1Factory3.h>
#include <sol/directx/Direct2D1Device2.h>
#include <sol/directx/Direct2D1DeviceContext2.h>
#include <sol/directx/Direct2D1Bitmap1.h>
#include <sol/directx/Direct2D1RenderTarget.h>
#include <sol/directx/Direct2D1SolidColorBrush.h>
#include <sol/directx/DirectWriteFactory.h> 
#include <sol/directx/DirectWriteTextFormat.h>
#include <sol/directx/DirectWriteTextLayout.h>
#include <sol/dxgi/DirectXGIDevice1.h>

#include "resource.h"

namespace SOL {
  
class MainView :public DirectX3D12MainView {
private:
  static const int FRAME_COUNT = 3;
  
private:
  //Inner class starts.
  //-----------------------------------------------------------------------------------------
  class DirectXTextRenderer : public  DirectXGIDevice1 {
    
  private:    
    static const int               LAYOUT_WIDTH  = 280;
    static const int               LAYOUT_HEIGHT = 280;
      SmartPtr<Direct2D1Device2>              d2d1Device2;
    SmartPtr<Direct2D1DeviceContext2>       d2d1DeviceContext2;
    
    SmartPtr<Direct3D11On12WrappedResource> wrappedBackBuffers[FRAME_COUNT];
    SmartPtr<Direct2D1Bitmap1>              d2d1RenderTargets1[FRAME_COUNT];
    SmartPtr<Direct2D1Factory3>             d2d1Factory3;
    SmartPtr<DirectWriteFactory>            writeFactory;
    SmartPtr<DirectWriteTextFormat>         textFormat;
    SmartPtr<DirectWriteTextLayout>         textLayout;
    SmartPtr<Direct2D1SolidColorBrush>      colorBrush;

    StringT<wchar_t>               text;

    void createWrappedBackBuffers(ID3D11On12Device* d3d11On12Device,
                              Direct3D12RenderTargetView* renderTargetView)
    {
    try {
      float dpiX;
      float dpiY;
      d2d1Factory3->getDesktopDpi(&dpiX, &dpiY);

      for (UINT n = 0; n < FRAME_COUNT; n++) {
        wrappedBackBuffers[n] = new Direct3D11On12WrappedResource(d3d11On12Device, 
                renderTargetView->getResource(n));
        IDXGISurface* surface = nullptr;
        wrappedBackBuffers[n]->queryInterface(IID_PPV_ARGS(&surface));
        
        d2d1RenderTargets1[n] = new Direct2D1Bitmap1(*d2d1DeviceContext2, surface,
              dpiX,
              dpiY);
        surface->Release();
      }
    }
    catch (Exception& ex) {
      caught(ex);
    }
  }
    
  public:
    //
    //Inner class constructor
    DirectXTextRenderer(ID3D11On12Device* d3d11On12Device,
                  Direct3D12RenderTargetView* renderTargetView,
                  const wchar_t* string)
    :DirectXGIDevice1(d3d11On12Device),
    text(string)
    {
      d2d1Factory3       = new Direct2D1Factory3();
             
      d2d1Device2        = new Direct2D1Device2(*d2d1Factory3, *this);
  
      d2d1DeviceContext2 = new Direct2D1DeviceContext2(*d2d1Device2);

      createWrappedBackBuffers(d3d11On12Device, renderTargetView);

      writeFactory      = new DirectWriteFactory();
      textFormat           = new DirectWriteTextFormat(*writeFactory,
                             L"Gabriola",
                             NULL,
                             DWRITE_FONT_WEIGHT_NORMAL,
                             DWRITE_FONT_STYLE_NORMAL,
                             DWRITE_FONT_STRETCH_NORMAL,
                             40.0f,  
                             L"");      

      textFormat -> setTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
      textFormat -> setParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
        
      textLayout = new DirectWriteTextLayout(*writeFactory,
                             (const wchar_t*)text,
                              text.getLength(),
                              *textFormat,
                              (float)LAYOUT_WIDTH,
                              (float)LAYOUT_HEIGHT);
    
      const wchar_t* dragon = L"dragon";
      int pos = text.findPosition(dragon);

      DWRITE_TEXT_RANGE textRange = {pos, wcslen(dragon) }; //"dragon" range in text
      textLayout -> setFontSize(80.0f, textRange);
      textLayout -> setUnderline(TRUE, textRange);
      
      colorBrush = new Direct2D1SolidColorBrush(*d2d1DeviceContext2,
                    D2D1::ColorF(D2D1::ColorF::Red));
    }

    ~DirectXTextRenderer()
    {
    }

    void drawLayout(int frameIndex)
    {
      try {
        D2D1_SIZE_F rtSize   = d2d1RenderTargets1[frameIndex]->getSize();
        D2D1_RECT_F textRect = D2D1::RectF(0, 0, rtSize.width, rtSize.height);
     
        wrappedBackBuffers[frameIndex]->acquire();
        d2d1DeviceContext2->setTarget(*d2d1RenderTargets1[frameIndex]);
      
        d2d1DeviceContext2->beginDraw();
        d2d1DeviceContext2->setTransform(D2D1::Matrix3x2F::Identity());

        D2D1_POINT_2F origin = D2D1::Point2F(0, 0);
        d2d1DeviceContext2->drawTextLayout(
            origin,
            *textLayout,
            *colorBrush
          );
        
        d2d1DeviceContext2->endDraw();
        wrappedBackBuffers[frameIndex]->release();
      }
      catch (Exception& ex) {
        caught(ex);
      }
    }    
  };
  //Inner class ends.
  //-----------------------------------------------------------------------------------------
    
private:

  SmartPtr<Direct3D12CommandQueue>         commandQueue;
  SmartPtr<Direct3D12CommandAllocator>     commandAllocators[FRAME_COUNT]; 
  SmartPtr<DirectXGISwapChain3>            swapChain;
  SmartPtr<Direct3D12RenderTargetView>     renderTargetView;
  SmartPtr<Direct3D12DepthStencilView>     depthStencilView;
  SmartPtr<Direct3D12RootSignature>        rootSignature;
  SmartPtr<Direct3D12CommonDescriptorHeap>  commonDescriptorHeap;
  SmartPtr<Direct3D12TransformConstantBufferView>   constantBufferView;
  SmartPtr<Direct3D12GraphicsCommandList> graphicsCommandList;
  SmartPtr<Direct3D12PipelineState>       pipelineState;
  SmartPtr<Direct3D12Synchronizer>        synchronizer;
  SmartPtr<Direct3DX12Torus>              sphere;
  UINT                                    frameIndex;
  DirectXTransform                        worldViewProjection;
  float                                   angle;
  StringT<TCHAR>                          directory;
  
  SmartPtr<Direct3D11On12Device>          d3d11On12Device;
  SmartPtr<DirectXTextRenderer>           textRenderer;
    
public:
  
  virtual void createPipelineState(ID3D12Device* device)
  {
    //Create a graphicPipelineStateDes.
    D3D12GraphicsPipelineStateDesc graphicsPipelineStateDesc(*rootSignature);
      
    UINT count = 0;
    const D3D12_INPUT_ELEMENT_DESC* inputElements = sphere->getInputElementDesc(count);
      
    D3D12RasterizerDesc  rasterDesc(D3D12_FILL_MODE_WIREFRAME, D3D12_CULL_MODE_NONE,  true);
      
    D3D12BlendDesc       blendDesc;
            
    graphicsPipelineStateDesc.setInputLayput(inputElements, count);
      
    graphicsPipelineStateDesc.setRasterizerState(rasterDesc);
    graphicsPipelineStateDesc.setBlendState(blendDesc);
      
    StringT<TCHAR> vsshaderFile = getShaderFilePath(directory, _T("VertexShader.cso"));
    StringT<TCHAR> psshaderFile = getShaderFilePath(directory, _T("PixelShader.cso"));

    graphicsPipelineStateDesc.setVertexShader(vsshaderFile);
    graphicsPipelineStateDesc.setPixelShader(psshaderFile);

    pipelineState = new Direct3D12PipelineState(device, graphicsPipelineStateDesc);
  }

  void setDirectXTransform()
  {
    int width = 0;
    int height = 0;
    getClientSize(width, height);

    try {
      XMVECTOR eye = XMVectorSet(0.0f, 2.0f,-6.0f, 0.0f);
      XMVECTOR at  = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
      XMVECTOR up  = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

      worldViewProjection.world = DirectXMatrix::rotationY(angle);
      worldViewProjection.view = DirectXMatrix::lookAtLH(eye, at, up);
      worldViewProjection.projection = DirectXMatrix::perspectiveFovLH(XM_PIDIV2*0.3f, width / (FLOAT)height, 0.01f, 100.0f);

      if (constantBufferView != nullptr) {
        constantBufferView->update(worldViewProjection);
      }
    }
    catch (Exception& ex) {
      caught(ex);
    }
  }


public:
  void deleteViews()
  {
    if (graphicsCommandList) {
      graphicsCommandList->setOMRenderTargets(0, nullptr, FALSE, nullptr);
    }
      renderTargetView = nullptr;
      depthStencilView = nullptr;
  }

  virtual void createViews()
  {
    int width = 0;
    int height = 0;
    validateClientSize(width, height);

    try {      
      Direct3D12Device*   device  = getD3D12Device();      
      DirectXGISwapChain3* swapChain = getSwapChain3();

      renderTargetView = new Direct3D12RenderTargetView(*device, *swapChain, FRAME_COUNT);
      depthStencilView = new Direct3D12DepthStencilView(*device, width, height);

    } catch (Exception& ex) {
      caught(ex);
    }
  }
  
  DirectXGISwapChain3* getSwapChain3()
  {
    return swapChain;
  }
  
  Direct3D12CommandQueue* getCommandQueue()
  {
    return commandQueue;
  }
  
  virtual void initialize()
  {
    int width  = 0;
    int height = 0;
    validateClientSize(width, height);
    
    DirectXGIFactory4* factory = getDXGIFactory4(); //From DirectX3D12MainView

    Direct3D12Device*  device  = getD3D12Device();  //From DirectX3D12MainView

    try {
      commandQueue             = new Direct3D12CommandQueue(*device);
  
      for (int i = 0; i < FRAME_COUNT; i++) {
        commandAllocators[i] = new Direct3D12CommandAllocator(*device);
      }

      swapChain                = new DirectXGISwapChain3(*factory, *commandQueue, FRAME_COUNT,
                                      getWindow(), width, height, false);
       
      d3d11On12Device          =  new Direct3D11On12Device(*device, *commandQueue);

      createViews();
      
      textRenderer             = new DirectXTextRenderer(*d3d11On12Device, renderTargetView,
                                   L"The dragon wing of night o'erspreads the earth.");
        
      graphicsCommandList      = new Direct3D12GraphicsCommandList(*device, *commandAllocators[0]);
        
      rootSignature            = new Direct3D12RootSignature(*device);

      commonDescriptorHeap     = new Direct3D12CommonDescriptorHeap(*device, 1);

      constantBufferView       = new Direct3D12TransformConstantBufferView(*device,
                                            commonDescriptorHeap->getCPUHandle(0) );

      synchronizer             = new Direct3D12Synchronizer(*device, *commandQueue);
   
      sphere                   = new Direct3DX12Torus(*device, 0.2f, 0.5f, 20, 20);
                
      createPipelineState(*device);

      setDirectXTransform();

    } catch (Exception& ex) {
      caught(ex);
    }
  }
  
  bool ready()
  {
    Direct3D12Device*    device          = getD3D12Device();
    DirectXGISwapChain3* swapChain       = getSwapChain3();
    Direct3D12CommandQueue* commandQueue = getCommandQueue();
      
    if (
        commandQueue          == nullptr ||
        swapChain             == nullptr ||
        renderTargetView      == nullptr ||
        depthStencilView      == nullptr ||
        rootSignature         == nullptr ||
        commonDescriptorHeap  == nullptr ||
        constantBufferView    == nullptr ||
        graphicsCommandList   == nullptr ||
        pipelineState         == nullptr ||
        synchronizer          == nullptr ||
        sphere                == nullptr ||
        textRenderer          == nullptr) {

      return false;
    }
    return true;
  }
  
  virtual void display()
  {
    int width  = 0;
    int height = 0;
    validateClientSize(width, height);
    if ( !ready() ) {
      return;
    }
    
    try {
        DirectXGISwapChain3*    swapChain = getSwapChain3();
        Direct3D12CommandQueue* commandQueue = getCommandQueue();
        
        setDirectXTransform();

        frameIndex = swapChain -> getCurrentBackBufferIndex();

        commandAllocators[frameIndex]->reset();

        graphicsCommandList->reset(*commandAllocators[frameIndex], nullptr);

        D3D12ResourceBarrier barrier(renderTargetView->getResource(frameIndex));

        barrier.startRendering();

        graphicsCommandList->resourceBarrier(1, barrier);

        graphicsCommandList->setDescriptorHeap(*commonDescriptorHeap);

        graphicsCommandList->setGraphicsRootSignature(*rootSignature);

        graphicsCommandList->setPipelineState(*pipelineState);
        
        graphicsCommandList->setGraphicsRootDescriptorTable(0, 
                commonDescriptorHeap->getGPUHandle(0));

        graphicsCommandList-> setRSViewport(0, 0, width, height);

        graphicsCommandList-> setRSScissorRect(0, 0, width, height);

        D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = renderTargetView->getHandle(frameIndex);
                 
        graphicsCommandList->clearRenderTargetView(rtvHandle, XMColor(0.0f, 0.0f, 0.0f, 1.0f));

        D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = *depthStencilView;
        
        graphicsCommandList->clearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH);
        
        graphicsCommandList->setOMRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
       
        sphere->drawIndexedInstanced(graphicsCommandList);

        barrier.endRendering();

        graphicsCommandList->resourceBarrier(1, barrier);

        graphicsCommandList->close();

        commandQueue->executeCommandList(*graphicsCommandList);

        textRenderer -> drawLayout(frameIndex);

        d3d11On12Device->flush();
      
        swapChain->present(1, 0);
        
        synchronizer->waitForCompletion();
       
    } catch (Exception& ex) {
        caught(ex);
    }
  }
    
public:
  /**
   * Constructor
   */
  MainView(Application& applet, const TCHAR* name, Args& args)
  :DirectX3D12MainView(applet, name,
                 args.set(XmNstyle, (ulong)WS_CLIPCHILDREN) )
  {
    directory = (const TCHAR*)args.get(XmNapplicationDirectory);
    angle = 1.0f;
    restorePlacement();

    postResizeRequest(); 
  }

public:
  ~MainView()
  {
  }

private:

  virtual void resize(int width, int height)
  {
    Direct3D12Device*   device = getD3D12Device();
    DirectXGISwapChain3* swapChain = getSwapChain3();
    if (device           == nullptr || 
        swapChain        == nullptr) { 
      return ;
    }
      
    try {
      deleteViews();
      
      swapChain->resizeBuffers(width, height);
      
      createViews();
    } catch (Exception& ex) {
      caught(ex);
    }
  }
  
  virtual void keyDown(Event& event)
  {
    WPARAM wparam = event.getWParam();
    switch(wparam) {
    case VK_LEFT:
        angle += 0.1f;
        display();
        break;
        
    case VK_RIGHT:
        angle -= 0.1f;
        display();
        break;
    }  
  }
};

}


//////////////////////////////////////////////
//
void  Main(int argc, TCHAR** argv)
{
  const TCHAR* appClass = appName(argv[0]); 
  TCHAR directory[MAX_PATH];
  appDirectory(argv[0], directory, CountOf(directory));

  try {    
    Application applet(appClass, argc, argv);

    Args args;
    args.set(XmNwidth,  700);
    args.set(XmNheight, 480);
    args.set(XmNapplicationDirectory, directory);

    MainView mainv(applet, appClass, args);
    
    mainv.realize();

    applet.run();
  } catch (Exception& ex) {
    caught(ex);
  }
}


Last modified: 1 Dec 2016

Copyright (c) 2016 Antillia.com ALL RIGHTS RESERVED.