FFont.cpp 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. // Description : Font class.
  9. #if !defined(USE_NULLFONT_ALWAYS)
  10. #include <AzCore/Math/Color.h>
  11. #include <AzCore/Math/Matrix4x4.h>
  12. #include <AzCore/Math/MatrixUtils.h>
  13. #include <AzCore/Casting/numeric_cast.h>
  14. #include <AzFramework/Viewport/ViewportScreen.h>
  15. #include <AzFramework/Viewport/ScreenGeometry.h>
  16. #include <AzFramework/Archive/Archive.h>
  17. #include <AtomLyIntegration/AtomFont/FFont.h>
  18. #include <AtomLyIntegration/AtomFont/AtomFont.h>
  19. #include <AtomLyIntegration/AtomFont/FontTexture.h>
  20. #include <CryCommon/MathConversion.h>
  21. #include <AzCore/std/parallel/lock.h>
  22. #include <Atom/RPI.Public/RPISystemInterface.h>
  23. #include <Atom/RHI/RHISystemInterface.h>
  24. #include <Atom/RPI.Public/Shader/Shader.h>
  25. #include <Atom/RPI.Public/RPIUtils.h>
  26. #include <Atom/RPI.Public/ViewportContext.h>
  27. #include <Atom/RPI.Public/View.h>
  28. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  29. #include <Atom/RPI.Public/Image/AttachmentImagePool.h>
  30. #include <Atom/RPI.Public/ViewportContextManager.h>
  31. #include <AzCore/Interface/Interface.h>
  32. #include <Atom/RHI/Factory.h>
  33. #include <Atom/RHI/DrawPacket.h>
  34. #include <Atom/RHI/ImagePool.h>
  35. #include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
  36. static const int TabCharCount = 4;
  37. // set buffer sizes to hold max characters that can be drawn in 1 DrawString call
  38. static const size_t MaxVerts = 8 * 1024; // 2048 quads
  39. static const size_t MaxIndices = (MaxVerts * 6) / 4; // 6 indices per quad, 6/4 * MaxVerts
  40. AZ::FFont::FFont(AZ::AtomFont* atomFont, const char* fontName)
  41. : m_name(fontName)
  42. , m_atomFont(atomFont)
  43. {
  44. assert(m_name.c_str());
  45. assert(m_atomFont);
  46. // create default effect
  47. FontEffect* effect = AddEffect("default");
  48. effect->AddPass();
  49. // Create cpu memory to cache the font draw data before submit
  50. m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts];
  51. m_indexBuffer = new u16[MaxIndices];
  52. m_vertexCount = 0;
  53. m_indexCount = 0;
  54. AddRef();
  55. }
  56. AZ::RPI::ViewportContextPtr AZ::FFont::GetDefaultViewportContext() const
  57. {
  58. auto viewContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
  59. return viewContextManager->GetDefaultViewportContext();
  60. }
  61. AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const
  62. {
  63. if (auto defaultViewportContext = GetDefaultViewportContext())
  64. {
  65. return defaultViewportContext->GetWindowContext();
  66. }
  67. return {};
  68. }
  69. AZ::FFont::~FFont()
  70. {
  71. AZ_Assert(m_atomFont == nullptr, "The font should already be unregistered through a call to AZ::FFont::Release()");
  72. delete[] m_vertexBuffer;
  73. delete[] m_indexBuffer;
  74. Free();
  75. }
  76. int32_t AZ::FFont::AddRef()
  77. {
  78. ref_count::add_ref();
  79. return aznumeric_cast<int32_t>(ref_count::use_count());
  80. }
  81. int32_t AZ::FFont::Release()
  82. {
  83. int32_t useCount = aznumeric_cast<int32_t>(ref_count::use_count()) - 1;
  84. ref_count::release();
  85. return useCount;
  86. }
  87. // Load a font from a TTF file
  88. bool AZ::FFont::Load(const char* fontFilePath, unsigned int width, unsigned int height, unsigned int widthNumSlots, unsigned int heightNumSlots, unsigned int flags, float sizeRatio)
  89. {
  90. if (!fontFilePath)
  91. {
  92. return false;
  93. }
  94. Free();
  95. auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
  96. AZ::IO::Path fullFile(m_curPath);
  97. fullFile /= fontFilePath;
  98. int smoothMethodFlag = (flags & TTFFLAG_SMOOTH_MASK) >> TTFFLAG_SMOOTH_SHIFT;
  99. AZ::FontSmoothMethod smoothMethod = AZ::FontSmoothMethod::None;
  100. switch (smoothMethodFlag)
  101. {
  102. case TTFFLAG_SMOOTH_BLUR:
  103. smoothMethod = AZ::FontSmoothMethod::Blur;
  104. break;
  105. case TTFFLAG_SMOOTH_SUPERSAMPLE:
  106. smoothMethod = AZ::FontSmoothMethod::SuperSample;
  107. break;
  108. }
  109. int smoothAmountFlag = (flags & TTFFLAG_SMOOTH_AMOUNT_MASK);
  110. AZ::FontSmoothAmount smoothAmount = AZ::FontSmoothAmount::None;
  111. switch (smoothAmountFlag)
  112. {
  113. case TTFLAG_SMOOTH_AMOUNT_2X:
  114. smoothAmount = AZ::FontSmoothAmount::x2;
  115. break;
  116. case TTFLAG_SMOOTH_AMOUNT_4X:
  117. smoothAmount = AZ::FontSmoothAmount::x4;
  118. break;
  119. }
  120. AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
  121. fileIoBase->Open(fullFile.c_str(), AZ::IO::GetOpenModeFromStringMode("rb"), fileHandle);
  122. if (fileHandle == AZ::IO::InvalidHandle)
  123. {
  124. return false;
  125. }
  126. AZ::u64 fileSize{};
  127. fileIoBase->Size(fileHandle, fileSize);
  128. if (!fileSize)
  129. {
  130. fileIoBase->Close(fileHandle);
  131. return false;
  132. }
  133. auto buffer = AZStd::make_unique<uint8_t[]>(fileSize);
  134. if (!fileIoBase->Read(fileHandle, buffer.get(), fileSize))
  135. {
  136. fileIoBase->Close(fileHandle);
  137. return false;
  138. }
  139. fileIoBase->Close(fileHandle);
  140. if (!m_fontTexture)
  141. {
  142. m_fontTexture = new FontTexture();
  143. }
  144. if (!m_fontTexture || !m_fontTexture->CreateFromMemory(buffer.get(), (int)fileSize, width, height, smoothMethod, smoothAmount, widthNumSlots, heightNumSlots, sizeRatio))
  145. {
  146. return false;
  147. }
  148. m_monospacedFont = m_fontTexture->GetMonospaced();
  149. m_fontBuffer = AZStd::move(buffer);
  150. m_fontBufferSize = fileSize;
  151. m_fontTexDirty = false;
  152. m_sizeRatio = sizeRatio;
  153. InitCache();
  154. return true;
  155. }
  156. void AZ::FFont::Free()
  157. {
  158. m_fontImage = nullptr;
  159. m_fontImageVersion = 0;
  160. delete m_fontTexture;
  161. m_fontTexture = nullptr;
  162. m_fontBuffer.reset();
  163. m_fontBufferSize = 0;
  164. }
  165. void AZ::FFont::DrawString(float x, float y, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
  166. {
  167. if (!str)
  168. {
  169. return;
  170. }
  171. DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, 1.0f, str, asciiMultiLine, ctx);
  172. }
  173. void AZ::FFont::DrawString(float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
  174. {
  175. if (!str)
  176. {
  177. return;
  178. }
  179. DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, z, str, asciiMultiLine, ctx);
  180. }
  181. void AZ::FFont::DrawStringUInternal(
  182. const RHI::Viewport& viewport,
  183. RPI::ViewportContextPtr viewportContext,
  184. float x,
  185. float y,
  186. float z,
  187. const char* str,
  188. const bool asciiMultiLine,
  189. const TextDrawContext& ctx)
  190. {
  191. // Lazily ensure we're initialized before attempting to render.
  192. // Validate that there is a render scene before attempting to init.
  193. if (!viewportContext || !viewportContext->GetRenderScene())
  194. {
  195. return;
  196. }
  197. if (!str
  198. || !m_vertexBuffer // vertex buffer isn't created until BootstrapScene is ready, Editor tries to render text before that.
  199. || !m_fontTexture
  200. || ctx.m_fxIdx >= m_effects.size()
  201. || m_effects[ctx.m_fxIdx].m_passes.empty())
  202. {
  203. return;
  204. }
  205. const size_t fxSize = m_effects.size();
  206. if (fxSize && !m_fontImage && !InitTexture())
  207. {
  208. return;
  209. }
  210. const bool orthoMode = ctx.m_overrideViewProjMatrices;
  211. const float viewX = viewport.m_minX;
  212. const float viewY = viewport.m_minY;
  213. const float viewWidth = viewport.m_maxX - viewport.m_minX;
  214. const float viewHeight = viewport.m_maxY - viewport.m_minY;
  215. const float zf = viewport.m_minZ;
  216. const float zn = viewport.m_maxZ;
  217. Matrix4x4 modelViewProjMat;
  218. if (!orthoMode)
  219. {
  220. AZ::RPI::ViewPtr view = viewportContext->GetDefaultView();
  221. modelViewProjMat = view->GetWorldToClipMatrix();
  222. }
  223. else
  224. {
  225. if (viewWidth == 0 || viewHeight == 0)
  226. {
  227. return;
  228. }
  229. AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
  230. }
  231. size_t startingVertexCount = m_vertexCount;
  232. // Local function that is passed into CreateQuadsForText as the AddQuad function
  233. AZ::FFont::AddFunction AddQuad = [this, startingVertexCount]
  234. (const Vec3& v0, const Vec3& v1, const Vec3& v2, const Vec3& v3, const Vec2& tc0, const Vec2& tc1, const Vec2& tc2, const Vec2& tc3, uint32_t packedColor)
  235. {
  236. const bool vertexSpaceLeft = m_vertexCount + 4 < MaxVerts;
  237. const bool indexSpaceLeft = m_indexCount + 6 < MaxIndices;
  238. if (!vertexSpaceLeft || !indexSpaceLeft)
  239. {
  240. return false;
  241. }
  242. size_t vertexOffset = m_vertexCount;
  243. m_vertexCount += 4;
  244. size_t indexOffset = m_indexCount;
  245. m_indexCount += 6;
  246. // define char quad
  247. m_vertexBuffer[vertexOffset + 0].xyz = v0;
  248. m_vertexBuffer[vertexOffset + 0].color.dcolor = packedColor;
  249. m_vertexBuffer[vertexOffset + 0].st = tc0;
  250. m_vertexBuffer[vertexOffset + 1].xyz = v1;
  251. m_vertexBuffer[vertexOffset + 1].color.dcolor = packedColor;
  252. m_vertexBuffer[vertexOffset + 1].st = tc1;
  253. m_vertexBuffer[vertexOffset + 2].xyz = v2;
  254. m_vertexBuffer[vertexOffset + 2].color.dcolor = packedColor;
  255. m_vertexBuffer[vertexOffset + 2].st = tc2;
  256. m_vertexBuffer[vertexOffset + 3].xyz = v3;
  257. m_vertexBuffer[vertexOffset + 3].color.dcolor = packedColor;
  258. m_vertexBuffer[vertexOffset + 3].st = tc3;
  259. uint16_t startingIndex = static_cast<uint16_t>(vertexOffset - startingVertexCount);
  260. m_indexBuffer[indexOffset + 0] = startingIndex + 0;
  261. m_indexBuffer[indexOffset + 1] = startingIndex + 1;
  262. m_indexBuffer[indexOffset + 2] = startingIndex + 2;
  263. m_indexBuffer[indexOffset + 3] = startingIndex + 2;
  264. m_indexBuffer[indexOffset + 4] = startingIndex + 3;
  265. m_indexBuffer[indexOffset + 5] = startingIndex + 0;
  266. return true;
  267. };
  268. int numQuads = 0;
  269. {
  270. AZStd::lock_guard<AZStd::mutex> lock(m_vertexDataMutex);
  271. numQuads = CreateQuadsForText(viewport, x, y, z, str, asciiMultiLine, ctx, AddQuad);
  272. }
  273. if (numQuads)
  274. {
  275. AZ::RPI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::AtomBridge::PerViewportDynamicDraw::Get()->GetDynamicDrawContextForViewport(m_dynamicDrawContextName, viewportContext->GetId());
  276. if (dynamicDraw)
  277. {
  278. //setup per draw srg
  279. auto drawSrg = dynamicDraw->NewDrawSrg();
  280. drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat);
  281. drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontAttachmentImage->GetImageView());
  282. drawSrg->Compile();
  283. dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg);
  284. }
  285. m_indexCount = 0;
  286. m_vertexCount = 0;
  287. }
  288. }
  289. Vec2 AZ::FFont::GetTextSize(const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
  290. {
  291. if (!str)
  292. {
  293. return Vec2(0.0f, 0.0f);
  294. }
  295. return GetTextSizeUInternal(GetDefaultWindowContext()->GetViewport(), str, asciiMultiLine, ctx);
  296. }
  297. Vec2 AZ::FFont::GetTextSizeUInternal(
  298. const RHI::Viewport& viewport,
  299. const char* str,
  300. const bool asciiMultiLine,
  301. const TextDrawContext& ctx)
  302. {
  303. const size_t fxSize = m_effects.size();
  304. if (!str || !m_fontTexture || !fxSize)
  305. {
  306. return Vec2(0, 0);
  307. }
  308. Prepare(str, false, ctx.m_requestSize);
  309. // This is the "logical" size of the font (in pixels). The actual size of
  310. // the glyphs in the font texture may have additional scaling applied or
  311. // could have been re-rendered at a different size.
  312. Vec2 size = ctx.m_size;
  313. if (ctx.m_sizeIn800x600)
  314. {
  315. ScaleCoord(viewport, size.x, size.y);
  316. }
  317. // This scaling takes into account the logical size of the font relative
  318. // to any additional scaling applied (such as from "size ratio").
  319. const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
  320. float maxW = 0;
  321. float maxH = 0;
  322. const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
  323. const FontEffect& fx = m_effects[fxIdx];
  324. AZStd::wstring strW;
  325. AZStd::to_wstring(strW, str);
  326. for (size_t i = 0, numPasses = fx.m_passes.size(); i < numPasses; ++i)
  327. {
  328. const FontRenderingPass* pass = &fx.m_passes[numPasses - i - 1];
  329. // gather pass data
  330. Vec2 offset = pass->m_posOffset;
  331. float charX = offset.x;
  332. float charY = offset.y + size.y;
  333. if (charY > maxH)
  334. {
  335. maxH = charY;
  336. }
  337. // parse the string, ignoring control characters
  338. uint32_t nextCh = 0;
  339. const wchar_t* pChar = strW.c_str();
  340. while (uint32_t ch = *pChar)
  341. {
  342. ++pChar;
  343. nextCh = *pChar;
  344. switch (ch)
  345. {
  346. case '\\':
  347. {
  348. if (*pChar != 'n' || !asciiMultiLine)
  349. {
  350. break;
  351. }
  352. ++pChar;
  353. }
  354. case '\n':
  355. {
  356. if (charX > maxW)
  357. {
  358. maxW = charX;
  359. }
  360. charX = offset.x;
  361. charY += size.y * (1.f + ctx.GetLineSpacing());
  362. if (charY > maxH)
  363. {
  364. maxH = charY;
  365. }
  366. continue;
  367. }
  368. break;
  369. case '\r':
  370. {
  371. if (charX > maxW)
  372. {
  373. maxW = charX;
  374. }
  375. charX = offset.x;
  376. continue;
  377. }
  378. break;
  379. case '\t':
  380. {
  381. if (ctx.m_proportional)
  382. {
  383. charX += TabCharCount * size.x * AZ_FONT_SPACE_SIZE;
  384. }
  385. else
  386. {
  387. charX += TabCharCount * size.x * ctx.m_widthScale;
  388. }
  389. continue;
  390. }
  391. break;
  392. case '$':
  393. {
  394. if (ctx.m_processSpecialChars)
  395. {
  396. if (*pChar == '$')
  397. {
  398. ++pChar;
  399. }
  400. else if (isdigit(*pChar))
  401. {
  402. ++pChar;
  403. continue;
  404. }
  405. else if (*pChar == 'O' || *pChar == 'o')
  406. {
  407. ++pChar;
  408. continue;
  409. }
  410. }
  411. }
  412. break;
  413. default:
  414. break;
  415. }
  416. const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
  417. const AtomFont::GlyphSize requestSize = rerenderGlyphs ? ctx.m_requestSize : AtomFont::defaultGlyphSize;
  418. int horizontalAdvance = m_fontTexture->GetHorizontalAdvance(ch, requestSize);
  419. float advance;
  420. if (ctx.m_proportional)
  421. {
  422. advance = horizontalAdvance * scaleInfo.scale.x;
  423. }
  424. else
  425. {
  426. advance = size.x * ctx.m_widthScale;
  427. }
  428. // Adjust "advance" here for kerning purposes
  429. Vec2 kerningOffset(Vec2_Zero);
  430. if (ctx.m_kerningEnabled && nextCh)
  431. {
  432. kerningOffset = m_fontTexture->GetKerning(ch, nextCh) * scaleInfo.scale.x;
  433. }
  434. // Adjust char width with tracking only if there is a next character
  435. if (nextCh)
  436. {
  437. charX += ctx.m_tracking;
  438. }
  439. charX += advance + kerningOffset.x;
  440. }
  441. if (charX > maxW)
  442. {
  443. maxW = charX;
  444. }
  445. }
  446. return Vec2(maxW, maxH);
  447. }
  448. uint32_t AZ::FFont::GetNumQuadsForText(const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
  449. {
  450. uint32_t numQuads = 0;
  451. const size_t fxSize = m_effects.size();
  452. const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
  453. const FontEffect& fx = m_effects[fxIdx];
  454. AZStd::wstring strW;
  455. AZStd::to_wstring(strW, str);
  456. for (size_t j = 0, numPasses = fx.m_passes.size(); j < numPasses; ++j)
  457. {
  458. size_t i = numPasses - j - 1;
  459. bool drawFrame = ctx.m_framed && i == numPasses - 1;
  460. if (drawFrame)
  461. {
  462. ++numQuads;
  463. }
  464. const wchar_t* pChar = strW.c_str();
  465. while (uint32_t ch = *pChar)
  466. {
  467. ++pChar;
  468. switch (ch)
  469. {
  470. case '\\':
  471. {
  472. if (*pChar != 'n' || !asciiMultiLine)
  473. {
  474. break;
  475. }
  476. ++pChar;
  477. }
  478. case '\n':
  479. {
  480. continue;
  481. }
  482. break;
  483. case '\r':
  484. {
  485. continue;
  486. }
  487. break;
  488. case '\t':
  489. {
  490. continue;
  491. }
  492. break;
  493. case '$':
  494. {
  495. if (ctx.m_processSpecialChars)
  496. {
  497. if (*pChar == '$')
  498. {
  499. ++pChar;
  500. }
  501. else if (isdigit(*pChar))
  502. {
  503. ++pChar;
  504. continue;
  505. }
  506. else if (*pChar == 'O' || *pChar == 'o')
  507. {
  508. ++pChar;
  509. continue;
  510. }
  511. }
  512. }
  513. break;
  514. default:
  515. break;
  516. }
  517. ++numQuads;
  518. }
  519. }
  520. return numQuads;
  521. }
  522. uint32_t AZ::FFont::WriteTextQuadsToBuffers(SVF_P2F_C4B_T2F_F4B* verts, uint16_t* indices, uint32_t maxQuads, float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
  523. {
  524. uint32_t numQuadsWritten = 0;
  525. const size_t fxSize = m_effects.size();
  526. if (fxSize && !m_fontImage && !InitTexture())
  527. {
  528. return numQuadsWritten;
  529. }
  530. SVF_P2F_C4B_T2F_F4B* vertexData = verts;
  531. uint16_t* indexData = indices;
  532. size_t vertexOffset = 0;
  533. size_t indexOffset = 0;
  534. // Local function that is passed into CreateQuadsForText as the AddQuad function
  535. AddFunction AddQuad = [&vertexData, &indexData, &vertexOffset, &indexOffset, maxQuads, &numQuadsWritten]
  536. (const Vec3& v0, const Vec3& v1, const Vec3& v2, const Vec3& v3, const Vec2& tc0, const Vec2& tc1, const Vec2& tc2, const Vec2& tc3, uint32_t packedColor)
  537. {
  538. Vec2 xy0(v0);
  539. Vec2 xy1(v1);
  540. Vec2 xy2(v2);
  541. Vec2 xy3(v3);
  542. const bool vertexSpaceLeft = vertexOffset + 3 < maxQuads * 4;
  543. const bool indexSpaceLeft = indexOffset + 5 < maxQuads * 6;
  544. if (!vertexSpaceLeft || !indexSpaceLeft)
  545. {
  546. return false;
  547. }
  548. // This should never happen but for safety make sure we never write off end of buffers (should hit asserts above if this is the case)
  549. if (numQuadsWritten < maxQuads)
  550. {
  551. // define char quad
  552. vertexData[vertexOffset].xy = xy0;
  553. vertexData[vertexOffset].color.dcolor = packedColor;
  554. vertexData[vertexOffset].st = tc0;
  555. vertexData[vertexOffset].texIndex = 0;
  556. vertexData[vertexOffset].texHasColorChannel = 0;
  557. vertexData[vertexOffset].texIndex2 = 0;
  558. vertexData[vertexOffset].pad = 0;
  559. vertexData[vertexOffset + 1].xy = xy1;
  560. vertexData[vertexOffset + 1].color.dcolor = packedColor;
  561. vertexData[vertexOffset + 1].st = tc1;
  562. vertexData[vertexOffset + 1].texIndex = 0;
  563. vertexData[vertexOffset + 1].texHasColorChannel = 0;
  564. vertexData[vertexOffset + 1].texIndex2 = 0;
  565. vertexData[vertexOffset + 1].pad = 0;
  566. vertexData[vertexOffset + 2].xy = xy2;
  567. vertexData[vertexOffset + 2].color.dcolor = packedColor;
  568. vertexData[vertexOffset + 2].st = tc2;
  569. vertexData[vertexOffset + 2].texIndex = 0;
  570. vertexData[vertexOffset + 2].texHasColorChannel = 0;
  571. vertexData[vertexOffset + 2].texIndex2 = 0;
  572. vertexData[vertexOffset + 2].pad = 0;
  573. vertexData[vertexOffset + 3].xy = xy3;
  574. vertexData[vertexOffset + 3].color.dcolor = packedColor;
  575. vertexData[vertexOffset + 3].st = tc3;
  576. vertexData[vertexOffset + 3].texIndex = 0;
  577. vertexData[vertexOffset + 3].texHasColorChannel = 0;
  578. vertexData[vertexOffset + 3].texIndex2 = 0;
  579. vertexData[vertexOffset + 3].pad = 0;
  580. indexData[indexOffset + 0] = static_cast<uint16_t>(vertexOffset + 0);
  581. indexData[indexOffset + 1] = static_cast<uint16_t>(vertexOffset + 1);
  582. indexData[indexOffset + 2] = static_cast<uint16_t>(vertexOffset + 2);
  583. indexData[indexOffset + 3] = static_cast<uint16_t>(vertexOffset + 2);
  584. indexData[indexOffset + 4] = static_cast<uint16_t>(vertexOffset + 3);
  585. indexData[indexOffset + 5] = static_cast<uint16_t>(vertexOffset + 0);
  586. vertexOffset += 4;
  587. indexOffset += 6;
  588. ++numQuadsWritten;
  589. }
  590. return true;
  591. };
  592. CreateQuadsForText(GetDefaultWindowContext()->GetViewport(), x, y, z, str, asciiMultiLine, ctx, AddQuad);
  593. return numQuadsWritten;
  594. }
  595. uint32_t AZ::FFont::GetFontTextureVersion()
  596. {
  597. return m_fontImageVersion;
  598. }
  599. int AZ::FFont::CreateQuadsForText(const RHI::Viewport& viewport, float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx,
  600. AddFunction AddQuad)
  601. {
  602. int numQuads = 0;
  603. const size_t fxSize = m_effects.size();
  604. Prepare(str, true, ctx.m_requestSize);
  605. const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
  606. const FontEffect& fx = m_effects[fxIdx];
  607. bool passZeroColorOverridden = ctx.IsColorOverridden();
  608. uint32_t alphaBlend = passZeroColorOverridden ? ctx.m_colorOverride.a : fx.m_passes[0].m_color.a;
  609. if (alphaBlend > 128)
  610. {
  611. ++alphaBlend; // 0..256 for proper blending
  612. }
  613. // This is the "logical" size of the font (in pixels). The actual size of
  614. // the glyphs in the font texture may have additional scaling applied or
  615. // could have been re-rendered at a different size.
  616. Vec2 size = ctx.m_size;
  617. if (ctx.m_sizeIn800x600)
  618. {
  619. ScaleCoord(viewport, size.x, size.y);
  620. }
  621. // This scaling takes into account the logical size of the font relative
  622. // to any additional scaling applied (such as from "size ratio").
  623. const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
  624. Vec2 baseXY = Vec2(x, y); // in pixels
  625. if (ctx.m_sizeIn800x600)
  626. {
  627. ScaleCoord(viewport, baseXY.x, baseXY.y);
  628. }
  629. // snap for pixel perfect rendering (better quality for text)
  630. if (ctx.m_pixelAligned)
  631. {
  632. baseXY.x = floor(baseXY.x);
  633. baseXY.y = floor(baseXY.y);
  634. // for smaller fonts (half res or less) it's better to average multiple pixels (we don't miss lines)
  635. if (scaleInfo.scale.x < 0.9f)
  636. {
  637. baseXY.x += 0.5f; // try to average two columns (for exact half res)
  638. }
  639. if (scaleInfo.scale.y < 0.9f)
  640. {
  641. baseXY.y += 0.25f; // hand tweaked value to get a good result with tiny font (640x480 underscore in console)
  642. }
  643. }
  644. for (size_t j = 0, numPasses = fx.m_passes.size(); j < numPasses; ++j)
  645. {
  646. size_t i = numPasses - j - 1;
  647. const FontRenderingPass* pass = &fx.m_passes[i];
  648. if (!i)
  649. {
  650. alphaBlend = 256;
  651. }
  652. const ColorB& passColor = !i && passZeroColorOverridden ? ctx.m_colorOverride : fx.m_passes[i].m_color;
  653. // gather pass data
  654. Vec2 offset = pass->m_posOffset; // in pixels
  655. float charX = baseXY.x + offset.x; // in pixels
  656. float charY = baseXY.y + offset.y; // in pixels
  657. ColorB color = passColor;
  658. bool drawFrame = ctx.m_framed && i == numPasses - 1;
  659. if (drawFrame)
  660. {
  661. ColorB tempColor(255, 255, 255, 255);
  662. uint32_t frameColor = tempColor.pack_argb8888(); //note: this ends up in r,g,b,a order on little-endian machines
  663. Vec2 textSize = GetTextSizeUInternal(viewport, str, asciiMultiLine, ctx);
  664. float x0 = baseXY.x - 12;
  665. float y0 = baseXY.y - 6;
  666. float x1 = baseXY.x + textSize.x + 12;
  667. float y1 = baseXY.y + textSize.y + 6;
  668. bool culled = false;
  669. if (ctx.m_clippingEnabled)
  670. {
  671. float clipX = ctx.m_clipX;
  672. float clipY = ctx.m_clipY;
  673. float clipR = ctx.m_clipX + ctx.m_clipWidth;
  674. float clipB = ctx.m_clipY + ctx.m_clipHeight;
  675. if ((x0 >= clipR) || (y0 >= clipB) || (x1 < clipX) || (y1 < clipY))
  676. {
  677. culled = true;
  678. }
  679. x0 = max(clipX, x0);
  680. y0 = max(clipY, y0);
  681. x1 = min(clipR, x1);
  682. y1 = min(clipB, y1);
  683. }
  684. if (!culled)
  685. {
  686. Vec3 v0(x0, y0, z);
  687. Vec3 v2(x1, y1, z);
  688. Vec3 v1(v2.x, v0.y, v0.z);
  689. Vec3 v3(v0.x, v2.y, v0.z);
  690. if (ctx.m_drawTextFlags & eDrawText_UseTransform)
  691. {
  692. v0 = ctx.m_transform * v0;
  693. v2 = ctx.m_transform * v2;
  694. v1 = ctx.m_transform * v1;
  695. v3 = ctx.m_transform * v3;
  696. }
  697. Vec2 gradientUvMin, gradientUvMax;
  698. GetGradientTextureCoord(gradientUvMin.x, gradientUvMin.y, gradientUvMax.x, gradientUvMax.y);
  699. // define the frame quad
  700. Vec2 uv(gradientUvMin.x, gradientUvMax.y);
  701. if (AddQuad(v0, v1, v2, v3, uv, uv, uv, uv, frameColor))
  702. {
  703. ++numQuads;
  704. }
  705. else
  706. {
  707. return numQuads;
  708. }
  709. }
  710. }
  711. AZStd::wstring strW;
  712. AZStd::to_wstring(strW, str);
  713. // parse the string, ignoring control characters
  714. uint32_t nextCh = 0;
  715. const wchar_t* pChar = strW.c_str();
  716. while (uint32_t ch = *pChar)
  717. {
  718. ++pChar;
  719. nextCh = *pChar;
  720. switch (ch)
  721. {
  722. case '\\':
  723. {
  724. if (*pChar != 'n' || !asciiMultiLine)
  725. {
  726. break;
  727. }
  728. ++pChar;
  729. }
  730. case '\n':
  731. {
  732. charX = baseXY.x + offset.x;
  733. charY += size.y * (1.f + ctx.GetLineSpacing());
  734. continue;
  735. }
  736. break;
  737. case '\r':
  738. {
  739. charX = baseXY.x + offset.x;
  740. continue;
  741. }
  742. break;
  743. case '\t':
  744. {
  745. if (ctx.m_proportional)
  746. {
  747. charX += TabCharCount * size.x * AZ_FONT_SPACE_SIZE;
  748. }
  749. else
  750. {
  751. charX += TabCharCount * size.x * ctx.m_widthScale;
  752. }
  753. continue;
  754. }
  755. break;
  756. case '$':
  757. {
  758. if (ctx.m_processSpecialChars)
  759. {
  760. if (*pChar == '$')
  761. {
  762. ++pChar;
  763. }
  764. else if (isdigit(*pChar))
  765. {
  766. if (!i)
  767. {
  768. static const AZ::Color ColorTable[10] =
  769. {
  770. AZ::Colors::Black,
  771. AZ::Colors::White,
  772. AZ::Colors::Blue,
  773. AZ::Colors::Lime,
  774. AZ::Colors::Red,
  775. AZ::Colors::Cyan,
  776. AZ::Colors::Yellow,
  777. AZ::Colors::Fuchsia,
  778. AZ::Colors::Orange,
  779. AZ::Colors::Grey,
  780. };
  781. int colorIndex = (*pChar) - '0';
  782. ColorB newColor = AZColorToLYColorB(ColorTable[colorIndex]);
  783. color.r = newColor.r;
  784. color.g = newColor.g;
  785. color.b = newColor.b;
  786. // Leave alpha at original value!
  787. }
  788. ++pChar;
  789. continue;
  790. }
  791. else if (*pChar == 'O' || *pChar == 'o')
  792. {
  793. if (!i)
  794. {
  795. color = passColor;
  796. }
  797. ++pChar;
  798. continue;
  799. }
  800. }
  801. }
  802. break;
  803. default:
  804. break;
  805. }
  806. // get texture coordinates
  807. float texCoord[4];
  808. int charOffsetX, charOffsetY; // in font texels
  809. int charSizeX, charSizeY; // in font texels
  810. const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
  811. const AtomFont::GlyphSize requestSize = rerenderGlyphs ? ctx.m_requestSize : AtomFont::defaultGlyphSize;
  812. m_fontTexture->GetTextureCoord(m_fontTexture->GetCharSlot(ch, requestSize), texCoord, charSizeX, charSizeY, charOffsetX, charOffsetY, requestSize);
  813. int horizontalAdvance = m_fontTexture->GetHorizontalAdvance(ch, requestSize);
  814. float advance;
  815. if (ctx.m_proportional)
  816. {
  817. advance = horizontalAdvance * scaleInfo.scale.x;
  818. }
  819. else
  820. {
  821. advance = size.x * ctx.m_widthScale;
  822. }
  823. Vec2 kerningOffset(Vec2_Zero);
  824. if (ctx.m_kerningEnabled && nextCh)
  825. {
  826. kerningOffset = m_fontTexture->GetKerning(ch, nextCh) * scaleInfo.scale.x;
  827. }
  828. float trackingOffset = 0.0f;
  829. if (nextCh)
  830. {
  831. trackingOffset = ctx.m_tracking;
  832. }
  833. float px = charX + charOffsetX * scaleInfo.scale.x; // in pixels
  834. float py = charY + charOffsetY * scaleInfo.scale.y; // in pixels
  835. float pr = px + charSizeX * scaleInfo.scale.x;
  836. float pb = py + charSizeY * scaleInfo.scale.y;
  837. // compute clipping
  838. float newX = px; // in pixels
  839. float newY = py; // in pixels
  840. float newR = pr; // in pixels
  841. float newB = pb; // in pixels
  842. if (ctx.m_clippingEnabled)
  843. {
  844. float clipX = ctx.m_clipX;
  845. float clipY = ctx.m_clipY;
  846. float clipR = ctx.m_clipX + ctx.m_clipWidth;
  847. float clipB = ctx.m_clipY + ctx.m_clipHeight;
  848. // clip non visible
  849. if ((px >= clipR) || (py >= clipB) || (pr < clipX) || (pb < clipY))
  850. {
  851. charX += advance + kerningOffset.x + trackingOffset;
  852. continue;
  853. }
  854. // clip partially visible
  855. else
  856. {
  857. float width = horizontalAdvance * scaleInfo.rcpCellWidth;
  858. if ((width <= 0.0f) || (size.y <= 0.0f))
  859. {
  860. charX += advance + kerningOffset.x + trackingOffset;
  861. continue;
  862. }
  863. // clip the image to the scissor rect
  864. newX = max(clipX, px);
  865. newY = max(clipY, py);
  866. newR = min(clipR, pr);
  867. newB = min(clipB, pb);
  868. float rcpWidth = 1.0f / width;
  869. float rcpHeight = 1.0f / size.y;
  870. float texW = texCoord[2] - texCoord[0];
  871. float texH = texCoord[3] - texCoord[1];
  872. // clip horizontal
  873. texCoord[0] = texCoord[0] + texW * (newX - px) * rcpWidth;
  874. texCoord[2] = texCoord[2] + texW * (newR - pr) * rcpWidth;
  875. // clip vertical
  876. texCoord[1] = texCoord[1] + texH * (newY - py) * rcpHeight;
  877. texCoord[3] = texCoord[3] + texH * (newB - pb) * rcpHeight;
  878. }
  879. }
  880. Vec3 v0(newX, newY, z);
  881. Vec3 v2(newR, newB, z);
  882. Vec3 v1(v2.x, v0.y, v0.z);
  883. Vec3 v3(v0.x, v2.y, v0.z);
  884. Vec2 tc0(texCoord[0], texCoord[1]);
  885. Vec2 tc2(texCoord[2], texCoord[3]);
  886. Vec2 tc1(tc2.x, tc0.y);
  887. Vec2 tc3(tc0.x, tc2.y);
  888. uint32_t packedColor = 0xffffffff;
  889. {
  890. ColorB tempColor = color;
  891. tempColor.a = static_cast<uint8_t>(((uint32_t) tempColor.a * alphaBlend) >> 8);
  892. packedColor = tempColor.pack_argb8888(); //note: this ends up in r,g,b,a order on little-endian machines
  893. }
  894. if (ctx.m_drawTextFlags & eDrawText_UseTransform)
  895. {
  896. v0 = ctx.m_transform * v0;
  897. v2 = ctx.m_transform * v2;
  898. v1 = ctx.m_transform * v1;
  899. v3 = ctx.m_transform * v3;
  900. }
  901. if (AddQuad(v0, v1, v2, v3, tc0, tc1, tc2, tc3, packedColor))
  902. {
  903. ++numQuads;
  904. }
  905. else
  906. {
  907. return numQuads;
  908. }
  909. charX += advance + kerningOffset.x + trackingOffset;
  910. }
  911. }
  912. return numQuads;
  913. }
  914. AZ::FFont::TextScaleInfoInternal AZ::FFont::CalculateScaleInternal(const RHI::Viewport& viewport, const TextDrawContext& ctx) const
  915. {
  916. Vec2 size = GetRestoredFontSize(ctx); // in pixel
  917. if (ctx.m_sizeIn800x600)
  918. {
  919. ScaleCoord(viewport, size.x, size.y);
  920. }
  921. float rcpCellWidth;
  922. Vec2 scale;
  923. int fontTextureCellWidth = GetFontTexture()->GetCellWidth();
  924. int fontTextureCellHeight = GetFontTexture()->GetCellHeight();
  925. if (ctx.m_proportional)
  926. {
  927. rcpCellWidth = (1.0f / static_cast<float>(fontTextureCellWidth)) * size.x;
  928. scale = Vec2(rcpCellWidth * ctx.m_widthScale, size.y / static_cast<float>(fontTextureCellHeight));
  929. }
  930. else
  931. {
  932. rcpCellWidth = size.x / 16.0f;
  933. scale = Vec2(rcpCellWidth * ctx.m_widthScale, size.y * ctx.m_widthScale / 16.0f);
  934. }
  935. return TextScaleInfoInternal(scale, rcpCellWidth);
  936. }
  937. size_t AZ::FFont::GetTextLength(const char* str, const bool asciiMultiLine) const
  938. {
  939. size_t len = 0;
  940. // parse the string, ignoring control characters
  941. const char* pChar = str;
  942. while (char ch = *pChar++)
  943. {
  944. if ((ch & 0xC0) == 0x80)
  945. {
  946. continue; // Skip UTF-8 continuation bytes, we count only the first byte of a code-point
  947. }
  948. switch (ch)
  949. {
  950. case '\\':
  951. {
  952. if (*pChar != 'n' || !asciiMultiLine)
  953. {
  954. break;
  955. }
  956. ++pChar;
  957. }
  958. case '\n':
  959. case '\r':
  960. case '\t':
  961. {
  962. continue;
  963. }
  964. break;
  965. case '$':
  966. {
  967. if (*pChar == '$')
  968. {
  969. ++pChar;
  970. }
  971. else if (*pChar)
  972. {
  973. ++pChar;
  974. continue;
  975. }
  976. }
  977. break;
  978. default:
  979. break;
  980. }
  981. ++len;
  982. }
  983. return len;
  984. }
  985. void AZ::FFont::WrapText(AZStd::string& result, float maxWidth, const char* str, const TextDrawContext& ctx)
  986. {
  987. result = str;
  988. if (ctx.m_sizeIn800x600)
  989. {
  990. // ToDo: Update to work with Atom? LYN-3676
  991. // maxWidth = ???->ScaleCoordX(maxWidth);
  992. }
  993. Vec2 strSize = GetTextSize(result.c_str(), true, ctx);
  994. if (strSize.x <= maxWidth)
  995. {
  996. return;
  997. }
  998. // Assume a given string has multiple lines of text if it's height is
  999. // greater than the height of its font.
  1000. const bool multiLine = strSize.y > GetRestoredFontSize(ctx).y;
  1001. int lastSpace = -1;
  1002. const wchar_t* pLastSpace = NULL;
  1003. float lastSpaceWidth = 0.0f;
  1004. float curCharWidth = 0.0f;
  1005. float curLineWidth = 0.0f;
  1006. float biggestLineWidth = 0.0f;
  1007. float widthSum = 0.0f;
  1008. int curChar = 0;
  1009. AZStd::wstring resultW;
  1010. AZStd::to_wstring(resultW, result.c_str());
  1011. const wchar_t* pChar = resultW.c_str();
  1012. while (uint32_t ch = *pChar)
  1013. {
  1014. // Dollar sign escape codes. The following scenarios can happen with dollar signs embedded in a string.
  1015. // The following character is...
  1016. // 1. ... a digit, 'O' or 'o' which indicates a color code. Both characters a skipped in the width calculation.
  1017. // 2. ... another dollar sign. Only 1 dollar sign is skipped in the width calculation.
  1018. // 3. ... anything else. The dollar sign is processed in the width calculation.
  1019. if (ctx.m_processSpecialChars && ch == '$')
  1020. {
  1021. ++pChar;
  1022. char nextChar = static_cast<char>(*pChar);
  1023. if (isdigit(nextChar) || nextChar == 'O' || nextChar == 'o')
  1024. {
  1025. ++pChar;
  1026. continue;
  1027. }
  1028. else if (nextChar != '$')
  1029. {
  1030. --pChar;
  1031. }
  1032. }
  1033. // get char width and sum it to the line width
  1034. // Note: This is not unicode compatible, since char-width depends on surrounding context (ie, combining diacritics etc)
  1035. char codepoint[5];
  1036. AZStd::to_string(codepoint, 5, { (wchar_t*)&ch, 1 });
  1037. curCharWidth = GetTextSize(codepoint, true, ctx).x;
  1038. // keep track of spaces
  1039. // they are good for splitting the string
  1040. if (ch == ' ')
  1041. {
  1042. lastSpace = curChar;
  1043. lastSpaceWidth = curLineWidth + curCharWidth;
  1044. pLastSpace = pChar;
  1045. assert(*pLastSpace == ' ');
  1046. }
  1047. bool prevCharWasNewline = false;
  1048. const bool notFirstChar = pChar != resultW.c_str();
  1049. if (*pChar && notFirstChar)
  1050. {
  1051. const wchar_t* pPrevCharStr = pChar - 1;
  1052. prevCharWasNewline = pPrevCharStr[0] == '\n';
  1053. }
  1054. // if line exceed allowed width, split it
  1055. if (prevCharWasNewline || (curLineWidth + curCharWidth >= maxWidth && (*pChar)))
  1056. {
  1057. if (prevCharWasNewline)
  1058. {
  1059. // Reset the current line width to account for newline
  1060. curLineWidth = curCharWidth;
  1061. widthSum += curLineWidth;
  1062. }
  1063. else if ((lastSpace > 0) && ((curChar - lastSpace) < 16) && (curChar - lastSpace >= 0)) // 16 is the default threshold
  1064. {
  1065. *(char*)pLastSpace = '\n'; // This is safe inside UTF-8 because space is single-byte codepoint
  1066. if (lastSpaceWidth > biggestLineWidth)
  1067. {
  1068. biggestLineWidth = lastSpaceWidth;
  1069. }
  1070. curLineWidth = curLineWidth - lastSpaceWidth + curCharWidth;
  1071. widthSum += curLineWidth;
  1072. }
  1073. else
  1074. {
  1075. const wchar_t* buf = pChar;
  1076. size_t bytesProcessed = buf - resultW.c_str();
  1077. resultW.insert(resultW.begin() + bytesProcessed, L'\n'); // Insert the newline, this invalidates the iterator
  1078. buf = resultW.c_str() + bytesProcessed; // In case reallocation occurs, we ensure we are inside the new buffer
  1079. assert(*buf == '\n');
  1080. pChar = buf; // pChar once again points inside the target string, at the current character
  1081. assert(*pChar == ch);
  1082. ++pChar;
  1083. ++curChar;
  1084. if (curLineWidth > biggestLineWidth)
  1085. {
  1086. biggestLineWidth = curLineWidth;
  1087. }
  1088. widthSum += curLineWidth;
  1089. curLineWidth = curCharWidth;
  1090. }
  1091. // if we don't need any more line breaks, then just stop, but for
  1092. // multiple lines we can't assume that there aren't any more
  1093. // strings to wrap, so continue
  1094. if (strSize.x - widthSum <= maxWidth && !multiLine)
  1095. {
  1096. break;
  1097. }
  1098. lastSpaceWidth = 0;
  1099. lastSpace = 0;
  1100. }
  1101. else
  1102. {
  1103. curLineWidth += curCharWidth;
  1104. }
  1105. ++curChar;
  1106. ++pChar;
  1107. }
  1108. }
  1109. void AZ::FFont::GetGradientTextureCoord(float& minU, float& minV, float& maxU, float& maxV) const
  1110. {
  1111. const TextureSlot* slot = m_fontTexture->GetGradientSlot();
  1112. assert(slot);
  1113. float invWidth = 1.0f / (float) m_fontTexture->GetWidth();
  1114. float invHeight = 1.0f / (float) m_fontTexture->GetHeight();
  1115. // deflate by one pixel to avoid bilinear filtering on the borders
  1116. minU = slot->m_texCoords[0] + invWidth;
  1117. minV = slot->m_texCoords[1] + invHeight;
  1118. maxU = slot->m_texCoords[0] + (slot->m_characterWidth - 1) * invWidth;
  1119. maxV = slot->m_texCoords[1] + (slot->m_characterHeight - 1) * invHeight;
  1120. }
  1121. unsigned int AZ::FFont::GetEffectId(const char* effectName) const
  1122. {
  1123. if (effectName)
  1124. {
  1125. for (size_t i = 0, numEffects = m_effects.size(); i < numEffects; ++i)
  1126. {
  1127. if (!strcmp(m_effects[i].m_name.c_str(), effectName))
  1128. {
  1129. return static_cast<unsigned int>(i);
  1130. }
  1131. }
  1132. }
  1133. return 0;
  1134. }
  1135. unsigned int AZ::FFont::GetNumEffects() const
  1136. {
  1137. return static_cast<unsigned int>(m_effects.size());
  1138. }
  1139. const char* AZ::FFont::GetEffectName(unsigned int effectId) const
  1140. {
  1141. return (effectId < m_effects.size()) ? m_effects[effectId].m_name.c_str() : nullptr;
  1142. }
  1143. Vec2 AZ::FFont::GetMaxEffectOffset(unsigned int effectId) const
  1144. {
  1145. Vec2 maxOffset(0.0f, 0.0f);
  1146. if (effectId < m_effects.size())
  1147. {
  1148. const FontEffect& fx = m_effects[effectId];
  1149. for (size_t i = 0, numPasses = fx.m_passes.size(); i < numPasses; ++i)
  1150. {
  1151. const FontRenderingPass* pass = &fx.m_passes[numPasses - i - 1];
  1152. // gather pass data
  1153. Vec2 offset = pass->m_posOffset;
  1154. if (maxOffset.x < offset.x)
  1155. {
  1156. maxOffset.x = offset.x;
  1157. }
  1158. if (maxOffset.y < offset.y)
  1159. {
  1160. maxOffset.y = offset.y;
  1161. }
  1162. }
  1163. }
  1164. return maxOffset;
  1165. }
  1166. bool AZ::FFont::DoesEffectHaveTransparency(unsigned int effectId) const
  1167. {
  1168. const size_t fxSize = m_effects.size();
  1169. const size_t fxIdx = effectId < fxSize ? effectId : 0;
  1170. const FontEffect& fx = m_effects[fxIdx];
  1171. for (auto& pass : fx.m_passes)
  1172. {
  1173. // if the alpha is not 255 then there is transparency
  1174. if (pass.m_color.a != 255)
  1175. {
  1176. return true;
  1177. }
  1178. }
  1179. return false;
  1180. }
  1181. void AZ::FFont::AddCharsToFontTexture(const char* chars, int glyphSizeX, int glyphSizeY)
  1182. {
  1183. AtomFont::GlyphSize glyphSize(glyphSizeX, glyphSizeY);
  1184. Prepare(chars, false, glyphSize);
  1185. }
  1186. Vec2 AZ::FFont::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph, const TextDrawContext& ctx) const
  1187. {
  1188. return GetKerningInternal(GetDefaultWindowContext()->GetViewport(), leftGlyph, rightGlyph, ctx);
  1189. }
  1190. Vec2 AZ::FFont::GetKerningInternal(const RHI::Viewport& viewport, uint32_t leftGlyph, uint32_t rightGlyph, const TextDrawContext& ctx) const
  1191. {
  1192. const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
  1193. return m_fontTexture->GetKerning(leftGlyph, rightGlyph) * scaleInfo.scale.x;
  1194. }
  1195. float AZ::FFont::GetAscender(const TextDrawContext& ctx) const
  1196. {
  1197. return (ctx.m_size.y * m_fontTexture->GetAscenderToHeightRatio());
  1198. }
  1199. float AZ::FFont::GetBaseline(const TextDrawContext& ctx) const
  1200. {
  1201. return GetBaselineInternal(GetDefaultWindowContext()->GetViewport(), ctx);
  1202. }
  1203. float AZ::FFont::GetBaselineInternal(const RHI::Viewport& viewport, const TextDrawContext& ctx) const
  1204. {
  1205. const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
  1206. // Calculate baseline the same way as the font renderer which uses the glyph height * size ratio.
  1207. // Adding 1 because FontTexture always adds 1 to the char height in GetTextureCoord
  1208. return (round(m_fontTexture->GetCellHeight() * GetSizeRatio()) + 1.0f) * scaleInfo.scale.y;
  1209. }
  1210. bool AZ::FFont::InitTexture()
  1211. {
  1212. using namespace AZ;
  1213. const RHI::Format rhiImageFormat = RHI::Format::R8_UNORM;
  1214. const int width = m_fontTexture->GetWidth();
  1215. const int height = m_fontTexture->GetHeight();
  1216. const Name imageName(m_name.c_str());
  1217. Data::Instance<RPI::AttachmentImagePool> imagePool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
  1218. RHI::ImageDescriptor imageDescriptor = RHI::ImageDescriptor::Create2D(RHI::ImageBindFlags::ShaderRead, width, height, rhiImageFormat);
  1219. m_fontAttachmentImage = RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, imageName);
  1220. m_fontImage = m_fontAttachmentImage->GetRHIImage();
  1221. m_fontImage->SetName(imageName);
  1222. m_fontImageVersion = 0;
  1223. return true;
  1224. }
  1225. bool AZ::FFont::UpdateTexture()
  1226. {
  1227. using namespace AZ;
  1228. if (!m_fontImage)
  1229. {
  1230. return false;
  1231. }
  1232. if (m_fontTexture->GetWidth() != static_cast<int>(m_fontImage->GetDescriptor().m_size.m_width) || m_fontTexture->GetHeight() != static_cast<int>(m_fontImage->GetDescriptor().m_size.m_height))
  1233. {
  1234. AZ_Assert(false, "AtomFont::FFont:::UpdateTexture size mismatch between texture and image!");
  1235. return false;
  1236. }
  1237. RHI::ImageSubresourceRange range;
  1238. range.m_mipSliceMin = 0;
  1239. range.m_mipSliceMax = 0;
  1240. range.m_arraySliceMin = 0;
  1241. range.m_arraySliceMax = 0;
  1242. RHI::ImageSubresourceLayout layout;
  1243. m_fontImage->GetSubresourceLayouts(range, &layout, nullptr);
  1244. RHI::ImageUpdateRequest imageUpdateReq;
  1245. imageUpdateReq.m_image = m_fontImage.get();
  1246. imageUpdateReq.m_imageSubresource = RHI::ImageSubresource{ 0, 0 };
  1247. imageUpdateReq.m_sourceData = m_fontTexture->GetBuffer();
  1248. imageUpdateReq.m_sourceSubresourceLayout = layout;
  1249. const RHI::ResultCode result = m_fontAttachmentImage->UpdateImageContents(imageUpdateReq);
  1250. return result == RHI::ResultCode::Success;
  1251. }
  1252. bool AZ::FFont::InitCache()
  1253. {
  1254. m_fontTexture->CreateGradientSlot();
  1255. // precache (not required but for faster printout later)
  1256. const char first = ' ';
  1257. const char last = '~';
  1258. char buf[last - first + 2];
  1259. char* p = buf;
  1260. // precache all [normal] printable characters to the string (missing ones are updated on demand)
  1261. for (char i = first; i <= last; ++i)
  1262. {
  1263. *p++ = i;
  1264. }
  1265. *p = 0;
  1266. Prepare(buf, false);
  1267. return true;
  1268. }
  1269. AZ::FFont::FontEffect* AZ::FFont::AddEffect(const char* effectName)
  1270. {
  1271. m_effects.push_back(FontEffect(effectName));
  1272. return &m_effects[m_effects.size() - 1];
  1273. }
  1274. AZ::FFont::FontEffect* AZ::FFont::GetDefaultEffect()
  1275. {
  1276. return &m_effects[0];
  1277. }
  1278. void AZ::FFont::Prepare(const char* str, bool updateTexture, const AtomFont::GlyphSize& glyphSize)
  1279. {
  1280. const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
  1281. const AtomFont::GlyphSize usedGlyphSize = rerenderGlyphs ? glyphSize : AtomFont::defaultGlyphSize;
  1282. bool texUpdateNeeded = m_fontTexture->PreCacheString(str, nullptr, m_sizeRatio, usedGlyphSize, m_fontHintParams) == 1 || m_fontTexDirty;
  1283. if (updateTexture && texUpdateNeeded && m_fontImage)
  1284. {
  1285. UpdateTexture();
  1286. m_fontTexDirty = false;
  1287. ++m_fontImageVersion;
  1288. // Let any listeners know that the font texture has changed
  1289. // TODO Update to an AZ::Event when Cry use of this bus is cleaned out.
  1290. FontNotificationBus::Broadcast(&FontNotificationBus::Events::OnFontTextureUpdated, this);
  1291. }
  1292. else
  1293. {
  1294. m_fontTexDirty = texUpdateNeeded;
  1295. }
  1296. }
  1297. Vec2 AZ::FFont::GetRestoredFontSize(const TextDrawContext& ctx) const
  1298. {
  1299. // Calculate the scale that we need to apply to the text size to ensure
  1300. // it's on-screen size is the same regardless of the slot scaling needed
  1301. // to fit the glyphs of the font within the font texture slots.
  1302. float restoringScale = IFFontConstants::defaultSizeRatio / m_sizeRatio;
  1303. return Vec2(ctx.m_size.x * restoringScale, ctx.m_size.y * restoringScale);
  1304. }
  1305. void AZ::FFont::ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) const
  1306. {
  1307. float width = viewport.m_maxX - viewport.m_minX;
  1308. float height = viewport.m_maxY - viewport.m_minY;
  1309. x *= width / WindowScaleWidth;
  1310. y *= height / WindowScaleHeight;
  1311. }
  1312. static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::TextDrawParameters& params)
  1313. {
  1314. if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)
  1315. {
  1316. ctx.m_drawTextFlags |= eDrawText_Center;
  1317. }
  1318. if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Right)
  1319. {
  1320. ctx.m_drawTextFlags |= eDrawText_Right;
  1321. }
  1322. if (params.m_vAlign == AzFramework::TextVerticalAlignment::Center)
  1323. {
  1324. ctx.m_drawTextFlags |= eDrawText_CenterV;
  1325. }
  1326. if (params.m_vAlign == AzFramework::TextVerticalAlignment::Bottom)
  1327. {
  1328. ctx.m_drawTextFlags |= eDrawText_Bottom;
  1329. }
  1330. if (params.m_monospace)
  1331. {
  1332. ctx.m_drawTextFlags |= eDrawText_Monospace;
  1333. }
  1334. if (params.m_depthTest)
  1335. {
  1336. ctx.m_drawTextFlags |= eDrawText_DepthTest;
  1337. }
  1338. if (params.m_virtual800x600ScreenSize)
  1339. {
  1340. ctx.m_drawTextFlags |= eDrawText_800x600;
  1341. }
  1342. if (!params.m_scaleWithWindow)
  1343. {
  1344. ctx.m_drawTextFlags |= eDrawText_FixedSize;
  1345. }
  1346. if (params.m_useTransform)
  1347. {
  1348. ctx.m_drawTextFlags |= eDrawText_UseTransform;
  1349. ctx.SetTransform(AZMatrix3x4ToLYMatrix3x4(params.m_transform));
  1350. }
  1351. }
  1352. AZ::FFont::DrawParameters AZ::FFont::ExtractDrawParameters(const AzFramework::TextDrawParameters& params, AZStd::string_view text, bool forceCalculateSize)
  1353. {
  1354. DrawParameters internalParams;
  1355. if (params.m_drawViewportId == AzFramework::InvalidViewportId ||
  1356. text.empty())
  1357. {
  1358. return internalParams;
  1359. }
  1360. float posX = params.m_position.GetX();
  1361. float posY = params.m_position.GetY();
  1362. internalParams.m_viewportContext = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get()->GetViewportContextById(params.m_drawViewportId);
  1363. const AZ::RHI::Viewport& viewport = internalParams.m_viewportContext->GetWindowContext()->GetViewport();
  1364. internalParams.m_viewport = &viewport;
  1365. if (params.m_virtual800x600ScreenSize)
  1366. {
  1367. posX *= WindowScaleWidth / (viewport.m_maxX - viewport.m_minX);
  1368. posY *= WindowScaleHeight / (viewport.m_maxY - viewport.m_minY);
  1369. }
  1370. internalParams.m_ctx.SetBaseState(GS_NODEPTHTEST);
  1371. internalParams.m_ctx.SetColor(AZColorToLYColorF(params.m_color));
  1372. internalParams.m_ctx.SetEffect(params.m_effectIndex);
  1373. internalParams.m_ctx.SetCharWidthScale((params.m_monospace || params.m_scaleWithWindow) ? 0.5f : 1.0f);
  1374. internalParams.m_ctx.EnableFrame(false);
  1375. internalParams.m_ctx.SetProportional(!params.m_monospace && params.m_scaleWithWindow);
  1376. internalParams.m_ctx.SetSizeIn800x600(params.m_scaleWithWindow && params.m_virtual800x600ScreenSize);
  1377. internalParams.m_ctx.SetSize(AZVec2ToLYVec2(
  1378. AZ::Vector2(params.m_textSizeFactor, params.m_textSizeFactor) * params.m_scale *
  1379. internalParams.m_viewportContext->GetDpiScalingFactor()));
  1380. internalParams.m_ctx.SetLineSpacing(params.m_lineSpacing);
  1381. if (params.m_hAlign != AzFramework::TextHorizontalAlignment::Left ||
  1382. params.m_vAlign != AzFramework::TextVerticalAlignment::Top ||
  1383. forceCalculateSize)
  1384. {
  1385. // We align based on the size of the default font effect because we do not want the
  1386. // text to move when the font effect is changed
  1387. unsigned int effectIndex = internalParams.m_ctx.m_fxIdx;
  1388. internalParams.m_ctx.SetEffect(0);
  1389. Vec2 textSize = GetTextSizeUInternal(viewport, text.data(), params.m_multiline, internalParams.m_ctx);
  1390. internalParams.m_ctx.SetEffect(effectIndex);
  1391. // If we're using virtual 800x600 coordinates, convert the text size from
  1392. // pixels to that before using it as an offset.
  1393. if (internalParams.m_ctx.m_sizeIn800x600)
  1394. {
  1395. float width = 1.0f;
  1396. float height = 1.0f;
  1397. ScaleCoord(viewport, width, height);
  1398. textSize.x /= width;
  1399. textSize.y /= height;
  1400. }
  1401. if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)
  1402. {
  1403. posX -= textSize.x * 0.5f;
  1404. }
  1405. else if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Right)
  1406. {
  1407. posX -= textSize.x;
  1408. }
  1409. if (params.m_vAlign == AzFramework::TextVerticalAlignment::Center)
  1410. {
  1411. posY -= textSize.y * 0.5f;
  1412. }
  1413. else if (params.m_vAlign == AzFramework::TextVerticalAlignment::Bottom)
  1414. {
  1415. posY -= textSize.y;
  1416. }
  1417. internalParams.m_size = AZ::Vector2{textSize.x, textSize.y};
  1418. }
  1419. SetCommonContextFlags(internalParams.m_ctx, params);
  1420. internalParams.m_ctx.m_drawTextFlags |= eDrawText_2D;
  1421. internalParams.m_position = AZ::Vector2{posX, posY};
  1422. return internalParams;
  1423. }
  1424. void AZ::FFont::DrawScreenAlignedText2d(
  1425. const AzFramework::TextDrawParameters& params,
  1426. AZStd::string_view text)
  1427. {
  1428. DrawParameters internalParams = ExtractDrawParameters(params, text, false);
  1429. if (!internalParams.m_viewportContext)
  1430. {
  1431. return;
  1432. }
  1433. DrawStringUInternal(
  1434. *internalParams.m_viewport,
  1435. internalParams.m_viewportContext,
  1436. internalParams.m_position.GetX(),
  1437. internalParams.m_position.GetY(),
  1438. params.m_position.GetZ(), // Z
  1439. text.data(),
  1440. params.m_multiline,
  1441. internalParams.m_ctx
  1442. );
  1443. }
  1444. void AZ::FFont::DrawScreenAlignedText3d(
  1445. const AzFramework::TextDrawParameters& params,
  1446. AZStd::string_view text)
  1447. {
  1448. DrawParameters internalParams = ExtractDrawParameters(params, text, false);
  1449. if (!internalParams.m_viewportContext)
  1450. {
  1451. return;
  1452. }
  1453. AZ::RPI::ViewPtr currentView = internalParams.m_viewportContext->GetDefaultView();
  1454. if (!currentView)
  1455. {
  1456. return;
  1457. }
  1458. const AZ::Vector3 positionNdc = AzFramework::WorldToScreenNdc(
  1459. params.m_position, currentView->GetWorldToViewMatrixAsMatrix3x4(), currentView->GetViewToClipMatrix());
  1460. // Text behind the camera shouldn't get rendered. WorldToScreenNdc returns values in the range 0 - 1, so Z < 0.5 is behind the screen
  1461. // and >= 0.5 is in front of the screen.
  1462. if (positionNdc.GetZ() < 0.5f)
  1463. {
  1464. return;
  1465. }
  1466. internalParams.m_ctx.m_sizeIn800x600 = false;
  1467. DrawStringUInternal(
  1468. *internalParams.m_viewport,
  1469. internalParams.m_viewportContext,
  1470. positionNdc.GetX() * internalParams.m_viewport->GetWidth() + internalParams.m_position.GetX(),
  1471. (1.0f - positionNdc.GetY()) * internalParams.m_viewport->GetHeight() + internalParams.m_position.GetY(),
  1472. positionNdc.GetZ(), // Z
  1473. text.data(),
  1474. params.m_multiline,
  1475. internalParams.m_ctx
  1476. );
  1477. }
  1478. AZ::Vector2 AZ::FFont::GetTextSize(const AzFramework::TextDrawParameters& params, AZStd::string_view text)
  1479. {
  1480. DrawParameters sizeParams = ExtractDrawParameters(params, text, true);
  1481. return sizeParams.m_size;
  1482. }
  1483. #endif //USE_NULLFONT_ALWAYS