123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
- // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
- // SPDX-License-Identifier: MIT
- #include <TestFramework.h>
- #include <Renderer/Font.h>
- #include <Renderer/Renderer.h>
- #include <Image/Surface.h>
- #include <Jolt/Core/Profiler.h>
- const int cSpacingH = 2; // Number of pixels to put horizontally between characters
- const int cSpacingV = 2; // Number of pixels to put vertically between characters
- Font::Font(Renderer *inRenderer) :
- mRenderer(inRenderer)
- {
- }
- bool
- Font::Create(const char *inFontName, int inCharHeight)
- {
- JPH_PROFILE("Create");
- // Initialize
- mFontName = inFontName;
- mCharHeight = inCharHeight;
- mHorizontalTexels = 64;
- mVerticalTexels = 64;
- // Check font name length
- if (mFontName.size() >= LF_FACESIZE)
- return false;
- // Create font
- LOGFONTA font_desc;
- memset(&font_desc, 0, sizeof(font_desc));
- font_desc.lfHeight = mCharHeight;
- font_desc.lfWeight = FW_NORMAL;
- font_desc.lfCharSet = DEFAULT_CHARSET;
- font_desc.lfOutPrecision = OUT_DEFAULT_PRECIS;
- font_desc.lfClipPrecision = CLIP_DEFAULT_PRECIS;
- font_desc.lfQuality = ANTIALIASED_QUALITY;
- font_desc.lfPitchAndFamily = VARIABLE_PITCH;
- strcpy_s(font_desc.lfFaceName, mFontName.c_str());
- HFONT font = CreateFontIndirectA(&font_desc);
- if (font == nullptr)
- return false;
- // Create a DC for the font
- HDC dc = CreateCompatibleDC(nullptr);
- if (dc == nullptr)
- {
- DeleteObject(font);
- return false;
- }
- // Select the font
- SelectObject(dc, font);
- SetMapMode(dc, MM_TEXT);
- // Get text metrics
- TEXTMETRICA textmetric;
- if (!GetTextMetricsA(dc, &textmetric))
- {
- DeleteObject(font);
- DeleteDC(dc);
- return false;
- }
- // Compute spacing
- ABC widths[256];
- if (!GetCharABCWidthsA(dc, 0, 255, widths))
- {
- DeleteObject(font);
- DeleteDC(dc);
- return false;
- }
- for (int idx1 = 0; idx1 < cNumChars; ++idx1)
- for (int idx2 = 0; idx2 < cNumChars; ++idx2)
- {
- int spacing = int(widths[idx1 + cBeginChar].abcB) + widths[idx1 + cBeginChar].abcC + widths[idx2 + cBeginChar].abcA;
- JPH_ASSERT(spacing >= 0 && spacing <= 0xff);
- mSpacing[idx1][idx2] = (uint8)spacing;
- }
- // Adjust spacing for kerning pairs
- DWORD pair_count = GetKerningPairsA(dc, 0, nullptr);
- if (pair_count > 0)
- {
- LPKERNINGPAIR pairs = new KERNINGPAIR [pair_count];
- GetKerningPairsA(dc, pair_count, pairs);
- for (DWORD i = 0; i < pair_count; ++i)
- if (pairs[i].wFirst >= cBeginChar && pairs[i].wFirst < cEndChar && pairs[i].wSecond >= cBeginChar && pairs[i].wSecond < cEndChar)
- {
- int idx1 = pairs[i].wFirst - cBeginChar;
- int idx2 = pairs[i].wSecond - cBeginChar;
- int new_spacing = (int)mSpacing[idx1][idx2] + pairs[i].iKernAmount;
- JPH_ASSERT(new_spacing >= 0 && new_spacing <= 0xff);
- mSpacing[idx1][idx2] = (uint8)new_spacing;
- }
- delete [] pairs;
- }
- // Create surface for characters
- Ref<SoftwareSurface> surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
- surface->Clear();
- surface->Lock(ESurfaceLockMode::Write);
- // Identity transform
- MAT2 identity;
- memset(&identity, 0, sizeof(identity));
- identity.eM11.value = 1;
- identity.eM22.value = 1;
- // Draw all printable characters
- try_again:;
- int x = 0, y = 0;
- static_assert(cBeginChar == ' ', "We skip space in the for loop below");
- for (int c = cBeginChar + 1; c < cEndChar; ++c)
- {
- // Get index in the arrays
- int idx = c - cBeginChar;
- // Check if there is room on this line
- if (int(x + widths[c].abcB + cSpacingH) > mHorizontalTexels)
- {
- // Next line
- x = 0;
- y += textmetric.tmHeight + cSpacingV;
- // Check if character fits
- if (y + textmetric.tmHeight + cSpacingV > mVerticalTexels)
- {
- // Character doesn't fit, enlarge surface
- if (mHorizontalTexels < 2 * mVerticalTexels)
- mHorizontalTexels <<= 1;
- else
- mVerticalTexels <<= 1;
- // Create new surface
- surface->UnLock();
- surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
- surface->Clear();
- surface->Lock(ESurfaceLockMode::Write);
- // Try again with the larger texture
- goto try_again;
- }
- }
- // Get location of character in font surface
- JPH_ASSERT(x >= 0 && x <= 0xffff);
- JPH_ASSERT(y >= 0 && y <= 0xffff);
- JPH_ASSERT(widths[c].abcB <= 0xff);
- mStartU[idx] = uint16(x);
- mStartV[idx] = uint16(y);
- mWidth[idx] = uint8(widths[c].abcB);
- // Get character data size
- GLYPHMETRICS metrics;
- int char_size = GetGlyphOutlineA(dc, c, GGO_GRAY8_BITMAP, &metrics, 0, nullptr, &identity);
- if (char_size != 0)
- {
- // Allocate room for character
- uint8 *char_data = new uint8 [char_size];
- // Get character
- GetGlyphOutlineA(dc, c, GGO_GRAY8_BITMAP, &metrics, char_size, char_data, &identity);
- uint src_pitch = (metrics.gmBlackBoxX + 3) & ~uint(3);
- // Copy the character data
- for (uint src_y = 0, dst_y = y; src_y < metrics.gmBlackBoxY; ++src_y, ++dst_y)
- {
- uint8 *src = char_data + src_y * src_pitch;
- uint8 *dst = surface->GetScanLine(dst_y + int(textmetric.tmHeight - textmetric.tmDescent - metrics.gmptGlyphOrigin.y)) + x;
- for (uint src_x = 0; src_x < metrics.gmBlackBoxX; ++src_x, ++src, ++dst)
- *dst = uint8(min(int(*src) << 2, 255));
- }
- // Destroy temporary character data
- delete [] char_data;
- }
- // Go to the next character
- x += widths[c].abcB + cSpacingH;
- }
- // Unlock surface
- surface->UnLock();
- // Release GDI objects
- DeleteObject(font);
- DeleteDC(dc);
- // Create input layout
- const D3D12_INPUT_ELEMENT_DESC vertex_desc[] =
- {
- { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
- { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
- { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
- };
- // Load vertex shader
- ComPtr<ID3DBlob> vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader.hlsl");
- // Load pixel shader
- ComPtr<ID3DBlob> pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader.hlsl");
- mPipelineState = mRenderer->CreatePipelineState(vtx.Get(), vertex_desc, ARRAYSIZE(vertex_desc), pix.Get(), D3D12_FILL_MODE_SOLID, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaTest, PipelineState::ECullMode::Backface);
- // Create texture
- mTexture = mRenderer->CreateTexture(surface);
- // Trace success
- Trace("Created font \"%s\" with height %d in a %dx%d surface", mFontName.c_str(), mCharHeight, mHorizontalTexels, mVerticalTexels);
- return true;
- }
- Float2 Font::MeasureText(const string_view &inText) const
- {
- JPH_PROFILE("MeasureText");
- Float2 extents(0, 1.0f);
- // Current raster position
- float x = 0;
- // Loop through string
- for (uint i = 0; i < inText.size(); ++i)
- {
- // Get character
- int ch = inText[i];
- // Create character if it is printable
- static_assert(cBeginChar == ' ', "We skip space in the for loop below");
- if (ch > cBeginChar && ch < cEndChar)
- {
- // Update extents
- int c1 = ch - cBeginChar;
- extents.x = max(extents.x, x + float(mWidth[c1]) / mCharHeight);
- }
- // Go to next (x, y) location
- if (ch == '\n')
- {
- // Next line
- x = 0;
- extents.y += 1.0f;
- }
- else if (i + 1 < inText.size())
- {
- // Do spacing between the two characters
- int c1 = ch - cBeginChar;
- int c2 = inText[i + 1] - cBeginChar;
- if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
- x += float(mSpacing[c1][c2]) / mCharHeight;
- }
- }
- return extents;
- }
- bool Font::CreateString(Mat44Arg inTransform, const string_view &inText, ColorArg inColor, RenderPrimitive &ioPrimitive) const
- {
- JPH_PROFILE("CreateString");
- // Reset primitive
- ioPrimitive.Clear();
- // Count the number of printable chars
- int printable = 0;
- for (uint i = 0; i < inText.size(); ++i)
- {
- int ch = inText[i];
- static_assert(cBeginChar == ' ', "We skip space in the for loop below");
- if (ch > cBeginChar && ch < cEndChar) // Space is not printable
- printable++;
- }
- if (printable == 0)
- return false;
- // Get correction factor for texture size
- float texel_to_u = 1.0f / mHorizontalTexels;
- float texel_to_v = 1.0f / mVerticalTexels;
- int vtx_size = printable * 4;
- int idx_size = printable * 6;
- ioPrimitive.CreateVertexBuffer(vtx_size, sizeof(FontVertex));
- ioPrimitive.CreateIndexBuffer(idx_size);
- // Current vertex
- uint32 vtx = 0;
- // Lock buffers
- FontVertex *font_vtx = (FontVertex *)ioPrimitive.LockVertexBuffer();
- uint32 *idx_start = ioPrimitive.LockIndexBuffer();
- uint32 *idx = idx_start;
- // Current raster position
- float x = 0, y = -1.0f;
- // Loop through string
- for (uint i = 0; i < inText.size(); ++i)
- {
- // Get character
- int ch = inText[i];
- // Create character if it is printable
- static_assert(cBeginChar == ' ', "We skip space in the for loop below");
- if (ch > cBeginChar && ch < cEndChar)
- {
- // Get index for character
- int c1 = ch - cBeginChar;
- // Create indices
- *idx = vtx;
- ++idx;
- *idx = vtx + 3;
- ++idx;
- *idx = vtx + 1;
- ++idx;
- *idx = vtx;
- ++idx;
- *idx = vtx + 2;
- ++idx;
- *idx = vtx + 3;
- ++idx;
- vtx += 4;
- // Get properties of this character
- Float2 uv_start(texel_to_u * mStartU[c1], texel_to_v * mStartV[c1]);
- Float2 uv_end(texel_to_u * (mStartU[c1] + mWidth[c1]), texel_to_v * (mStartV[c1] + mCharHeight));
- Float2 xy_end(x + float(mWidth[c1]) / mCharHeight, y + 1.0f);
- // Create vertices
- (inTransform * Vec3(x, y, 0)).StoreFloat3(&font_vtx->mPosition);
- font_vtx->mColor = inColor;
- font_vtx->mTexCoord = Float2(uv_start.x, uv_end.y);
- ++font_vtx;
- (inTransform * Vec3(x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
- font_vtx->mColor = inColor;
- font_vtx->mTexCoord = uv_start;
- ++font_vtx;
- (inTransform * Vec3(xy_end.x, y, 0)).StoreFloat3(&font_vtx->mPosition);
- font_vtx->mColor = inColor;
- font_vtx->mTexCoord = uv_end;
- ++font_vtx;
- (inTransform * Vec3(xy_end.x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
- font_vtx->mColor = inColor;
- font_vtx->mTexCoord = Float2(uv_end.x, uv_start.y);
- ++font_vtx;
- }
- // Go to next (x, y) location
- if (ch == '\n')
- {
- // Next line
- x = 0.0f;
- y -= 1.0f;
- }
- else if (i + 1 < inText.size())
- {
- // Do spacing between the two characters
- int c1 = ch - cBeginChar;
- int c2 = inText[i + 1] - cBeginChar;
- if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
- x += float(mSpacing[c1][c2]) / mCharHeight;
- }
- }
- // Check that we completely filled the output buffer
- JPH_ASSERT(vtx == (uint32)vtx_size);
- JPH_ASSERT(idx == idx_start + idx_size);
- // Unlock buffers
- ioPrimitive.UnlockVertexBuffer();
- ioPrimitive.UnlockIndexBuffer();
- return true;
- }
- void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg inColor) const
- {
- JPH_PROFILE("DrawText3D");
- // Early out
- if (inText.empty())
- return;
- RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
- if (CreateString(inTransform, inText, inColor, primitive))
- {
- mTexture->Bind(2);
- mPipelineState->Activate();
- primitive.Draw();
- }
- }
|