447832dd81
* Updated the GameApplication to mount the engine.pak This allows loading the autoexec.cfg and bootstrap.game.<config>.<platform>.setreg from the engine.pak files The engine.pak is searched for in the following order: <ExecutableDirectory>/engine.pak, followed by <ProjectCacheRoot>/engine.pak Removed a lot of unused APIs from the AZ::IO::Archive feature suite Updated many of the AZ::IO::Archive classes to use AZ::IO::Path internally. The logic to search for files within an Archive has been updated to use AZ::IO::Path and to remove case-insensitve string comparisons Somehow removed the CryFile dependency on anything Cry Updated the Settings Registry to support reading from the FileIOBase and therefore Archive files in the GameLauncher via the `SetUseFileIO` function Removed AzFramework Dependency on md5 3rdParty library Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Linux build fix Added an include of <stdio.h> before the <rapidxml/rapidxml.h> include as it usesnprintf. Added `static` to the constexpr constants in ExtractFileDescription in SettingsRegistryImpl.cpp to fix clang compile issue Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the case used to mount the Engine PAK file in the GameApplication to be Engine.pak to match the other locations where it is mounted Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the proper FFont call to FileIOBase::Size to supply the correct integer type of AZ::u64 instead of size_t This fixes building on platforms where size_t is type defined to be unsigned long Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed segmentation fault in Archive::Unregister when outputing the filename of the Archive file being closed Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fix calls to OpenPack in the Legacy LevelSystem The LevelSystem was calling the incorrect overload of OpenPack that accepts BindRoot for the mounted level.pak instead of the overload that that passes a memory block object. This was causing the level pak files to be mounted using an invalid directory, causing file accesses inside the level pak to fail. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the error messages in the ZipDir CacheFactory class to use AZ_Warning directly Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the ArchiveFileIO m_trackedFiles container to store mapped type as an AZ::IO::Path Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
1764 lines
56 KiB
C++
1764 lines
56 KiB
C++
/*
|
|
* 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/Image/StreamingImage.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/StreamingImagePool.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_fontStreamingImage->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;
|
|
}
|
|
|
|
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':
|
|
{
|
|
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;
|
|
|
|
RHI::Format rhiImageFormat = RHI::Format::R8_UNORM;
|
|
int width = m_fontTexture->GetWidth();
|
|
int height = m_fontTexture->GetHeight();
|
|
uint8_t* fontImageData = m_fontTexture->GetBuffer();
|
|
uint32_t fontImageDataSize = RHI::GetFormatSize(rhiImageFormat) * width * height;
|
|
|
|
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
|
|
m_fontStreamingImage = RPI::StreamingImage::CreateFromCpuData(
|
|
*streamingImagePool.get(),
|
|
RHI::ImageDimension::Image2D,
|
|
RHI::Size(width, height, 1),
|
|
rhiImageFormat,
|
|
fontImageData,
|
|
fontImageDataSize);
|
|
|
|
m_fontImage = m_fontStreamingImage->GetRHIImage();
|
|
m_fontImage->SetName(Name(m_name.c_str()));
|
|
|
|
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::ImageSubresourceLayoutPlaced 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;
|
|
|
|
m_fontStreamingImage->UpdateImageContents(imageUpdateReq);
|
|
|
|
return true;
|
|
}
|
|
|
|
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.
|
|
EBUS_EVENT(FontNotificationBus, 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} * internalParams.m_viewportContext->GetDpiScalingFactor();
|
|
}
|
|
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;
|
|
}
|
|
AZ::Vector3 positionNDC = AzFramework::WorldToScreenNDC(
|
|
params.m_position,
|
|
currentView->GetWorldToViewMatrix(),
|
|
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(),
|
|
(1.0f - positionNDC.GetY()) * internalParams.m_viewport->GetHeight(),
|
|
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
|
|
|