Font.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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 <Utils/ReadData.h>
  9. #include <Jolt/Core/Profiler.h>
  10. #include <Jolt/Core/ScopeExit.h>
  11. JPH_SUPPRESS_WARNINGS_STD_BEGIN
  12. JPH_CLANG_SUPPRESS_WARNING("-Wreserved-identifier")
  13. JPH_CLANG_SUPPRESS_WARNING("-Wzero-as-null-pointer-constant")
  14. JPH_CLANG_SUPPRESS_WARNING("-Wcast-qual")
  15. JPH_CLANG_SUPPRESS_WARNING("-Wimplicit-fallthrough")
  16. JPH_CLANG_SUPPRESS_WARNING("-Wcomma")
  17. JPH_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
  18. #define STB_TRUETYPE_IMPLEMENTATION
  19. #include <External/stb_truetype.h>
  20. JPH_SUPPRESS_WARNINGS_STD_END
  21. Font::Font(Renderer *inRenderer) :
  22. mRenderer(inRenderer)
  23. {
  24. }
  25. bool Font::Create(const char *inFontName, int inCharHeight)
  26. {
  27. JPH_PROFILE("Create");
  28. // Initialize
  29. mFontName = inFontName;
  30. mCharHeight = inCharHeight;
  31. mHorizontalTexels = 64;
  32. mVerticalTexels = 64;
  33. constexpr int cSpacingH = 2; // Number of pixels to put horizontally between characters
  34. constexpr int cSpacingV = 2; // Number of pixels to put vertically between characters
  35. // Read font data
  36. Array<uint8> font_data = ReadData((String("Assets/Fonts/") + inFontName + ".ttf").c_str());
  37. // Construct a font info
  38. stbtt_fontinfo font;
  39. if (!stbtt_InitFont(&font, font_data.data(), stbtt_GetFontOffsetForIndex(font_data.data(), 0)))
  40. return false;
  41. // Get the base line for the font
  42. float scale = stbtt_ScaleForPixelHeight(&font, float(mCharHeight));
  43. int ascent;
  44. stbtt_GetFontVMetrics(&font, &ascent, nullptr, nullptr);
  45. int baseline = int(ascent * scale);
  46. // Create surface for characters
  47. Ref<SoftwareSurface> surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
  48. surface->Clear();
  49. surface->Lock(ESurfaceLockMode::Write);
  50. // Draw all printable characters
  51. try_again:;
  52. int x = 0, y = 0;
  53. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  54. for (int c = cBeginChar + 1; c < cEndChar; ++c)
  55. {
  56. // Get index in the arrays
  57. int idx = c - cBeginChar;
  58. int w, h, xoff, yoff;
  59. unsigned char *bitmap = stbtt_GetCodepointBitmap(&font, 0, scale, c, &w, &h, &xoff, &yoff);
  60. JPH_SCOPE_EXIT([bitmap]{ STBTT_free(bitmap, nullptr); });
  61. yoff = baseline + yoff;
  62. // Check if there is room on this line
  63. if (int(x + xoff + w + cSpacingH) > mHorizontalTexels)
  64. {
  65. // Next line
  66. x = 0;
  67. y += mCharHeight + cSpacingV;
  68. // Check if character fits
  69. if (y + mCharHeight + cSpacingV > mVerticalTexels)
  70. {
  71. // Character doesn't fit, enlarge surface
  72. if (mHorizontalTexels < 2 * mVerticalTexels)
  73. mHorizontalTexels <<= 1;
  74. else
  75. mVerticalTexels <<= 1;
  76. // Create new surface
  77. surface->UnLock();
  78. surface = new SoftwareSurface(mHorizontalTexels, mVerticalTexels, ESurfaceFormat::L8);
  79. surface->Clear();
  80. surface->Lock(ESurfaceLockMode::Write);
  81. // Try again with the larger texture
  82. goto try_again;
  83. }
  84. }
  85. // Get location of character in font surface
  86. JPH_ASSERT(x >= 0 && x <= 0xffff);
  87. JPH_ASSERT(y >= 0 && y <= 0xffff);
  88. JPH_ASSERT(w <= 0xff);
  89. mStartU[idx] = uint16(x);
  90. mStartV[idx] = uint16(y);
  91. mWidth[idx] = uint8(w + 1);
  92. // Copy the character data
  93. for (int y2 = 0; y2 < h; ++y2)
  94. {
  95. uint8 *src = bitmap + y2 * w;
  96. uint8 *dst = surface->GetScanLine(y + yoff + y2) + x + xoff;
  97. memcpy(dst, src, w);
  98. }
  99. // Go to the next character
  100. x += w + cSpacingH;
  101. }
  102. // Calculate spacing between characters
  103. for (int idx1 = 0; idx1 < cNumChars; ++idx1)
  104. for (int idx2 = 0; idx2 < cNumChars; ++idx2)
  105. {
  106. int c1 = cBeginChar + idx1;
  107. int c2 = cBeginChar + idx2;
  108. int advance;
  109. stbtt_GetCodepointHMetrics(&font, c1, &advance, nullptr);
  110. int spacing = Clamp(int(scale * (advance + stbtt_GetCodepointKernAdvance(&font, c1, c2))), 0, 0xff);
  111. mSpacing[idx1][idx2] = (uint8)spacing;
  112. }
  113. // Unlock surface
  114. surface->UnLock();
  115. // Create input layout
  116. const PipelineState::EInputDescription vertex_desc[] =
  117. {
  118. PipelineState::EInputDescription::Position,
  119. PipelineState::EInputDescription::TexCoord,
  120. PipelineState::EInputDescription::Color
  121. };
  122. // Load vertex shader
  123. Ref<VertexShader> vtx = mRenderer->CreateVertexShader("Assets/Shaders/FontVertexShader");
  124. // Load pixel shader
  125. Ref<PixelShader> pix = mRenderer->CreatePixelShader("Assets/Shaders/FontPixelShader");
  126. mPipelineState = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
  127. // Create texture
  128. mTexture = mRenderer->CreateTexture(surface);
  129. // Trace success
  130. Trace("Created font \"%s\" with height %d in a %dx%d surface", mFontName.c_str(), mCharHeight, mHorizontalTexels, mVerticalTexels);
  131. return true;
  132. }
  133. Float2 Font::MeasureText(const string_view &inText) const
  134. {
  135. JPH_PROFILE("MeasureText");
  136. Float2 extents(0, 1.0f);
  137. // Current raster position
  138. float x = 0;
  139. // Loop through string
  140. for (uint i = 0; i < inText.size(); ++i)
  141. {
  142. // Get character
  143. int ch = inText[i];
  144. // Create character if it is printable
  145. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  146. if (ch > cBeginChar && ch < cEndChar)
  147. {
  148. // Update extents
  149. int c1 = ch - cBeginChar;
  150. extents.x = max(extents.x, x + float(mWidth[c1]) / mCharHeight);
  151. }
  152. // Go to next (x, y) location
  153. if (ch == '\n')
  154. {
  155. // Next line
  156. x = 0;
  157. extents.y += 1.0f;
  158. }
  159. else if (i + 1 < inText.size())
  160. {
  161. // Do spacing between the two characters
  162. int c1 = ch - cBeginChar;
  163. int c2 = inText[i + 1] - cBeginChar;
  164. if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
  165. x += float(mSpacing[c1][c2]) / mCharHeight;
  166. }
  167. }
  168. return extents;
  169. }
  170. bool Font::CreateString(Mat44Arg inTransform, const string_view &inText, ColorArg inColor, RenderPrimitive &ioPrimitive) const
  171. {
  172. JPH_PROFILE("CreateString");
  173. // Reset primitive
  174. ioPrimitive.Clear();
  175. // Count the number of printable chars
  176. int printable = 0;
  177. for (uint i = 0; i < inText.size(); ++i)
  178. {
  179. int ch = inText[i];
  180. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  181. if (ch > cBeginChar && ch < cEndChar) // Space is not printable
  182. printable++;
  183. }
  184. if (printable == 0)
  185. return false;
  186. // Get correction factor for texture size
  187. float texel_to_u = 1.0f / mHorizontalTexels;
  188. float texel_to_v = 1.0f / mVerticalTexels;
  189. int vtx_size = printable * 4;
  190. int idx_size = printable * 6;
  191. ioPrimitive.CreateVertexBuffer(vtx_size, sizeof(FontVertex));
  192. ioPrimitive.CreateIndexBuffer(idx_size);
  193. // Current vertex
  194. uint32 vtx = 0;
  195. // Lock buffers
  196. FontVertex *font_vtx = (FontVertex *)ioPrimitive.LockVertexBuffer();
  197. uint32 *idx_start = ioPrimitive.LockIndexBuffer();
  198. uint32 *idx = idx_start;
  199. // Current raster position
  200. float x = 0, y = -1.0f;
  201. // Loop through string
  202. for (uint i = 0; i < inText.size(); ++i)
  203. {
  204. // Get character
  205. int ch = inText[i];
  206. // Create character if it is printable
  207. static_assert(cBeginChar == ' ', "We skip space in the for loop below");
  208. if (ch > cBeginChar && ch < cEndChar)
  209. {
  210. // Get index for character
  211. int c1 = ch - cBeginChar;
  212. // Create indices
  213. *idx = vtx;
  214. ++idx;
  215. *idx = vtx + 3;
  216. ++idx;
  217. *idx = vtx + 1;
  218. ++idx;
  219. *idx = vtx;
  220. ++idx;
  221. *idx = vtx + 2;
  222. ++idx;
  223. *idx = vtx + 3;
  224. ++idx;
  225. vtx += 4;
  226. // Get properties of this character
  227. Float2 uv_start(texel_to_u * mStartU[c1], texel_to_v * mStartV[c1]);
  228. Float2 uv_end(texel_to_u * (mStartU[c1] + mWidth[c1]), texel_to_v * (mStartV[c1] + mCharHeight));
  229. Float2 xy_end(x + float(mWidth[c1]) / mCharHeight, y + 1.0f);
  230. // Create vertices
  231. (inTransform * Vec3(x, y, 0)).StoreFloat3(&font_vtx->mPosition);
  232. font_vtx->mColor = inColor;
  233. font_vtx->mTexCoord = Float2(uv_start.x, uv_end.y);
  234. ++font_vtx;
  235. (inTransform * Vec3(x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
  236. font_vtx->mColor = inColor;
  237. font_vtx->mTexCoord = uv_start;
  238. ++font_vtx;
  239. (inTransform * Vec3(xy_end.x, y, 0)).StoreFloat3(&font_vtx->mPosition);
  240. font_vtx->mColor = inColor;
  241. font_vtx->mTexCoord = uv_end;
  242. ++font_vtx;
  243. (inTransform * Vec3(xy_end.x, xy_end.y, 0)).StoreFloat3(&font_vtx->mPosition);
  244. font_vtx->mColor = inColor;
  245. font_vtx->mTexCoord = Float2(uv_end.x, uv_start.y);
  246. ++font_vtx;
  247. }
  248. // Go to next (x, y) location
  249. if (ch == '\n')
  250. {
  251. // Next line
  252. x = 0.0f;
  253. y -= 1.0f;
  254. }
  255. else if (i + 1 < inText.size())
  256. {
  257. // Do spacing between the two characters
  258. int c1 = ch - cBeginChar;
  259. int c2 = inText[i + 1] - cBeginChar;
  260. if (c1 >= 0 && c1 < cNumChars && c2 >= 0 && c2 < cNumChars)
  261. x += float(mSpacing[c1][c2]) / mCharHeight;
  262. }
  263. }
  264. // Check that we completely filled the output buffer
  265. JPH_ASSERT(vtx == (uint32)vtx_size);
  266. JPH_ASSERT(idx == idx_start + idx_size);
  267. // Unlock buffers
  268. ioPrimitive.UnlockVertexBuffer();
  269. ioPrimitive.UnlockIndexBuffer();
  270. return true;
  271. }
  272. void Font::DrawText3D(Mat44Arg inTransform, const string_view &inText, ColorArg inColor) const
  273. {
  274. JPH_PROFILE("DrawText3D");
  275. // Early out
  276. if (inText.empty())
  277. return;
  278. Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
  279. if (CreateString(inTransform, inText, inColor, *primitive))
  280. {
  281. mTexture->Bind();
  282. mPipelineState->Activate();
  283. primitive->Draw();
  284. }
  285. }