2
0

Font.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <TestFramework.h>
  4. #include <Renderer/Font.h>
  5. #include <Renderer/Renderer.h>
  6. #include <Image/Surface.h>
  7. #include <Jolt/Core/Profiler.h>
  8. const int cSpacingH = 2; // Number of pixels to put horizontally between characters
  9. const int cSpacingV = 2; // Number of pixels to put vertically between characters
  10. Font::Font(Renderer *inRenderer) :
  11. mRenderer(inRenderer)
  12. {
  13. }
  14. bool
  15. Font::Create(const char *inFontName, int inCharHeight)
  16. {
  17. JPH_PROFILE("Create");
  18. // Initialize
  19. mFontName = inFontName;
  20. mCharHeight = inCharHeight;
  21. mHorizontalTexels = 64;
  22. mVerticalTexels = 64;
  23. // Check font name length
  24. if (mFontName.size() >= LF_FACESIZE)
  25. return false;
  26. // Create font
  27. LOGFONTA font_desc;
  28. memset(&font_desc, 0, sizeof(font_desc));
  29. font_desc.lfHeight = mCharHeight;
  30. font_desc.lfWeight = FW_NORMAL;
  31. font_desc.lfCharSet = DEFAULT_CHARSET;
  32. font_desc.lfOutPrecision = OUT_DEFAULT_PRECIS;
  33. font_desc.lfClipPrecision = CLIP_DEFAULT_PRECIS;
  34. font_desc.lfQuality = ANTIALIASED_QUALITY;
  35. font_desc.lfPitchAndFamily = VARIABLE_PITCH;
  36. strcpy_s(font_desc.lfFaceName, mFontName.c_str());
  37. HFONT font = CreateFontIndirectA(&font_desc);
  38. if (font == nullptr)
  39. return false;
  40. // Create a DC for the font
  41. HDC dc = CreateCompatibleDC(nullptr);
  42. if (dc == nullptr)
  43. {
  44. DeleteObject(font);
  45. return false;
  46. }
  47. // Select the font
  48. SelectObject(dc, font);
  49. SetMapMode(dc, MM_TEXT);
  50. // Get text metrics
  51. TEXTMETRICA textmetric;
  52. if (!GetTextMetricsA(dc, &textmetric))
  53. {
  54. DeleteObject(font);
  55. DeleteDC(dc);
  56. return false;
  57. }
  58. // Compute spacing
  59. ABC widths[256];
  60. if (!GetCharABCWidthsA(dc, 0, 255, widths))
  61. {
  62. DeleteObject(font);
  63. DeleteDC(dc);
  64. return false;
  65. }
  66. for (int idx1 = 0; idx1 < cNumChars; ++idx1)
  67. for (int idx2 = 0; idx2 < cNumChars; ++idx2)
  68. {
  69. int spacing = int(widths[idx1 + cBeginChar].abcB) + widths[idx1 + cBeginChar].abcC + widths[idx2 + cBeginChar].abcA;
  70. JPH_ASSERT(spacing >= 0 && spacing <= 0xff);
  71. mSpacing[idx1][idx2] = (uint8)spacing;
  72. }
  73. // Adjust spacing for kerning pairs
  74. DWORD pair_count = GetKerningPairsA(dc, 0, nullptr);
  75. if (pair_count > 0)
  76. {
  77. LPKERNINGPAIR pairs = new KERNINGPAIR [pair_count];
  78. GetKerningPairsA(dc, pair_count, pairs);
  79. for (DWORD i = 0; i < pair_count; ++i)
  80. if (pairs[i].wFirst >= cBeginChar && pairs[i].wFirst < cEndChar && pairs[i].wSecond >= cBeginChar && pairs[i].wSecond < cEndChar)
  81. {
  82. int idx1 = pairs[i].wFirst - cBeginChar;
  83. int idx2 = pairs[i].wSecond - cBeginChar;
  84. int new_spacing = (int)mSpacing[idx1][idx2] + pairs[i].iKernAmount;
  85. JPH_ASSERT(new_spacing >= 0 && new_spacing <= 0xff);
  86. mSpacing[idx1][idx2] = (uint8)new_spacing;
  87. }
  88. delete [] pairs;
  89. }
  90. // Create surface for characters
  91. Ref<SoftwareSurface> surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
  92. surface->Clear();
  93. surface->Lock(ESurfaceLockMode::Write);
  94. // Identity transform
  95. MAT2 identity;
  96. memset(&identity, 0, sizeof(identity));
  97. identity.eM11.value = 1;
  98. identity.eM22.value = 1;
  99. // Draw all printable characters
  100. try_again:;
  101. int x = 0, y = 0;
  102. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  103. for (int c = cBeginChar + 1; c < cEndChar; ++c)
  104. {
  105. // Get index in the arrays
  106. int idx = c - cBeginChar;
  107. // Check if there is room on this line
  108. if (int(x + widths[c].abcB + cSpacingH) > mHorizontalTexels)
  109. {
  110. // Next line
  111. x = 0;
  112. y += textmetric.tmHeight + cSpacingV;
  113. // Check if character fits
  114. if (y + textmetric.tmHeight + cSpacingV > mVerticalTexels)
  115. {
  116. // Character doesn't fit, enlarge surface
  117. if (mHorizontalTexels < 2 * mVerticalTexels)
  118. mHorizontalTexels <<= 1;
  119. else
  120. mVerticalTexels <<= 1;
  121. // Create new surface
  122. surface->UnLock();
  123. surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
  124. surface->Clear();
  125. surface->Lock(ESurfaceLockMode::Write);
  126. // Try again with the larger texture
  127. goto try_again;
  128. }
  129. }
  130. // Get location of character in font surface
  131. JPH_ASSERT(x >= 0 && x <= 0xffff);
  132. JPH_ASSERT(y >= 0 && y <= 0xffff);
  133. JPH_ASSERT(widths[c].abcB <= 0xff);
  134. mStartU[idx] = uint16(x);
  135. mStartV[idx] = uint16(y);
  136. mWidth[idx] = uint8(widths[c].abcB);
  137. // Get character data size
  138. GLYPHMETRICS metrics;
  139. int char_size = GetGlyphOutlineA(dc, c, GGO_GRAY8_BITMAP, &metrics, 0, nullptr, &identity);
  140. if (char_size != 0)
  141. {
  142. // Allocate room for character
  143. uint8 *char_data = new uint8 [char_size];
  144. // Get character
  145. GetGlyphOutlineA(dc, c, GGO_GRAY8_BITMAP, &metrics, char_size, char_data, &identity);
  146. uint src_pitch = (metrics.gmBlackBoxX + 3) & ~uint(3);
  147. // Copy the character data
  148. for (uint src_y = 0, dst_y = y; src_y < metrics.gmBlackBoxY; ++src_y, ++dst_y)
  149. {
  150. uint8 *src = char_data + src_y * src_pitch;
  151. uint8 *dst = surface->GetScanLine(dst_y + int(textmetric.tmHeight - textmetric.tmDescent - metrics.gmptGlyphOrigin.y)) + x;
  152. for (uint src_x = 0; src_x < metrics.gmBlackBoxX; ++src_x, ++src, ++dst)
  153. *dst = uint8(min(int(*src) << 2, 255));
  154. }
  155. // Destroy temporary character data
  156. delete [] char_data;
  157. }
  158. // Go to the next character
  159. x += widths[c].abcB + cSpacingH;
  160. }
  161. // Unlock surface
  162. surface->UnLock();
  163. // Release GDI objects
  164. DeleteObject(font);
  165. DeleteDC(dc);
  166. // Create input layout
  167. const D3D12_INPUT_ELEMENT_DESC vertex_desc[] =
  168. {
  169. { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
  170. { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
  171. { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
  172. };
  173. // Load vertex shader
  174. ComPtr<ID3DBlob> vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader.hlsl");
  175. // Load pixel shader
  176. ComPtr<ID3DBlob> pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader.hlsl");
  177. 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);
  178. // Create texture
  179. mTexture = mRenderer->CreateTexture(surface);
  180. // Trace success
  181. Trace("Created font \"%s\" with height %d in a %dx%d surface", mFontName.c_str(), mCharHeight, mHorizontalTexels, mVerticalTexels);
  182. return true;
  183. }
  184. Float2 Font::MeasureText(const string_view &inText) const
  185. {
  186. JPH_PROFILE("MeasureText");
  187. Float2 extents(0, 1.0f);
  188. // Current raster position
  189. float x = 0;
  190. // Loop through string
  191. for (uint i = 0; i < inText.size(); ++i)
  192. {
  193. // Get character
  194. int ch = inText[i];
  195. // Create character if it is printable
  196. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  197. if (ch > cBeginChar && ch < cEndChar)
  198. {
  199. // Update extents
  200. int c1 = ch - cBeginChar;
  201. extents.x = max(extents.x, x + float(mWidth[c1]) / mCharHeight);
  202. }
  203. // Go to next (x, y) location
  204. if (ch == '\n')
  205. {
  206. // Next line
  207. x = 0;
  208. extents.y += 1.0f;
  209. }
  210. else if (i + 1 < inText.size())
  211. {
  212. // Do spacing between the two characters
  213. int c1 = ch - cBeginChar;
  214. int c2 = inText[i + 1] - cBeginChar;
  215. if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
  216. x += float(mSpacing[c1][c2]) / mCharHeight;
  217. }
  218. }
  219. return extents;
  220. }
  221. bool Font::CreateString(Mat44Arg inTransform, const string_view &inText, ColorArg inColor, RenderPrimitive &ioPrimitive) const
  222. {
  223. JPH_PROFILE("CreateString");
  224. // Reset primitive
  225. ioPrimitive.Clear();
  226. // Count the number of printable chars
  227. int printable = 0;
  228. for (uint i = 0; i < inText.size(); ++i)
  229. {
  230. int ch = inText[i];
  231. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  232. if (ch > cBeginChar && ch < cEndChar) // Space is not printable
  233. printable++;
  234. }
  235. if (printable == 0)
  236. return false;
  237. // Get correction factor for texture size
  238. float texel_to_u = 1.0f / mHorizontalTexels;
  239. float texel_to_v = 1.0f / mVerticalTexels;
  240. int vtx_size = printable * 4;
  241. int idx_size = printable * 6;
  242. ioPrimitive.CreateVertexBuffer(vtx_size, sizeof(FontVertex));
  243. ioPrimitive.CreateIndexBuffer(idx_size);
  244. // Current vertex
  245. uint32 vtx = 0;
  246. // Lock buffers
  247. FontVertex *font_vtx = (FontVertex *)ioPrimitive.LockVertexBuffer();
  248. uint32 *idx_start = ioPrimitive.LockIndexBuffer();
  249. uint32 *idx = idx_start;
  250. // Current raster position
  251. float x = 0, y = -1.0f;
  252. // Loop through string
  253. for (uint i = 0; i < inText.size(); ++i)
  254. {
  255. // Get character
  256. int ch = inText[i];
  257. // Create character if it is printable
  258. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  259. if (ch > cBeginChar && ch < cEndChar)
  260. {
  261. // Get index for character
  262. int c1 = ch - cBeginChar;
  263. // Create indices
  264. *idx = vtx;
  265. ++idx;
  266. *idx = vtx + 3;
  267. ++idx;
  268. *idx = vtx + 1;
  269. ++idx;
  270. *idx = vtx;
  271. ++idx;
  272. *idx = vtx + 2;
  273. ++idx;
  274. *idx = vtx + 3;
  275. ++idx;
  276. vtx += 4;
  277. // Get properties of this character
  278. Float2 uv_start(texel_to_u * mStartU[c1], texel_to_v * mStartV[c1]);
  279. Float2 uv_end(texel_to_u * (mStartU[c1] + mWidth[c1]), texel_to_v * (mStartV[c1] + mCharHeight));
  280. Float2 xy_end(x + float(mWidth[c1]) / mCharHeight, y + 1.0f);
  281. // Create vertices
  282. (inTransform * Vec3(x, y, 0)).StoreFloat3(&font_vtx->mPosition);
  283. font_vtx->mColor = inColor;
  284. font_vtx->mTexCoord = Float2(uv_start.x, uv_end.y);
  285. ++font_vtx;
  286. (inTransform * Vec3(x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
  287. font_vtx->mColor = inColor;
  288. font_vtx->mTexCoord = uv_start;
  289. ++font_vtx;
  290. (inTransform * Vec3(xy_end.x, y, 0)).StoreFloat3(&font_vtx->mPosition);
  291. font_vtx->mColor = inColor;
  292. font_vtx->mTexCoord = uv_end;
  293. ++font_vtx;
  294. (inTransform * Vec3(xy_end.x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
  295. font_vtx->mColor = inColor;
  296. font_vtx->mTexCoord = Float2(uv_end.x, uv_start.y);
  297. ++font_vtx;
  298. }
  299. // Go to next (x, y) location
  300. if (ch == '\n')
  301. {
  302. // Next line
  303. x = 0.0f;
  304. y -= 1.0f;
  305. }
  306. else if (i + 1 < inText.size())
  307. {
  308. // Do spacing between the two characters
  309. int c1 = ch - cBeginChar;
  310. int c2 = inText[i + 1] - cBeginChar;
  311. if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
  312. x += float(mSpacing[c1][c2]) / mCharHeight;
  313. }
  314. }
  315. // Check that we completely filled the output buffer
  316. JPH_ASSERT(vtx == (uint32)vtx_size);
  317. JPH_ASSERT(idx == idx_start + idx_size);
  318. // Unlock buffers
  319. ioPrimitive.UnlockVertexBuffer();
  320. ioPrimitive.UnlockIndexBuffer();
  321. return true;
  322. }
  323. void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg inColor) const
  324. {
  325. JPH_PROFILE("DrawText3D");
  326. // Early out
  327. if (inText.empty())
  328. return;
  329. RenderPrimitive primitive(mRenderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
  330. if (CreateString(inTransform, inText, inColor, primitive))
  331. {
  332. mTexture->Bind(2);
  333. mPipelineState->Activate();
  334. primitive.Draw();
  335. }
  336. }