| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- // Description : Font class.
- #if !defined(USE_NULLFONT_ALWAYS)
- #include <AzCore/Math/Color.h>
- #include <AzCore/Math/Matrix4x4.h>
- #include <AzCore/Math/MatrixUtils.h>
- #include <AzCore/Casting/numeric_cast.h>
- #include <AzFramework/Viewport/ViewportScreen.h>
- #include <AzFramework/Viewport/ScreenGeometry.h>
- #include <AzFramework/Archive/Archive.h>
- #include <AtomLyIntegration/AtomFont/FFont.h>
- #include <AtomLyIntegration/AtomFont/AtomFont.h>
- #include <AtomLyIntegration/AtomFont/FontTexture.h>
- #include <CryCommon/MathConversion.h>
- #include <AzCore/std/parallel/lock.h>
- #include <Atom/RPI.Public/RPISystemInterface.h>
- #include <Atom/RHI/RHISystemInterface.h>
- #include <Atom/RPI.Public/Shader/Shader.h>
- #include <Atom/RPI.Public/RPIUtils.h>
- #include <Atom/RPI.Public/ViewportContext.h>
- #include <Atom/RPI.Public/View.h>
- #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
- #include <Atom/RPI.Public/Image/AttachmentImagePool.h>
- #include <Atom/RPI.Public/ViewportContextManager.h>
- #include <AzCore/Interface/Interface.h>
- #include <Atom/RHI/Factory.h>
- #include <Atom/RHI/DrawPacket.h>
- #include <Atom/RHI/ImagePool.h>
- #include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
- static const int TabCharCount = 4;
- // set buffer sizes to hold max characters that can be drawn in 1 DrawString call
- static const size_t MaxVerts = 8 * 1024; // 2048 quads
- static const size_t MaxIndices = (MaxVerts * 6) / 4; // 6 indices per quad, 6/4 * MaxVerts
- AZ::FFont::FFont(AZ::AtomFont* atomFont, const char* fontName)
- : m_name(fontName)
- , m_atomFont(atomFont)
- {
- assert(m_name.c_str());
- assert(m_atomFont);
- // create default effect
- FontEffect* effect = AddEffect("default");
- effect->AddPass();
- // Create cpu memory to cache the font draw data before submit
- m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts];
- m_indexBuffer = new u16[MaxIndices];
- m_vertexCount = 0;
- m_indexCount = 0;
- AddRef();
- }
- AZ::RPI::ViewportContextPtr AZ::FFont::GetDefaultViewportContext() const
- {
- auto viewContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
- return viewContextManager->GetDefaultViewportContext();
- }
- AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const
- {
- if (auto defaultViewportContext = GetDefaultViewportContext())
- {
- return defaultViewportContext->GetWindowContext();
- }
- return {};
- }
- AZ::FFont::~FFont()
- {
- AZ_Assert(m_atomFont == nullptr, "The font should already be unregistered through a call to AZ::FFont::Release()");
- delete[] m_vertexBuffer;
- delete[] m_indexBuffer;
- Free();
- }
- int32_t AZ::FFont::AddRef()
- {
- ref_count::add_ref();
- return aznumeric_cast<int32_t>(ref_count::use_count());
- }
- int32_t AZ::FFont::Release()
- {
- int32_t useCount = aznumeric_cast<int32_t>(ref_count::use_count()) - 1;
- ref_count::release();
- return useCount;
- }
- // Load a font from a TTF file
- bool AZ::FFont::Load(const char* fontFilePath, unsigned int width, unsigned int height, unsigned int widthNumSlots, unsigned int heightNumSlots, unsigned int flags, float sizeRatio)
- {
- if (!fontFilePath)
- {
- return false;
- }
- Free();
- auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
- AZ::IO::Path fullFile(m_curPath);
- fullFile /= fontFilePath;
- int smoothMethodFlag = (flags & TTFFLAG_SMOOTH_MASK) >> TTFFLAG_SMOOTH_SHIFT;
- AZ::FontSmoothMethod smoothMethod = AZ::FontSmoothMethod::None;
- switch (smoothMethodFlag)
- {
- case TTFFLAG_SMOOTH_BLUR:
- smoothMethod = AZ::FontSmoothMethod::Blur;
- break;
- case TTFFLAG_SMOOTH_SUPERSAMPLE:
- smoothMethod = AZ::FontSmoothMethod::SuperSample;
- break;
- }
- int smoothAmountFlag = (flags & TTFFLAG_SMOOTH_AMOUNT_MASK);
- AZ::FontSmoothAmount smoothAmount = AZ::FontSmoothAmount::None;
- switch (smoothAmountFlag)
- {
- case TTFLAG_SMOOTH_AMOUNT_2X:
- smoothAmount = AZ::FontSmoothAmount::x2;
- break;
- case TTFLAG_SMOOTH_AMOUNT_4X:
- smoothAmount = AZ::FontSmoothAmount::x4;
- break;
- }
- AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
- fileIoBase->Open(fullFile.c_str(), AZ::IO::GetOpenModeFromStringMode("rb"), fileHandle);
- if (fileHandle == AZ::IO::InvalidHandle)
- {
- return false;
- }
- AZ::u64 fileSize{};
- fileIoBase->Size(fileHandle, fileSize);
- if (!fileSize)
- {
- fileIoBase->Close(fileHandle);
- return false;
- }
- auto buffer = AZStd::make_unique<uint8_t[]>(fileSize);
- if (!fileIoBase->Read(fileHandle, buffer.get(), fileSize))
- {
- fileIoBase->Close(fileHandle);
- return false;
- }
- fileIoBase->Close(fileHandle);
- if (!m_fontTexture)
- {
- m_fontTexture = new FontTexture();
- }
- if (!m_fontTexture || !m_fontTexture->CreateFromMemory(buffer.get(), (int)fileSize, width, height, smoothMethod, smoothAmount, widthNumSlots, heightNumSlots, sizeRatio))
- {
- return false;
- }
- m_monospacedFont = m_fontTexture->GetMonospaced();
- m_fontBuffer = AZStd::move(buffer);
- m_fontBufferSize = fileSize;
- m_fontTexDirty = false;
- m_sizeRatio = sizeRatio;
- InitCache();
- return true;
- }
- void AZ::FFont::Free()
- {
- m_fontImage = nullptr;
- m_fontImageVersion = 0;
- delete m_fontTexture;
- m_fontTexture = nullptr;
- m_fontBuffer.reset();
- m_fontBufferSize = 0;
- }
- void AZ::FFont::DrawString(float x, float y, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
- {
- if (!str)
- {
- return;
- }
- DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, 1.0f, str, asciiMultiLine, ctx);
- }
- void AZ::FFont::DrawString(float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
- {
- if (!str)
- {
- return;
- }
- DrawStringUInternal(GetDefaultWindowContext()->GetViewport(), GetDefaultViewportContext(), x, y, z, str, asciiMultiLine, ctx);
- }
- void AZ::FFont::DrawStringUInternal(
- const RHI::Viewport& viewport,
- RPI::ViewportContextPtr viewportContext,
- float x,
- float y,
- float z,
- const char* str,
- const bool asciiMultiLine,
- const TextDrawContext& ctx)
- {
- // Lazily ensure we're initialized before attempting to render.
- // Validate that there is a render scene before attempting to init.
- if (!viewportContext || !viewportContext->GetRenderScene())
- {
- return;
- }
- if (!str
- || !m_vertexBuffer // vertex buffer isn't created until BootstrapScene is ready, Editor tries to render text before that.
- || !m_fontTexture
- || ctx.m_fxIdx >= m_effects.size()
- || m_effects[ctx.m_fxIdx].m_passes.empty())
- {
- return;
- }
- const size_t fxSize = m_effects.size();
- if (fxSize && !m_fontImage && !InitTexture())
- {
- return;
- }
- const bool orthoMode = ctx.m_overrideViewProjMatrices;
- const float viewX = viewport.m_minX;
- const float viewY = viewport.m_minY;
- const float viewWidth = viewport.m_maxX - viewport.m_minX;
- const float viewHeight = viewport.m_maxY - viewport.m_minY;
- const float zf = viewport.m_minZ;
- const float zn = viewport.m_maxZ;
- Matrix4x4 modelViewProjMat;
- if (!orthoMode)
- {
- AZ::RPI::ViewPtr view = viewportContext->GetDefaultView();
- modelViewProjMat = view->GetWorldToClipMatrix();
- }
- else
- {
- if (viewWidth == 0 || viewHeight == 0)
- {
- return;
- }
- AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
- }
- size_t startingVertexCount = m_vertexCount;
- // Local function that is passed into CreateQuadsForText as the AddQuad function
- AZ::FFont::AddFunction AddQuad = [this, startingVertexCount]
- (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)
- {
- const bool vertexSpaceLeft = m_vertexCount + 4 < MaxVerts;
- const bool indexSpaceLeft = m_indexCount + 6 < MaxIndices;
- if (!vertexSpaceLeft || !indexSpaceLeft)
- {
- return false;
- }
- size_t vertexOffset = m_vertexCount;
- m_vertexCount += 4;
- size_t indexOffset = m_indexCount;
- m_indexCount += 6;
- // define char quad
- m_vertexBuffer[vertexOffset + 0].xyz = v0;
- m_vertexBuffer[vertexOffset + 0].color.dcolor = packedColor;
- m_vertexBuffer[vertexOffset + 0].st = tc0;
- m_vertexBuffer[vertexOffset + 1].xyz = v1;
- m_vertexBuffer[vertexOffset + 1].color.dcolor = packedColor;
- m_vertexBuffer[vertexOffset + 1].st = tc1;
- m_vertexBuffer[vertexOffset + 2].xyz = v2;
- m_vertexBuffer[vertexOffset + 2].color.dcolor = packedColor;
- m_vertexBuffer[vertexOffset + 2].st = tc2;
- m_vertexBuffer[vertexOffset + 3].xyz = v3;
- m_vertexBuffer[vertexOffset + 3].color.dcolor = packedColor;
- m_vertexBuffer[vertexOffset + 3].st = tc3;
- uint16_t startingIndex = static_cast<uint16_t>(vertexOffset - startingVertexCount);
- m_indexBuffer[indexOffset + 0] = startingIndex + 0;
- m_indexBuffer[indexOffset + 1] = startingIndex + 1;
- m_indexBuffer[indexOffset + 2] = startingIndex + 2;
- m_indexBuffer[indexOffset + 3] = startingIndex + 2;
- m_indexBuffer[indexOffset + 4] = startingIndex + 3;
- m_indexBuffer[indexOffset + 5] = startingIndex + 0;
- return true;
- };
- int numQuads = 0;
- {
- AZStd::lock_guard<AZStd::mutex> lock(m_vertexDataMutex);
- numQuads = CreateQuadsForText(viewport, x, y, z, str, asciiMultiLine, ctx, AddQuad);
- }
- if (numQuads)
- {
- AZ::RPI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::AtomBridge::PerViewportDynamicDraw::Get()->GetDynamicDrawContextForViewport(m_dynamicDrawContextName, viewportContext->GetId());
- if (dynamicDraw)
- {
- //setup per draw srg
- auto drawSrg = dynamicDraw->NewDrawSrg();
- drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat);
- drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontAttachmentImage->GetImageView());
- drawSrg->Compile();
- dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg);
- }
- m_indexCount = 0;
- m_vertexCount = 0;
- }
- }
- Vec2 AZ::FFont::GetTextSize(const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
- {
- if (!str)
- {
- return Vec2(0.0f, 0.0f);
- }
- return GetTextSizeUInternal(GetDefaultWindowContext()->GetViewport(), str, asciiMultiLine, ctx);
- }
- Vec2 AZ::FFont::GetTextSizeUInternal(
- const RHI::Viewport& viewport,
- const char* str,
- const bool asciiMultiLine,
- const TextDrawContext& ctx)
- {
- const size_t fxSize = m_effects.size();
- if (!str || !m_fontTexture || !fxSize)
- {
- return Vec2(0, 0);
- }
- Prepare(str, false, ctx.m_requestSize);
- // This is the "logical" size of the font (in pixels). The actual size of
- // the glyphs in the font texture may have additional scaling applied or
- // could have been re-rendered at a different size.
- Vec2 size = ctx.m_size;
- if (ctx.m_sizeIn800x600)
- {
- ScaleCoord(viewport, size.x, size.y);
- }
- // This scaling takes into account the logical size of the font relative
- // to any additional scaling applied (such as from "size ratio").
- const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
- float maxW = 0;
- float maxH = 0;
- const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
- const FontEffect& fx = m_effects[fxIdx];
- AZStd::wstring strW;
- AZStd::to_wstring(strW, str);
- for (size_t i = 0, numPasses = fx.m_passes.size(); i < numPasses; ++i)
- {
- const FontRenderingPass* pass = &fx.m_passes[numPasses - i - 1];
- // gather pass data
- Vec2 offset = pass->m_posOffset;
- float charX = offset.x;
- float charY = offset.y + size.y;
- if (charY > maxH)
- {
- maxH = charY;
- }
- // parse the string, ignoring control characters
- uint32_t nextCh = 0;
- const wchar_t* pChar = strW.c_str();
- while (uint32_t ch = *pChar)
- {
- ++pChar;
- nextCh = *pChar;
- switch (ch)
- {
- case '\\':
- {
- if (*pChar != 'n' || !asciiMultiLine)
- {
- break;
- }
- ++pChar;
- }
- case '\n':
- {
- if (charX > maxW)
- {
- maxW = charX;
- }
- charX = offset.x;
- charY += size.y * (1.f + ctx.GetLineSpacing());
- if (charY > maxH)
- {
- maxH = charY;
- }
- continue;
- }
- break;
- case '\r':
- {
- if (charX > maxW)
- {
- maxW = charX;
- }
- charX = offset.x;
- continue;
- }
- break;
- case '\t':
- {
- if (ctx.m_proportional)
- {
- charX += TabCharCount * size.x * AZ_FONT_SPACE_SIZE;
- }
- else
- {
- charX += TabCharCount * size.x * ctx.m_widthScale;
- }
- continue;
- }
- break;
- case '$':
- {
- if (ctx.m_processSpecialChars)
- {
- if (*pChar == '$')
- {
- ++pChar;
- }
- else if (isdigit(*pChar))
- {
- ++pChar;
- continue;
- }
- else if (*pChar == 'O' || *pChar == 'o')
- {
- ++pChar;
- continue;
- }
- }
- }
- break;
- default:
- break;
- }
- const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
- const AtomFont::GlyphSize requestSize = rerenderGlyphs ? ctx.m_requestSize : AtomFont::defaultGlyphSize;
- int horizontalAdvance = m_fontTexture->GetHorizontalAdvance(ch, requestSize);
- float advance;
- if (ctx.m_proportional)
- {
- advance = horizontalAdvance * scaleInfo.scale.x;
- }
- else
- {
- advance = size.x * ctx.m_widthScale;
- }
- // Adjust "advance" here for kerning purposes
- Vec2 kerningOffset(Vec2_Zero);
- if (ctx.m_kerningEnabled && nextCh)
- {
- kerningOffset = m_fontTexture->GetKerning(ch, nextCh) * scaleInfo.scale.x;
- }
- // Adjust char width with tracking only if there is a next character
- if (nextCh)
- {
- charX += ctx.m_tracking;
- }
- charX += advance + kerningOffset.x;
- }
- if (charX > maxW)
- {
- maxW = charX;
- }
- }
- return Vec2(maxW, maxH);
- }
- uint32_t AZ::FFont::GetNumQuadsForText(const char* str, const bool asciiMultiLine, const TextDrawContext& ctx)
- {
- uint32_t numQuads = 0;
- const size_t fxSize = m_effects.size();
- const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
- const FontEffect& fx = m_effects[fxIdx];
- AZStd::wstring strW;
- AZStd::to_wstring(strW, str);
- for (size_t j = 0, numPasses = fx.m_passes.size(); j < numPasses; ++j)
- {
- size_t i = numPasses - j - 1;
- bool drawFrame = ctx.m_framed && i == numPasses - 1;
- if (drawFrame)
- {
- ++numQuads;
- }
- const wchar_t* pChar = strW.c_str();
- while (uint32_t ch = *pChar)
- {
- ++pChar;
- switch (ch)
- {
- case '\\':
- {
- if (*pChar != 'n' || !asciiMultiLine)
- {
- break;
- }
- ++pChar;
- }
- case '\n':
- {
- continue;
- }
- break;
- case '\r':
- {
- continue;
- }
- break;
- case '\t':
- {
- continue;
- }
- break;
- case '$':
- {
- if (ctx.m_processSpecialChars)
- {
- if (*pChar == '$')
- {
- ++pChar;
- }
- else if (isdigit(*pChar))
- {
- ++pChar;
- continue;
- }
- else if (*pChar == 'O' || *pChar == 'o')
- {
- ++pChar;
- continue;
- }
- }
- }
- break;
- default:
- break;
- }
- ++numQuads;
- }
- }
- return numQuads;
- }
- 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)
- {
- uint32_t numQuadsWritten = 0;
- const size_t fxSize = m_effects.size();
- if (fxSize && !m_fontImage && !InitTexture())
- {
- return numQuadsWritten;
- }
- SVF_P2F_C4B_T2F_F4B* vertexData = verts;
- uint16_t* indexData = indices;
- size_t vertexOffset = 0;
- size_t indexOffset = 0;
- // Local function that is passed into CreateQuadsForText as the AddQuad function
- AddFunction AddQuad = [&vertexData, &indexData, &vertexOffset, &indexOffset, maxQuads, &numQuadsWritten]
- (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)
- {
- Vec2 xy0(v0);
- Vec2 xy1(v1);
- Vec2 xy2(v2);
- Vec2 xy3(v3);
- const bool vertexSpaceLeft = vertexOffset + 3 < maxQuads * 4;
- const bool indexSpaceLeft = indexOffset + 5 < maxQuads * 6;
- if (!vertexSpaceLeft || !indexSpaceLeft)
- {
- return false;
- }
- // 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)
- if (numQuadsWritten < maxQuads)
- {
- // define char quad
- vertexData[vertexOffset].xy = xy0;
- vertexData[vertexOffset].color.dcolor = packedColor;
- vertexData[vertexOffset].st = tc0;
- vertexData[vertexOffset].texIndex = 0;
- vertexData[vertexOffset].texHasColorChannel = 0;
- vertexData[vertexOffset].texIndex2 = 0;
- vertexData[vertexOffset].pad = 0;
- vertexData[vertexOffset + 1].xy = xy1;
- vertexData[vertexOffset + 1].color.dcolor = packedColor;
- vertexData[vertexOffset + 1].st = tc1;
- vertexData[vertexOffset + 1].texIndex = 0;
- vertexData[vertexOffset + 1].texHasColorChannel = 0;
- vertexData[vertexOffset + 1].texIndex2 = 0;
- vertexData[vertexOffset + 1].pad = 0;
- vertexData[vertexOffset + 2].xy = xy2;
- vertexData[vertexOffset + 2].color.dcolor = packedColor;
- vertexData[vertexOffset + 2].st = tc2;
- vertexData[vertexOffset + 2].texIndex = 0;
- vertexData[vertexOffset + 2].texHasColorChannel = 0;
- vertexData[vertexOffset + 2].texIndex2 = 0;
- vertexData[vertexOffset + 2].pad = 0;
- vertexData[vertexOffset + 3].xy = xy3;
- vertexData[vertexOffset + 3].color.dcolor = packedColor;
- vertexData[vertexOffset + 3].st = tc3;
- vertexData[vertexOffset + 3].texIndex = 0;
- vertexData[vertexOffset + 3].texHasColorChannel = 0;
- vertexData[vertexOffset + 3].texIndex2 = 0;
- vertexData[vertexOffset + 3].pad = 0;
- indexData[indexOffset + 0] = static_cast<uint16_t>(vertexOffset + 0);
- indexData[indexOffset + 1] = static_cast<uint16_t>(vertexOffset + 1);
- indexData[indexOffset + 2] = static_cast<uint16_t>(vertexOffset + 2);
- indexData[indexOffset + 3] = static_cast<uint16_t>(vertexOffset + 2);
- indexData[indexOffset + 4] = static_cast<uint16_t>(vertexOffset + 3);
- indexData[indexOffset + 5] = static_cast<uint16_t>(vertexOffset + 0);
- vertexOffset += 4;
- indexOffset += 6;
- ++numQuadsWritten;
- }
- return true;
- };
- CreateQuadsForText(GetDefaultWindowContext()->GetViewport(), x, y, z, str, asciiMultiLine, ctx, AddQuad);
- return numQuadsWritten;
- }
- uint32_t AZ::FFont::GetFontTextureVersion()
- {
- return m_fontImageVersion;
- }
- int AZ::FFont::CreateQuadsForText(const RHI::Viewport& viewport, float x, float y, float z, const char* str, const bool asciiMultiLine, const TextDrawContext& ctx,
- AddFunction AddQuad)
- {
- int numQuads = 0;
- const size_t fxSize = m_effects.size();
- Prepare(str, true, ctx.m_requestSize);
- const size_t fxIdx = ctx.m_fxIdx < fxSize ? ctx.m_fxIdx : 0;
- const FontEffect& fx = m_effects[fxIdx];
- bool passZeroColorOverridden = ctx.IsColorOverridden();
- uint32_t alphaBlend = passZeroColorOverridden ? ctx.m_colorOverride.a : fx.m_passes[0].m_color.a;
- if (alphaBlend > 128)
- {
- ++alphaBlend; // 0..256 for proper blending
- }
- // This is the "logical" size of the font (in pixels). The actual size of
- // the glyphs in the font texture may have additional scaling applied or
- // could have been re-rendered at a different size.
- Vec2 size = ctx.m_size;
- if (ctx.m_sizeIn800x600)
- {
- ScaleCoord(viewport, size.x, size.y);
- }
- // This scaling takes into account the logical size of the font relative
- // to any additional scaling applied (such as from "size ratio").
- const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
- Vec2 baseXY = Vec2(x, y); // in pixels
- if (ctx.m_sizeIn800x600)
- {
- ScaleCoord(viewport, baseXY.x, baseXY.y);
- }
- // snap for pixel perfect rendering (better quality for text)
- if (ctx.m_pixelAligned)
- {
- baseXY.x = floor(baseXY.x);
- baseXY.y = floor(baseXY.y);
- // for smaller fonts (half res or less) it's better to average multiple pixels (we don't miss lines)
- if (scaleInfo.scale.x < 0.9f)
- {
- baseXY.x += 0.5f; // try to average two columns (for exact half res)
- }
- if (scaleInfo.scale.y < 0.9f)
- {
- baseXY.y += 0.25f; // hand tweaked value to get a good result with tiny font (640x480 underscore in console)
- }
- }
- for (size_t j = 0, numPasses = fx.m_passes.size(); j < numPasses; ++j)
- {
- size_t i = numPasses - j - 1;
- const FontRenderingPass* pass = &fx.m_passes[i];
- if (!i)
- {
- alphaBlend = 256;
- }
- const ColorB& passColor = !i && passZeroColorOverridden ? ctx.m_colorOverride : fx.m_passes[i].m_color;
- // gather pass data
- Vec2 offset = pass->m_posOffset; // in pixels
- float charX = baseXY.x + offset.x; // in pixels
- float charY = baseXY.y + offset.y; // in pixels
- ColorB color = passColor;
- bool drawFrame = ctx.m_framed && i == numPasses - 1;
- if (drawFrame)
- {
- ColorB tempColor(255, 255, 255, 255);
- uint32_t frameColor = tempColor.pack_argb8888(); //note: this ends up in r,g,b,a order on little-endian machines
- Vec2 textSize = GetTextSizeUInternal(viewport, str, asciiMultiLine, ctx);
- float x0 = baseXY.x - 12;
- float y0 = baseXY.y - 6;
- float x1 = baseXY.x + textSize.x + 12;
- float y1 = baseXY.y + textSize.y + 6;
- bool culled = false;
- if (ctx.m_clippingEnabled)
- {
- float clipX = ctx.m_clipX;
- float clipY = ctx.m_clipY;
- float clipR = ctx.m_clipX + ctx.m_clipWidth;
- float clipB = ctx.m_clipY + ctx.m_clipHeight;
- if ((x0 >= clipR) || (y0 >= clipB) || (x1 < clipX) || (y1 < clipY))
- {
- culled = true;
- }
- x0 = max(clipX, x0);
- y0 = max(clipY, y0);
- x1 = min(clipR, x1);
- y1 = min(clipB, y1);
- }
- if (!culled)
- {
- Vec3 v0(x0, y0, z);
- Vec3 v2(x1, y1, z);
- Vec3 v1(v2.x, v0.y, v0.z);
- Vec3 v3(v0.x, v2.y, v0.z);
- if (ctx.m_drawTextFlags & eDrawText_UseTransform)
- {
- v0 = ctx.m_transform * v0;
- v2 = ctx.m_transform * v2;
- v1 = ctx.m_transform * v1;
- v3 = ctx.m_transform * v3;
- }
- Vec2 gradientUvMin, gradientUvMax;
- GetGradientTextureCoord(gradientUvMin.x, gradientUvMin.y, gradientUvMax.x, gradientUvMax.y);
- // define the frame quad
- Vec2 uv(gradientUvMin.x, gradientUvMax.y);
- if (AddQuad(v0, v1, v2, v3, uv, uv, uv, uv, frameColor))
- {
- ++numQuads;
- }
- else
- {
- return numQuads;
- }
- }
- }
- AZStd::wstring strW;
- AZStd::to_wstring(strW, str);
- // parse the string, ignoring control characters
- uint32_t nextCh = 0;
- const wchar_t* pChar = strW.c_str();
- while (uint32_t ch = *pChar)
- {
- ++pChar;
- nextCh = *pChar;
- switch (ch)
- {
- case '\\':
- {
- if (*pChar != 'n' || !asciiMultiLine)
- {
- break;
- }
- ++pChar;
- }
- case '\n':
- {
- charX = baseXY.x + offset.x;
- charY += size.y * (1.f + ctx.GetLineSpacing());
- continue;
- }
- break;
- case '\r':
- {
- charX = baseXY.x + offset.x;
- continue;
- }
- break;
- case '\t':
- {
- if (ctx.m_proportional)
- {
- charX += TabCharCount * size.x * AZ_FONT_SPACE_SIZE;
- }
- else
- {
- charX += TabCharCount * size.x * ctx.m_widthScale;
- }
- continue;
- }
- break;
- case '$':
- {
- if (ctx.m_processSpecialChars)
- {
- if (*pChar == '$')
- {
- ++pChar;
- }
- else if (isdigit(*pChar))
- {
- if (!i)
- {
- static const AZ::Color ColorTable[10] =
- {
- AZ::Colors::Black,
- AZ::Colors::White,
- AZ::Colors::Blue,
- AZ::Colors::Lime,
- AZ::Colors::Red,
- AZ::Colors::Cyan,
- AZ::Colors::Yellow,
- AZ::Colors::Fuchsia,
- AZ::Colors::Orange,
- AZ::Colors::Grey,
- };
- int colorIndex = (*pChar) - '0';
- ColorB newColor = AZColorToLYColorB(ColorTable[colorIndex]);
- color.r = newColor.r;
- color.g = newColor.g;
- color.b = newColor.b;
- // Leave alpha at original value!
- }
- ++pChar;
- continue;
- }
- else if (*pChar == 'O' || *pChar == 'o')
- {
- if (!i)
- {
- color = passColor;
- }
- ++pChar;
- continue;
- }
- }
- }
- break;
- default:
- break;
- }
- // get texture coordinates
- float texCoord[4];
- int charOffsetX, charOffsetY; // in font texels
- int charSizeX, charSizeY; // in font texels
- const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
- const AtomFont::GlyphSize requestSize = rerenderGlyphs ? ctx.m_requestSize : AtomFont::defaultGlyphSize;
- m_fontTexture->GetTextureCoord(m_fontTexture->GetCharSlot(ch, requestSize), texCoord, charSizeX, charSizeY, charOffsetX, charOffsetY, requestSize);
- int horizontalAdvance = m_fontTexture->GetHorizontalAdvance(ch, requestSize);
- float advance;
- if (ctx.m_proportional)
- {
- advance = horizontalAdvance * scaleInfo.scale.x;
- }
- else
- {
- advance = size.x * ctx.m_widthScale;
- }
- Vec2 kerningOffset(Vec2_Zero);
- if (ctx.m_kerningEnabled && nextCh)
- {
- kerningOffset = m_fontTexture->GetKerning(ch, nextCh) * scaleInfo.scale.x;
- }
- float trackingOffset = 0.0f;
- if (nextCh)
- {
- trackingOffset = ctx.m_tracking;
- }
- float px = charX + charOffsetX * scaleInfo.scale.x; // in pixels
- float py = charY + charOffsetY * scaleInfo.scale.y; // in pixels
- float pr = px + charSizeX * scaleInfo.scale.x;
- float pb = py + charSizeY * scaleInfo.scale.y;
- // compute clipping
- float newX = px; // in pixels
- float newY = py; // in pixels
- float newR = pr; // in pixels
- float newB = pb; // in pixels
- if (ctx.m_clippingEnabled)
- {
- float clipX = ctx.m_clipX;
- float clipY = ctx.m_clipY;
- float clipR = ctx.m_clipX + ctx.m_clipWidth;
- float clipB = ctx.m_clipY + ctx.m_clipHeight;
- // clip non visible
- if ((px >= clipR) || (py >= clipB) || (pr < clipX) || (pb < clipY))
- {
- charX += advance + kerningOffset.x + trackingOffset;
- continue;
- }
- // clip partially visible
- else
- {
- float width = horizontalAdvance * scaleInfo.rcpCellWidth;
- if ((width <= 0.0f) || (size.y <= 0.0f))
- {
- charX += advance + kerningOffset.x + trackingOffset;
- continue;
- }
- // clip the image to the scissor rect
- newX = max(clipX, px);
- newY = max(clipY, py);
- newR = min(clipR, pr);
- newB = min(clipB, pb);
- float rcpWidth = 1.0f / width;
- float rcpHeight = 1.0f / size.y;
- float texW = texCoord[2] - texCoord[0];
- float texH = texCoord[3] - texCoord[1];
- // clip horizontal
- texCoord[0] = texCoord[0] + texW * (newX - px) * rcpWidth;
- texCoord[2] = texCoord[2] + texW * (newR - pr) * rcpWidth;
- // clip vertical
- texCoord[1] = texCoord[1] + texH * (newY - py) * rcpHeight;
- texCoord[3] = texCoord[3] + texH * (newB - pb) * rcpHeight;
- }
- }
- Vec3 v0(newX, newY, z);
- Vec3 v2(newR, newB, z);
- Vec3 v1(v2.x, v0.y, v0.z);
- Vec3 v3(v0.x, v2.y, v0.z);
- Vec2 tc0(texCoord[0], texCoord[1]);
- Vec2 tc2(texCoord[2], texCoord[3]);
- Vec2 tc1(tc2.x, tc0.y);
- Vec2 tc3(tc0.x, tc2.y);
- uint32_t packedColor = 0xffffffff;
- {
- ColorB tempColor = color;
- tempColor.a = static_cast<uint8_t>(((uint32_t) tempColor.a * alphaBlend) >> 8);
- packedColor = tempColor.pack_argb8888(); //note: this ends up in r,g,b,a order on little-endian machines
- }
- if (ctx.m_drawTextFlags & eDrawText_UseTransform)
- {
- v0 = ctx.m_transform * v0;
- v2 = ctx.m_transform * v2;
- v1 = ctx.m_transform * v1;
- v3 = ctx.m_transform * v3;
- }
- if (AddQuad(v0, v1, v2, v3, tc0, tc1, tc2, tc3, packedColor))
- {
- ++numQuads;
- }
- else
- {
- return numQuads;
- }
- charX += advance + kerningOffset.x + trackingOffset;
- }
- }
- return numQuads;
- }
- AZ::FFont::TextScaleInfoInternal AZ::FFont::CalculateScaleInternal(const RHI::Viewport& viewport, const TextDrawContext& ctx) const
- {
- Vec2 size = GetRestoredFontSize(ctx); // in pixel
- if (ctx.m_sizeIn800x600)
- {
- ScaleCoord(viewport, size.x, size.y);
- }
- float rcpCellWidth;
- Vec2 scale;
- int fontTextureCellWidth = GetFontTexture()->GetCellWidth();
- int fontTextureCellHeight = GetFontTexture()->GetCellHeight();
- if (ctx.m_proportional)
- {
- rcpCellWidth = (1.0f / static_cast<float>(fontTextureCellWidth)) * size.x;
- scale = Vec2(rcpCellWidth * ctx.m_widthScale, size.y / static_cast<float>(fontTextureCellHeight));
- }
- else
- {
- rcpCellWidth = size.x / 16.0f;
- scale = Vec2(rcpCellWidth * ctx.m_widthScale, size.y * ctx.m_widthScale / 16.0f);
- }
- return TextScaleInfoInternal(scale, rcpCellWidth);
- }
- size_t AZ::FFont::GetTextLength(const char* str, const bool asciiMultiLine) const
- {
- size_t len = 0;
- // parse the string, ignoring control characters
- const char* pChar = str;
- while (char ch = *pChar++)
- {
- if ((ch & 0xC0) == 0x80)
- {
- continue; // Skip UTF-8 continuation bytes, we count only the first byte of a code-point
- }
- switch (ch)
- {
- case '\\':
- {
- if (*pChar != 'n' || !asciiMultiLine)
- {
- break;
- }
- ++pChar;
- }
- case '\n':
- case '\r':
- case '\t':
- {
- continue;
- }
- break;
- case '$':
- {
- if (*pChar == '$')
- {
- ++pChar;
- }
- else if (*pChar)
- {
- ++pChar;
- continue;
- }
- }
- break;
- default:
- break;
- }
- ++len;
- }
- return len;
- }
- void AZ::FFont::WrapText(AZStd::string& result, float maxWidth, const char* str, const TextDrawContext& ctx)
- {
- result = str;
- if (ctx.m_sizeIn800x600)
- {
- // ToDo: Update to work with Atom? LYN-3676
- // maxWidth = ???->ScaleCoordX(maxWidth);
- }
- Vec2 strSize = GetTextSize(result.c_str(), true, ctx);
- if (strSize.x <= maxWidth)
- {
- return;
- }
- // Assume a given string has multiple lines of text if it's height is
- // greater than the height of its font.
- const bool multiLine = strSize.y > GetRestoredFontSize(ctx).y;
- int lastSpace = -1;
- const wchar_t* pLastSpace = NULL;
- float lastSpaceWidth = 0.0f;
- float curCharWidth = 0.0f;
- float curLineWidth = 0.0f;
- float biggestLineWidth = 0.0f;
- float widthSum = 0.0f;
- int curChar = 0;
- AZStd::wstring resultW;
- AZStd::to_wstring(resultW, result.c_str());
- const wchar_t* pChar = resultW.c_str();
- while (uint32_t ch = *pChar)
- {
- // Dollar sign escape codes. The following scenarios can happen with dollar signs embedded in a string.
- // The following character is...
- // 1. ... a digit, 'O' or 'o' which indicates a color code. Both characters a skipped in the width calculation.
- // 2. ... another dollar sign. Only 1 dollar sign is skipped in the width calculation.
- // 3. ... anything else. The dollar sign is processed in the width calculation.
- if (ctx.m_processSpecialChars && ch == '$')
- {
- ++pChar;
- char nextChar = static_cast<char>(*pChar);
- if (isdigit(nextChar) || nextChar == 'O' || nextChar == 'o')
- {
- ++pChar;
- continue;
- }
- else if (nextChar != '$')
- {
- --pChar;
- }
- }
- // get char width and sum it to the line width
- // Note: This is not unicode compatible, since char-width depends on surrounding context (ie, combining diacritics etc)
- char codepoint[5];
- AZStd::to_string(codepoint, 5, { (wchar_t*)&ch, 1 });
- curCharWidth = GetTextSize(codepoint, true, ctx).x;
- // keep track of spaces
- // they are good for splitting the string
- if (ch == ' ')
- {
- lastSpace = curChar;
- lastSpaceWidth = curLineWidth + curCharWidth;
- pLastSpace = pChar;
- assert(*pLastSpace == ' ');
- }
- bool prevCharWasNewline = false;
- const bool notFirstChar = pChar != resultW.c_str();
- if (*pChar && notFirstChar)
- {
- const wchar_t* pPrevCharStr = pChar - 1;
- prevCharWasNewline = pPrevCharStr[0] == '\n';
- }
- // if line exceed allowed width, split it
- if (prevCharWasNewline || (curLineWidth + curCharWidth >= maxWidth && (*pChar)))
- {
- if (prevCharWasNewline)
- {
- // Reset the current line width to account for newline
- curLineWidth = curCharWidth;
- widthSum += curLineWidth;
- }
- else if ((lastSpace > 0) && ((curChar - lastSpace) < 16) && (curChar - lastSpace >= 0)) // 16 is the default threshold
- {
- *(char*)pLastSpace = '\n'; // This is safe inside UTF-8 because space is single-byte codepoint
- if (lastSpaceWidth > biggestLineWidth)
- {
- biggestLineWidth = lastSpaceWidth;
- }
- curLineWidth = curLineWidth - lastSpaceWidth + curCharWidth;
- widthSum += curLineWidth;
- }
- else
- {
- const wchar_t* buf = pChar;
- size_t bytesProcessed = buf - resultW.c_str();
- resultW.insert(resultW.begin() + bytesProcessed, L'\n'); // Insert the newline, this invalidates the iterator
- buf = resultW.c_str() + bytesProcessed; // In case reallocation occurs, we ensure we are inside the new buffer
- assert(*buf == '\n');
- pChar = buf; // pChar once again points inside the target string, at the current character
- assert(*pChar == ch);
- ++pChar;
- ++curChar;
- if (curLineWidth > biggestLineWidth)
- {
- biggestLineWidth = curLineWidth;
- }
- widthSum += curLineWidth;
- curLineWidth = curCharWidth;
- }
- // if we don't need any more line breaks, then just stop, but for
- // multiple lines we can't assume that there aren't any more
- // strings to wrap, so continue
- if (strSize.x - widthSum <= maxWidth && !multiLine)
- {
- break;
- }
- lastSpaceWidth = 0;
- lastSpace = 0;
- }
- else
- {
- curLineWidth += curCharWidth;
- }
- ++curChar;
- ++pChar;
- }
- }
- void AZ::FFont::GetGradientTextureCoord(float& minU, float& minV, float& maxU, float& maxV) const
- {
- const TextureSlot* slot = m_fontTexture->GetGradientSlot();
- assert(slot);
- float invWidth = 1.0f / (float) m_fontTexture->GetWidth();
- float invHeight = 1.0f / (float) m_fontTexture->GetHeight();
- // deflate by one pixel to avoid bilinear filtering on the borders
- minU = slot->m_texCoords[0] + invWidth;
- minV = slot->m_texCoords[1] + invHeight;
- maxU = slot->m_texCoords[0] + (slot->m_characterWidth - 1) * invWidth;
- maxV = slot->m_texCoords[1] + (slot->m_characterHeight - 1) * invHeight;
- }
- unsigned int AZ::FFont::GetEffectId(const char* effectName) const
- {
- if (effectName)
- {
- for (size_t i = 0, numEffects = m_effects.size(); i < numEffects; ++i)
- {
- if (!strcmp(m_effects[i].m_name.c_str(), effectName))
- {
- return static_cast<unsigned int>(i);
- }
- }
- }
- return 0;
- }
- unsigned int AZ::FFont::GetNumEffects() const
- {
- return static_cast<unsigned int>(m_effects.size());
- }
- const char* AZ::FFont::GetEffectName(unsigned int effectId) const
- {
- return (effectId < m_effects.size()) ? m_effects[effectId].m_name.c_str() : nullptr;
- }
- Vec2 AZ::FFont::GetMaxEffectOffset(unsigned int effectId) const
- {
- Vec2 maxOffset(0.0f, 0.0f);
- if (effectId < m_effects.size())
- {
- const FontEffect& fx = m_effects[effectId];
- for (size_t i = 0, numPasses = fx.m_passes.size(); i < numPasses; ++i)
- {
- const FontRenderingPass* pass = &fx.m_passes[numPasses - i - 1];
- // gather pass data
- Vec2 offset = pass->m_posOffset;
- if (maxOffset.x < offset.x)
- {
- maxOffset.x = offset.x;
- }
- if (maxOffset.y < offset.y)
- {
- maxOffset.y = offset.y;
- }
- }
- }
- return maxOffset;
- }
- bool AZ::FFont::DoesEffectHaveTransparency(unsigned int effectId) const
- {
- const size_t fxSize = m_effects.size();
- const size_t fxIdx = effectId < fxSize ? effectId : 0;
- const FontEffect& fx = m_effects[fxIdx];
- for (auto& pass : fx.m_passes)
- {
- // if the alpha is not 255 then there is transparency
- if (pass.m_color.a != 255)
- {
- return true;
- }
- }
- return false;
- }
- void AZ::FFont::AddCharsToFontTexture(const char* chars, int glyphSizeX, int glyphSizeY)
- {
- AtomFont::GlyphSize glyphSize(glyphSizeX, glyphSizeY);
- Prepare(chars, false, glyphSize);
- }
- Vec2 AZ::FFont::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph, const TextDrawContext& ctx) const
- {
- return GetKerningInternal(GetDefaultWindowContext()->GetViewport(), leftGlyph, rightGlyph, ctx);
- }
- Vec2 AZ::FFont::GetKerningInternal(const RHI::Viewport& viewport, uint32_t leftGlyph, uint32_t rightGlyph, const TextDrawContext& ctx) const
- {
- const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
- return m_fontTexture->GetKerning(leftGlyph, rightGlyph) * scaleInfo.scale.x;
- }
- float AZ::FFont::GetAscender(const TextDrawContext& ctx) const
- {
- return (ctx.m_size.y * m_fontTexture->GetAscenderToHeightRatio());
- }
- float AZ::FFont::GetBaseline(const TextDrawContext& ctx) const
- {
- return GetBaselineInternal(GetDefaultWindowContext()->GetViewport(), ctx);
- }
- float AZ::FFont::GetBaselineInternal(const RHI::Viewport& viewport, const TextDrawContext& ctx) const
- {
- const TextScaleInfoInternal scaleInfo(CalculateScaleInternal(viewport, ctx));
- // Calculate baseline the same way as the font renderer which uses the glyph height * size ratio.
- // Adding 1 because FontTexture always adds 1 to the char height in GetTextureCoord
- return (round(m_fontTexture->GetCellHeight() * GetSizeRatio()) + 1.0f) * scaleInfo.scale.y;
- }
- bool AZ::FFont::InitTexture()
- {
- using namespace AZ;
- const RHI::Format rhiImageFormat = RHI::Format::R8_UNORM;
- const int width = m_fontTexture->GetWidth();
- const int height = m_fontTexture->GetHeight();
- const Name imageName(m_name.c_str());
- Data::Instance<RPI::AttachmentImagePool> imagePool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
- RHI::ImageDescriptor imageDescriptor = RHI::ImageDescriptor::Create2D(RHI::ImageBindFlags::ShaderRead, width, height, rhiImageFormat);
- m_fontAttachmentImage = RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, imageName);
- m_fontImage = m_fontAttachmentImage->GetRHIImage();
- m_fontImage->SetName(imageName);
- m_fontImageVersion = 0;
- return true;
- }
- bool AZ::FFont::UpdateTexture()
- {
- using namespace AZ;
- if (!m_fontImage)
- {
- return false;
- }
- 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))
- {
- AZ_Assert(false, "AtomFont::FFont:::UpdateTexture size mismatch between texture and image!");
- return false;
- }
- RHI::ImageSubresourceRange range;
- range.m_mipSliceMin = 0;
- range.m_mipSliceMax = 0;
- range.m_arraySliceMin = 0;
- range.m_arraySliceMax = 0;
- RHI::ImageSubresourceLayout layout;
- m_fontImage->GetSubresourceLayouts(range, &layout, nullptr);
- RHI::ImageUpdateRequest imageUpdateReq;
- imageUpdateReq.m_image = m_fontImage.get();
- imageUpdateReq.m_imageSubresource = RHI::ImageSubresource{ 0, 0 };
- imageUpdateReq.m_sourceData = m_fontTexture->GetBuffer();
- imageUpdateReq.m_sourceSubresourceLayout = layout;
- const RHI::ResultCode result = m_fontAttachmentImage->UpdateImageContents(imageUpdateReq);
- return result == RHI::ResultCode::Success;
- }
- bool AZ::FFont::InitCache()
- {
- m_fontTexture->CreateGradientSlot();
- // precache (not required but for faster printout later)
- const char first = ' ';
- const char last = '~';
- char buf[last - first + 2];
- char* p = buf;
- // precache all [normal] printable characters to the string (missing ones are updated on demand)
- for (char i = first; i <= last; ++i)
- {
- *p++ = i;
- }
- *p = 0;
- Prepare(buf, false);
- return true;
- }
- AZ::FFont::FontEffect* AZ::FFont::AddEffect(const char* effectName)
- {
- m_effects.push_back(FontEffect(effectName));
- return &m_effects[m_effects.size() - 1];
- }
- AZ::FFont::FontEffect* AZ::FFont::GetDefaultEffect()
- {
- return &m_effects[0];
- }
- void AZ::FFont::Prepare(const char* str, bool updateTexture, const AtomFont::GlyphSize& glyphSize)
- {
- const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender;
- const AtomFont::GlyphSize usedGlyphSize = rerenderGlyphs ? glyphSize : AtomFont::defaultGlyphSize;
- bool texUpdateNeeded = m_fontTexture->PreCacheString(str, nullptr, m_sizeRatio, usedGlyphSize, m_fontHintParams) == 1 || m_fontTexDirty;
- if (updateTexture && texUpdateNeeded && m_fontImage)
- {
- UpdateTexture();
- m_fontTexDirty = false;
- ++m_fontImageVersion;
- // Let any listeners know that the font texture has changed
- // TODO Update to an AZ::Event when Cry use of this bus is cleaned out.
- FontNotificationBus::Broadcast(&FontNotificationBus::Events::OnFontTextureUpdated, this);
- }
- else
- {
- m_fontTexDirty = texUpdateNeeded;
- }
- }
- Vec2 AZ::FFont::GetRestoredFontSize(const TextDrawContext& ctx) const
- {
- // Calculate the scale that we need to apply to the text size to ensure
- // it's on-screen size is the same regardless of the slot scaling needed
- // to fit the glyphs of the font within the font texture slots.
- float restoringScale = IFFontConstants::defaultSizeRatio / m_sizeRatio;
- return Vec2(ctx.m_size.x * restoringScale, ctx.m_size.y * restoringScale);
- }
- void AZ::FFont::ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) const
- {
- float width = viewport.m_maxX - viewport.m_minX;
- float height = viewport.m_maxY - viewport.m_minY;
- x *= width / WindowScaleWidth;
- y *= height / WindowScaleHeight;
- }
- static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::TextDrawParameters& params)
- {
- if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)
- {
- ctx.m_drawTextFlags |= eDrawText_Center;
- }
- if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Right)
- {
- ctx.m_drawTextFlags |= eDrawText_Right;
- }
-
- if (params.m_vAlign == AzFramework::TextVerticalAlignment::Center)
- {
- ctx.m_drawTextFlags |= eDrawText_CenterV;
- }
-
- if (params.m_vAlign == AzFramework::TextVerticalAlignment::Bottom)
- {
- ctx.m_drawTextFlags |= eDrawText_Bottom;
- }
-
- if (params.m_monospace)
- {
- ctx.m_drawTextFlags |= eDrawText_Monospace;
- }
-
- if (params.m_depthTest)
- {
- ctx.m_drawTextFlags |= eDrawText_DepthTest;
- }
-
- if (params.m_virtual800x600ScreenSize)
- {
- ctx.m_drawTextFlags |= eDrawText_800x600;
- }
-
- if (!params.m_scaleWithWindow)
- {
- ctx.m_drawTextFlags |= eDrawText_FixedSize;
- }
- if (params.m_useTransform)
- {
- ctx.m_drawTextFlags |= eDrawText_UseTransform;
- ctx.SetTransform(AZMatrix3x4ToLYMatrix3x4(params.m_transform));
- }
- }
- AZ::FFont::DrawParameters AZ::FFont::ExtractDrawParameters(const AzFramework::TextDrawParameters& params, AZStd::string_view text, bool forceCalculateSize)
- {
- DrawParameters internalParams;
- if (params.m_drawViewportId == AzFramework::InvalidViewportId ||
- text.empty())
- {
- return internalParams;
- }
- float posX = params.m_position.GetX();
- float posY = params.m_position.GetY();
- internalParams.m_viewportContext = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get()->GetViewportContextById(params.m_drawViewportId);
- const AZ::RHI::Viewport& viewport = internalParams.m_viewportContext->GetWindowContext()->GetViewport();
- internalParams.m_viewport = &viewport;
- if (params.m_virtual800x600ScreenSize)
- {
- posX *= WindowScaleWidth / (viewport.m_maxX - viewport.m_minX);
- posY *= WindowScaleHeight / (viewport.m_maxY - viewport.m_minY);
- }
- internalParams.m_ctx.SetBaseState(GS_NODEPTHTEST);
- internalParams.m_ctx.SetColor(AZColorToLYColorF(params.m_color));
- internalParams.m_ctx.SetEffect(params.m_effectIndex);
- internalParams.m_ctx.SetCharWidthScale((params.m_monospace || params.m_scaleWithWindow) ? 0.5f : 1.0f);
- internalParams.m_ctx.EnableFrame(false);
- internalParams.m_ctx.SetProportional(!params.m_monospace && params.m_scaleWithWindow);
- internalParams.m_ctx.SetSizeIn800x600(params.m_scaleWithWindow && params.m_virtual800x600ScreenSize);
- internalParams.m_ctx.SetSize(AZVec2ToLYVec2(
- AZ::Vector2(params.m_textSizeFactor, params.m_textSizeFactor) * params.m_scale *
- internalParams.m_viewportContext->GetDpiScalingFactor()));
- internalParams.m_ctx.SetLineSpacing(params.m_lineSpacing);
- if (params.m_hAlign != AzFramework::TextHorizontalAlignment::Left ||
- params.m_vAlign != AzFramework::TextVerticalAlignment::Top ||
- forceCalculateSize)
- {
- // We align based on the size of the default font effect because we do not want the
- // text to move when the font effect is changed
- unsigned int effectIndex = internalParams.m_ctx.m_fxIdx;
- internalParams.m_ctx.SetEffect(0);
- Vec2 textSize = GetTextSizeUInternal(viewport, text.data(), params.m_multiline, internalParams.m_ctx);
- internalParams.m_ctx.SetEffect(effectIndex);
-
- // If we're using virtual 800x600 coordinates, convert the text size from
- // pixels to that before using it as an offset.
- if (internalParams.m_ctx.m_sizeIn800x600)
- {
- float width = 1.0f;
- float height = 1.0f;
- ScaleCoord(viewport, width, height);
- textSize.x /= width;
- textSize.y /= height;
- }
- if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)
- {
- posX -= textSize.x * 0.5f;
- }
- else if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Right)
- {
- posX -= textSize.x;
- }
- if (params.m_vAlign == AzFramework::TextVerticalAlignment::Center)
- {
- posY -= textSize.y * 0.5f;
- }
- else if (params.m_vAlign == AzFramework::TextVerticalAlignment::Bottom)
- {
- posY -= textSize.y;
- }
- internalParams.m_size = AZ::Vector2{textSize.x, textSize.y};
- }
- SetCommonContextFlags(internalParams.m_ctx, params);
- internalParams.m_ctx.m_drawTextFlags |= eDrawText_2D;
- internalParams.m_position = AZ::Vector2{posX, posY};
- return internalParams;
- }
- void AZ::FFont::DrawScreenAlignedText2d(
- const AzFramework::TextDrawParameters& params,
- AZStd::string_view text)
- {
- DrawParameters internalParams = ExtractDrawParameters(params, text, false);
- if (!internalParams.m_viewportContext)
- {
- return;
- }
- DrawStringUInternal(
- *internalParams.m_viewport,
- internalParams.m_viewportContext,
- internalParams.m_position.GetX(),
- internalParams.m_position.GetY(),
- params.m_position.GetZ(), // Z
- text.data(),
- params.m_multiline,
- internalParams.m_ctx
- );
- }
- void AZ::FFont::DrawScreenAlignedText3d(
- const AzFramework::TextDrawParameters& params,
- AZStd::string_view text)
- {
- DrawParameters internalParams = ExtractDrawParameters(params, text, false);
- if (!internalParams.m_viewportContext)
- {
- return;
- }
- AZ::RPI::ViewPtr currentView = internalParams.m_viewportContext->GetDefaultView();
- if (!currentView)
- {
- return;
- }
- const AZ::Vector3 positionNdc = AzFramework::WorldToScreenNdc(
- params.m_position, currentView->GetWorldToViewMatrixAsMatrix3x4(), currentView->GetViewToClipMatrix());
- // Text behind the camera shouldn't get rendered. WorldToScreenNdc returns values in the range 0 - 1, so Z < 0.5 is behind the screen
- // and >= 0.5 is in front of the screen.
- if (positionNdc.GetZ() < 0.5f)
- {
- return;
- }
- internalParams.m_ctx.m_sizeIn800x600 = false;
- DrawStringUInternal(
- *internalParams.m_viewport,
- internalParams.m_viewportContext,
- positionNdc.GetX() * internalParams.m_viewport->GetWidth() + internalParams.m_position.GetX(),
- (1.0f - positionNdc.GetY()) * internalParams.m_viewport->GetHeight() + internalParams.m_position.GetY(),
- positionNdc.GetZ(), // Z
- text.data(),
- params.m_multiline,
- internalParams.m_ctx
- );
- }
- AZ::Vector2 AZ::FFont::GetTextSize(const AzFramework::TextDrawParameters& params, AZStd::string_view text)
- {
- DrawParameters sizeParams = ExtractDrawParameters(params, text, true);
- return sizeParams.m_size;
- }
- #endif //USE_NULLFONT_ALWAYS
|