FFont.cpp 55 KB

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