2
0

Font.cpp 11 KB

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