[DX11] Sprite/Text Rendering

Started by
9 comments, last by Necrolis 13 years, 2 months ago
So I decided recently to update to directx 11, and first thing on my list to get going where SDF based fonts, however I couldn't find a thing on 2d drawing in Dx11, other than MJP's code in his Depth Sprites example and in Hieroglyph3. I didn't understand it much but thought if I could get it running in my code and play with it I would be able to understand it (I have read the description of his shader in another post, so I get what the maths is doing, plus I saw the new (-1,1) (1,-1) scheme that I was unaware of in previous attempts). this is the code I'm using (Z-buffering is disabled):
class FontManager
{
private:
ID3D11SamplerState* pSamplerState;
ID3D11VertexShader* pVertexShader;
ID3D11PixelShader* pPixelShader;
ID3D11InputLayout* pLayout;
ID3D11Buffer* pConstantBuffer;
ID3D11Buffer* pVertexBuffer;
ID3D11Buffer* pInstanceBuffer;
ID3D11Buffer* pIndexBuffer;

struct FontVertex
{
D3DXVECTOR2 pPosition;
D3DXVECTOR2 pTexture;
};

struct GlyphInstance
{
D3DXMATRIX pTransform;
D3DXVECTOR4 pColor;
struct DrawRect
{
float fXpos;
float fYpos;
float fWidth;
float fHeight;
}pDrawRect;
};

public:
FontManager(ID3D11Device* pDevice)
{
Create(pDevice);
}

~FontManager()
{
Destroy();
}

BOOL Create(ID3D11Device* pDevice)
{
ID3D10Blob* pErrorMessage;
ID3D10Blob* pVertexShaderBuffer;
ID3D10Blob* pPixelShaderBuffer;
D3D11_BUFFER_DESC pConstantBufferDesc;
D3D11_SAMPLER_DESC pSamplerDesc;

if(FAILED(D3DX11CompileFromFile(L"Shaders\\Text.hlsl",NULL,NULL,"SpriteVS","vs_5_0",D3D10_SHADER_ENABLE_STRICTNESS,0,NULL,&pVertexShaderBuffer,&pErrorMessage,NULL)))
{
if(pErrorMessage)
ShaderError(pErrorMessage,NULL,L"Shaders\\Text.hlsl");
else
MessageBox(NULL,L"Shaders\\Text.hlsl",L"Missing Shader File",MB_OK);

return FALSE;
}

if(FAILED(D3DX11CompileFromFile(L"Shaders\\Text.hlsl",NULL,NULL,"SpritePS","ps_5_0",D3D10_SHADER_ENABLE_STRICTNESS,0,NULL,&pPixelShaderBuffer,&pErrorMessage,NULL)))
{
if(pErrorMessage)
ShaderError(pErrorMessage,NULL,L"Shaders\\Text.hlsl");
else
MessageBox(NULL,L"Shaders\\Text.hlsl",L"Missing Shader File",MB_OK);

return false;
}

if(FAILED(pDevice->CreateVertexShader(pVertexShaderBuffer->GetBufferPointer(),pVertexShaderBuffer->GetBufferSize(),NULL,&pVertexShader)))
return FALSE;

if(FAILED(pDevice->CreatePixelShader(pPixelShaderBuffer->GetBufferPointer(),pPixelShaderBuffer->GetBufferSize(),NULL,&pPixelShader)))
return FALSE;

D3D11_INPUT_ELEMENT_DESC pFontLayout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TRANSFORM", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "TRANSFORM", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "TRANSFORM", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "TRANSFORM", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "SOURCERECT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 80, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};

unsigned int nElements = ARRAY(pFontLayout);
if(FAILED(pDevice->CreateInputLayout(pFontLayout,nElements,pVertexShaderBuffer->GetBufferPointer(),pVertexShaderBuffer->GetBufferSize(),&pLayout)))
return FALSE;

pVertexShaderBuffer->Release();
pPixelShaderBuffer->Release();

FontVertex pFontQuad[] =
{
{D3DXVECTOR2(0.0f, 0.0f),D3DXVECTOR2(0.0f, 0.0f)},
{D3DXVECTOR2(1.0f, 0.0f),D3DXVECTOR2(1.0f, 0.0f)},
{D3DXVECTOR2(1.0f, 1.0f),D3DXVECTOR2(1.0f, 1.0f)},
{D3DXVECTOR2(0.0f, 1.0f),D3DXVECTOR2(0.0f, 1.0f)}
};

D3D11_BUFFER_DESC pVertexBufferDesc, pInstanceBufferDesc, pIndexBufferDesc;
D3D11_SUBRESOURCE_DATA pVertexData, pIndexData;

pVertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
pVertexBufferDesc.ByteWidth = sizeof(FontVertex) * 4;
pVertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
pVertexBufferDesc.CPUAccessFlags = 0;
pVertexBufferDesc.MiscFlags = 0;
pVertexBufferDesc.StructureByteStride = 0;
pVertexData.pSysMem = pFontQuad;
pVertexData.SysMemPitch = 0;
pVertexData.SysMemSlicePitch = 0;
if(FAILED(pDevice->CreateBuffer(&pVertexBufferDesc,&pVertexData,&pVertexBuffer)))
return FALSE;

pInstanceBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
pInstanceBufferDesc.ByteWidth = sizeof(GlyphInstance) * 1024;
pInstanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
pInstanceBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
pInstanceBufferDesc.MiscFlags = 0;
pInstanceBufferDesc.StructureByteStride = 0;
if(FAILED(pDevice->CreateBuffer(&pInstanceBufferDesc,NULL,&pInstanceBuffer)))
return FALSE;

UINT pFontQuadLayout[] = {0, 1, 2, 3, 0, 2};
pIndexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
pIndexBufferDesc.ByteWidth = sizeof(UINT) * 6;
pIndexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
pIndexBufferDesc.CPUAccessFlags = 0;
pIndexBufferDesc.MiscFlags = 0;
pIndexBufferDesc.StructureByteStride = 0;
pIndexData.pSysMem = pFontQuadLayout;
pIndexData.SysMemPitch = 0;
pIndexData.SysMemSlicePitch = 0;
if(FAILED(pDevice->CreateBuffer(&pIndexBufferDesc,&pIndexData,&pIndexBuffer)))
return FALSE;

pConstantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
pConstantBufferDesc.ByteWidth = sizeof(D3DXVECTOR4);
pConstantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
pConstantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
pConstantBufferDesc.MiscFlags = 0;
pConstantBufferDesc.StructureByteStride = 0;
if(FAILED(pDevice->CreateBuffer(&pConstantBufferDesc,NULL,&pConstantBuffer)))
return FALSE;

pSamplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
pSamplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
pSamplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
pSamplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
pSamplerDesc.MipLODBias = 0.0f;
pSamplerDesc.MaxAnisotropy = 1;
pSamplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
pSamplerDesc.BorderColor[0] = 0;
pSamplerDesc.BorderColor[1] = 0;
pSamplerDesc.BorderColor[2] = 0;
pSamplerDesc.BorderColor[3] = 0;
pSamplerDesc.MinLOD = 0;
pSamplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
if(FAILED(pDevice->CreateSamplerState(&pSamplerDesc,&pSamplerState)))
return FALSE;

return TRUE;
}

void Destroy()
{
pSamplerState->Release();
pVertexShader->Release();
pPixelShader->Release();
pLayout->Release();
pConstantBuffer->Release();
pVertexBuffer->Release();
pInstanceBuffer->Release();
pIndexBuffer->Release();
}

void Render(ID3D11DeviceContext* pDeviceContext, ID3D11ShaderResourceView* pTexture, const wchar_t* wszString)
{
D3DXMATRIX pTransform;
D3DXMatrixIdentity(&pTransform);
GlyphInstance pInstance = {pTransform,D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f),{10,10,256,256}};
D3D11_MAPPED_SUBRESOURCE pMap;
if(FAILED(pDeviceContext->Map(pInstanceBuffer,0,D3D11_MAP_WRITE_DISCARD,0,&pMap)))
return;

*(GlyphInstance*)pMap.pData = pInstance;
pDeviceContext->Unmap(pInstanceBuffer,0);
pDeviceContext->IASetInputLayout(pLayout);
pDeviceContext->VSSetShader(pVertexShader,NULL,0);
pDeviceContext->PSSetShader(pPixelShader,NULL,0);
pDeviceContext->PSSetSamplers(0,1,&pSamplerState);
unsigned int dwStride[] = {sizeof(FontVertex),sizeof(GlyphInstance)};
unsigned int dwOffset[] = {0,0};
ID3D11Buffer* pBuffers[] = {pVertexBuffer,pInstanceBuffer};
pDeviceContext->IASetVertexBuffers(0,2,pBuffers,dwStride,dwOffset);
pDeviceContext->IASetIndexBuffer(pIndexBuffer,DXGI_FORMAT_R32_UINT,0);
pDeviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
D3D11_MAPPED_SUBRESOURCE pMappedResource;
if(FAILED(pDeviceContext->Map(pConstantBuffer,0,D3D11_MAP_WRITE_DISCARD,0,&pMappedResource)))
return;

D3DXVECTOR4* pData = reinterpret_cast<D3DXVECTOR4*>(pMappedResource.pData);
*pData = D3DXVECTOR4(256,256,1024,768);//hc'ed for now: texture is 256x256, viewport is 1024x768
pDeviceContext->Unmap(pConstantBuffer,0);
pDeviceContext->VSSetConstantBuffers(0,1,&pConstantBuffer);
pDeviceContext->PSSetShaderResources(0,1,&pTexture);
pDeviceContext->DrawIndexedInstanced(6,1,0,0,0);
}
};
Can anyone (MJP?) exlpain to me what I'm missing, as this draws nothing :(...

EDIT: just to make it clear, this is about my third attempt at creating a ortho projecting VS, best I got was building a ortho transform matrix and mul'ing by that, but that cause quite a few other problems. The code about attempts to rending out the entire font texture to screen as one giant quad at (10,10), just a a test. It might also help to note that this code doesn't crash, nor does it stall the pipeline, its just not rendering anything, at least in the viewport...
Advertisement
Did some more 'work' today trying to get this to work, rethought the transform matrix too:
void Render(ID3D11DeviceContext* pDeviceContext, ID3D11ShaderResourceView* pTexture, const wchar_t* wszString)
{
D3DXMATRIX pTransform;
D3DXMatrixTranslation(D3DXMatrixIdentity(&pTransform),300,300,1.0f);
//D3DXMatrixTransformation(&
//D3DXMatrixOrthoLH(&pOrthoMatrix,fWidth,fHeight,fScreenNear,fScreenDepth);
//D3DXMatrixOrthoOffCenterLH(&pTransform,-1.0f,1.0f,-1.0f,1.0f,1.0f,1000.0f);
GlyphInstance pInstance = {pTransform,D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f),{0,0,256,256}};
D3D11_MAPPED_SUBRESOURCE pMap;
if(FAILED(pDeviceContext->Map(pInstanceBuffer,0,D3D11_MAP_WRITE_DISCARD,0,&pMap)))
return;

*(GlyphInstance*)pMap.pData = pInstance;
pDeviceContext->Unmap(pInstanceBuffer,0);
pDeviceContext->IASetInputLayout(pLayout);
pDeviceContext->VSSetShader(pVertexShader,NULL,0);
pDeviceContext->PSSetShader(pPixelShader,NULL,0);
pDeviceContext->PSSetSamplers(0,1,&pSamplerState);
unsigned int dwStride[] = {sizeof(FontVertex),sizeof(GlyphInstance)};
unsigned int dwOffset[] = {0,0};
ID3D11Buffer* pBuffers[] = {pVertexBuffer,pInstanceBuffer};
pDeviceContext->IASetVertexBuffers(0,2,pBuffers,dwStride,dwOffset);
pDeviceContext->IASetIndexBuffer(pIndexBuffer,DXGI_FORMAT_R32_UINT,0);
pDeviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
D3D11_MAPPED_SUBRESOURCE pMappedResource;
if(FAILED(pDeviceContext->Map(pConstantBuffer,0,D3D11_MAP_WRITE_DISCARD,0,&pMappedResource)))
return;

D3DXVECTOR4* pData = reinterpret_cast<D3DXVECTOR4*>(pMappedResource.pData);
*pData = D3DXVECTOR4(256,256,1024,768);
pDeviceContext->Unmap(pConstantBuffer,0);
pDeviceContext->VSSetConstantBuffers(0,1,&pConstantBuffer);
pDeviceContext->PSSetShaderResources(0,1,&pTexture);
pDeviceContext->DrawIndexedInstanced(6,1,0,0,0);
}


running through PIX, I get this for pre and post VS:
pixdebug.png

It seems that the z & w components are the problem, but I have no clue what could be causing them to become borked. just for reference my near are far planes are 0.1 & 1000 respectively, and the PS/VS is the sprite.hlsl from hieroglyph3
Well... i'm not very experienced, but i use this method to write text (i will change it in a future for a better performance one, because this one needs to create two textures per text, but in case you are not too worried by performance...).

If you know how to draw a textured quad, you'll be able to use it very easily.

Anyway i see you are using C++ and i'll give you C# code. Don't even know if the libraries i will talk about exist in c++, but in case they exist here i go:

You need
- System.Drawing
- System.DrawingImaging


//First of all you need to have a font type and create a bitmap where you are going to draw the text (create it accordingly to your quads size)
Font f = font;
Bitmap bmp = new Bitmap(canvasSize.X, canvasSize.Y);

//Then you crete a StringFormat object to specify text alignments etc.
StringFormat sf = StringFormat.GenericDefault;

if (alignment == Alignments.Center) //Example for cenetered text
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Near;
}

//Then create a graphics object to be able to write in the bmp image
Graphics g = Graphics.FromImage(bmp);
g.PageUnit = GraphicsUnit.Pixel;
g.Clear(Color.Transparent);
g.TextRenderingHint = font.Rendering;

//then create a brush (with a color) and write the text in the bmp file specifying text coordinates and alignment
Brush brush = new SolidBrush(color);
g.DrawString(txt, font, brush, textCoordinate.X, textCoordinate.Y, sf);

//Then create a stream and save the file to that stream with the desired image format
MemoryStream stream = new MemoryStream();
bmp.Save(stream, ImageFormat.Png);

//Finally you should load the texture from that stream with directx and use that texture to draw the quad.


Hope it helps.
you might try using the
wszString you are passing in your Render call. I am assuming that you are attempting to draw those characters, but since they are never used, well.. you get the idea.

Also, I am wondering where the actual text characters are stored? they are never created in your Create function. You just make a bunch of empty stuff and never initialize it.

There is too much to explain in detail. I suggest looking over his code again because you are missing some code.

You have to create a texture with the the letters of the alphabet and the other characters . " / ( and numbers. Then use that for your character drawing through the use of instancing.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

you might try using the
wszString you are passing in your Render call. I am assuming that you are attempting to draw those characters, but since they are never used, well.. you get the idea.

Also, I am wondering where the actual text characters are stored? they are never created in your Create function. You just make a bunch of empty stuff and never initialize it.

There is too much to explain in detail. I suggest looking over his code again because you are missing some code.

You have to create a texture with the the letters of the alphabet and the other characters . " / ( and numbers. Then use that for your character drawing through the use of instancing.

Wish it was that simple :P but actually its not meant to render text atm, hence why there is no text data nor is the string data used, its only meant to render one hardcoded quad (which is textured with the entire pre-rendered sprite sheet, which I load somewhere else and pass to the render method).
the relevant lines are these:


D3DXMATRIX pTransform;
D3DXMatrixTranslation(D3DXMatrixIdentity(&pTransform),300,300,1.0f);
//D3DXMatrixTransformation(&
//D3DXMatrixOrthoLH(&pOrthoMatrix,fWidth,fHeight,fScreenNear,fScreenDepth);
//D3DXMatrixOrthoOffCenterLH(&pTransform,-1.0f,1.0f,-1.0f,1.0f,1.0f,1000.0f);
GlyphInstance pInstance = {pTransform,D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f),{0,0,256,256}};//HERE: this is one quad, positioned at (300,300) with a size of 256x256, UV sampling from (0,0)
D3D11_MAPPED_SUBRESOURCE pMap;
if(FAILED(pDeviceContext->Map(pInstanceBuffer,0,D3D11_MAP_WRITE_DISCARD,0,&pMap)))
return;

*(GlyphInstance*)pMap.pData = pInstance;
pDeviceContext->Unmap(pInstanceBuffer,0);


@Cubo: My problem is not creating the text, its rendering the real time instanced quads to draw them on
Be careful with your matrices...by default the shaders will expect column-major matrices in constant buffers. So you need to either transpose before setting them into the constant buffer, or transpose in the shader, or mark them as "row_major" in the constant buffer declaration, or compile your shaders with D3D10_SHADER_PACK_MATRIX_ROW_MAJOR.

Be careful with your matrices...by default the shaders will expect column-major matrices in constant buffers. So you need to either transpose before setting them into the constant buffer, or transpose in the shader, or mark them as "row_major" in the constant buffer declaration, or compile your shaders with D3D10_SHADER_PACK_MATRIX_ROW_MAJOR.
This was the problem with the verts, transposing the matrix now yields 'correct' coordinates, however, nothing seems to be drawn(even forcing the PS to output a static color doesn't show anything), the 3d stuff in the background seems to render ok. I've check all my blend/raster/sampler states and made them match those from your depth sprites example(except with src/inv src alpha blending), so is there anything else that could cause the quad to not get drawn?

EDIT: GOT IT! seems I missed this:

pRasterDesc.DepthClipEnable = TRUE;
setting it to false solves the problem.

However, seeing as I have a 'generic' topic title, I might as well add another question:
Is it possible to use the z-buffer in 2d mode to order the drawn quads, allow render-order independent ordering? (if that makes any sense, thinking of using z here how CSS treats z, or like photoshop layers: mouse layer, debug output layer, overlay layer, alert & message boxes layer, main UI layer etc)
well, I suppose that you could write your own values to the depth buffer in order to accomplish those goals, but you would have to clear the depth buffer before this pass. I have never tried it before, but you could pass along a depth value that you want each UI component to have and then write that value to the depth buffer. I have never tried this, but it seems like it should work. However, the problem then becomes, if your c++ code knows what layer your UI components should be, then you should sort the UI stuff on the CPU, and then render the components in a back to front order with depth checking turned off.

But, which ever way you go, keep asking questions. Glad you got your problem fixed.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
Been spending most of my (limited) free time getting the text renderer kitted out with all sorts of options and fancy things, however, I've come across something a little annoying:


when scaling using the transform matrix, it scales from the center(and thus grows outwards in 4 directions), is it possible to set something akin to the rotation center for scaling so that I can get scaling from the upper left corner (and thus growing down and to the right)? or am I just going to have to adjust the instance data to account for the unidirectional growth.

secondly, I'm not doing any rotation of the sprites (as its for text, which I will never need to rotate), so is it possible to hijack some members of the matrix that deal with the rotation, and re-purpose them?
I only need to members, one to index into a float4 to select a channel, and one to use as a per instance constant (basically just a switch to tell the pixel shader to bold text or not).
The 'object' space vertices are centered around the origin, which makes the scale expand or contract from the center. If you want to scale the vertex positions from a different point then you have to apply a translation to move the top left vertex to the origin before applying the scaling. This can be done with a concatenated matrix, or you can change the way that the vertices are generated in the first place.

This topic is closed to new replies.

Advertisement