Files
GTASource/rage/scaleform/Src/GFxPlayer/GFxFontGlyphPacker.cpp

477 lines
17 KiB
C++
Raw Normal View History

2025-02-23 17:40:52 +08:00
/**********************************************************************
Filename : GFxFontGlyphPacker.cpp
Content : GFxFontGlyphPacker implementation
Created : 6/14/2007
Authors : Maxim Shemanarev, Artyom Bolgar
Copyright : (c) 2001-2007 Scaleform Corp. All Rights Reserved.
Notes :
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
**********************************************************************/
#include "GFxFontGlyphPacker.h"
#ifndef GFC_NO_FONT_GLYPH_PACKER
// The font glyphs are cached into textures; these textures are rendered
// in software using shape rasterization with anti-aliasing. A packing
// algorithm is used to pack individual glyph images into textures.
//------------------------------------------------------------------------
GFxFontGlyphPacker::GFxFontGlyphPacker(GFxFontPackParams* params,
GFxImageCreator *pimageCreator,
GFxRenderConfig *prenderConfig,
GFxLog* plog,
GFxResourceId* ptextureIdGen,
GMemoryHeap* fontHeap,
bool threadedLoading):
pFontPackParams(params),
pTextureIdGen(ptextureIdGen),
pImageCreator(pimageCreator),
pRenderConfig(prenderConfig),
pLog(plog),
pFontHeap(fontHeap),
ThreadedLoading(threadedLoading)
{
if (pFontPackParams)
pFontPackParams->GetTextureConfig(&PackTextureConfig);
Packer.SetWidth (PackTextureConfig.TextureWidth);
Packer.SetHeight(PackTextureConfig.TextureHeight);
}
//------------------------------------------------------------------------
GFxFontGlyphPacker::~GFxFontGlyphPacker()
{
}
//------------------------------------------------------------------------
void GFxFontGlyphPacker::generateGlyphInfo(GArray<GlyphInfo>* glyphs,
GFxFontResource* font)
{
GASSERT(glyphs != 0 && font != 0);
if (font->GetGlyphShapeCount() == 0)
return;
// The font must not have texture glyph data yet; if it does, as might happen
// for loaded pre-generated textures, it is filtered out by our caller function.
GASSERT(font->GetTextureGlyphData() == 0);
GPtr<GFxTextureGlyphData> ptextureGlyphData =
*GHEAP_NEW(pFontHeap) GFxTextureGlyphData(font->GetGlyphShapeCount());
ptextureGlyphData->SetTextureConfig(PackTextureConfig);
font->SetTextureGlyphData(ptextureGlyphData);
Float glyphToPix = PackTextureConfig.NominalSize / GFxFontPackParams::GlyphBoundBox;
UInt i;
UInt n = font->GetGlyphShapeCount();
for (i = 0; i < n; i++)
{
const GFxTextureGlyph& tg = ptextureGlyphData->GetTextureGlyph(i);
// If image has not yet been initialized
if (!tg.HasImageResource())
{
GPtr<GFxShapeBase> sh = *font->GetGlyphShape(i, 0);
if (sh)
{
GRectF glyphBounds;
sh->ComputeBound(&glyphBounds);
if (glyphBounds.Width() > 0 && glyphBounds.Height() > 0)
{
// Add a glyph.
GlyphInfo gi;
gi.Bounds.Left = glyphBounds.Left * glyphToPix - PackTextureConfig.PadPixels;
gi.Bounds.Top = glyphBounds.Top * glyphToPix - PackTextureConfig.PadPixels;
gi.Bounds.Right = glyphBounds.Right * glyphToPix + PackTextureConfig.PadPixels;
gi.Bounds.Bottom = glyphBounds.Bottom * glyphToPix + PackTextureConfig.PadPixels;
gi.Origin.x = 0;
gi.Origin.y = 0;
if (gi.Bounds.Width() > 0 && gi.Bounds.Height() > 0)
{
gi.pFont = font;
gi.GlyphIndex = i;
gi.GlyphReuse = ~0U;
gi.TextureIdx = ~0U;
// Try to reuse the glyph
GlyphGeometryKey key(font, sh, sh->ComputeGeometryHash());
const UInt* val = GlyphGeometryHash.Get(key);
if (val)
{
gi.GlyphReuse = *val;
}
else
{
UInt glyphIdx = (UInt)glyphs->GetSize();
GlyphGeometryHash.Add(key, glyphIdx);
}
glyphs->PushBack(gi);
}
}
}
}
}
}
//------------------------------------------------------------------------
UInt GFxFontGlyphPacker::packGlyphRects(GArray<GlyphInfo>* glyphs,
UInt start, UInt end, UInt texIdx)
{
Packer.Clear();
UInt i, j, w, h;
for (i = start; i < end; i++)
{
const GlyphInfo& gi = (*glyphs)[i];
if(gi.GlyphReuse == ~0U)
{
w = int(ceilf(gi.Bounds.Right) - floorf(gi.Bounds.Left));
h = int(ceilf(gi.Bounds.Bottom) - floorf(gi.Bounds.Top));
Packer.AddRect(w, h, i);
}
}
Packer.Pack();
for (i = 0; i < Packer.GetNumPacks(); i++)
{
const GRectPacker::PackType& pack = Packer.GetPack(i);
for(j = 0; j < pack.NumRects; j++)
{
const GRectPacker::RectType& rect = Packer.GetRect(pack, j);
GlyphInfo& gi = (*glyphs)[rect.Id];
w = UInt(ceilf(gi.Bounds.Right) - floorf(gi.Bounds.Left));
h = UInt(ceilf(gi.Bounds.Bottom) - floorf(gi.Bounds.Top));
gi.Origin.x = Float(rect.x) - gi.Bounds.Left;
gi.Origin.y = Float(rect.y) - gi.Bounds.Top;
gi.Bounds.Left = Float(rect.x);
gi.Bounds.Top = Float(rect.y);
gi.Bounds.Right = Float(rect.x + w);
gi.Bounds.Bottom = Float(rect.y + h);
gi.TextureIdx = texIdx + i;
}
}
return (UInt)(texIdx + Packer.GetNumPacks());
}
//------------------------------------------------------------------------
UInt GFxFontGlyphPacker::packGlyphRects(GArray<GlyphInfo>* glyphs)
{
UInt numTextures = 0;
if(pFontPackParams->GetUseSeparateTextures())
{
UInt i;
UInt start = 0;
for (i = 1; i < glyphs->GetSize(); i++)
{
if ((*glyphs)[i-1].pFont != (*glyphs)[i].pFont)
{
numTextures = packGlyphRects(glyphs, start, i, numTextures);
start = i;
}
}
numTextures = packGlyphRects(glyphs, start, (UInt)glyphs->GetSize(), numTextures);
}
else
{
numTextures = packGlyphRects(glyphs, 0, (UInt)glyphs->GetSize(), 0);
}
return numTextures;
}
//------------------------------------------------------------------------
void GFxFontGlyphPacker::rasterizeGlyph(GImage* texImage, GlyphInfo* gi)
{
GPtr<GFxShapeBase> sh = *gi->pFont->GetGlyphShape(gi->GlyphIndex, 0);
if (sh)
{
sh->MakeCompoundShape(&CompoundShape,
GFxFontPackParams::GlyphBoundBox / PackTextureConfig.NominalSize * 0.5f);
Rasterizer.Clear();
Rasterizer.AddShape(CompoundShape,
PackTextureConfig.NominalSize /
GFxFontPackParams::GlyphBoundBox);
if(Rasterizer.SortCells()) // If there is anything to sweep...
{
// Sweep the raster writing the scan lines to the image.
UInt h = Rasterizer.GetMaxY() - Rasterizer.GetMinY() + 1;
int x = int(floorf(gi->Bounds.Left)) + PackTextureConfig.PadPixels;
int y = int(floorf(gi->Bounds.Top)) + PackTextureConfig.PadPixels;
for (UInt i = 0; i < h; i++)
{
Rasterizer.SweepScanline(i, texImage->GetScanline(y+i) + x);
}
}
}
}
//------------------------------------------------------------------------
void GFxFontGlyphPacker::generateTextures(GArray<GlyphInfo>* glyphs, UInt numTextures)
{
UInt i, j;
for (i = 0; i < numTextures; i++)
{
UInt maxWidth = 0;
UInt maxHeight = 0;
for(j = 0; j < glyphs->GetSize(); j++)
{
const GlyphInfo& gi = (*glyphs)[j];
if (gi.TextureIdx == i)
{
if (int(ceilf(gi.Bounds.Right)) > (int)maxWidth)
maxWidth = int(ceilf(gi.Bounds.Right));
if (int(ceilf(gi.Bounds.Bottom)) > (int)maxHeight)
maxHeight = int(ceilf(gi.Bounds.Bottom));
}
}
UInt texWidth = PackTextureConfig.TextureWidth;
UInt texHeight = PackTextureConfig.TextureHeight;
if (maxWidth <= (UInt)PackTextureConfig.TextureWidth / 2)
{
texWidth = 1;
while (texWidth < maxWidth) texWidth <<= 1;
}
if (maxHeight <= (UInt)PackTextureConfig.TextureHeight / 2)
{
texHeight = 1;
while (texHeight < maxHeight) texHeight <<= 1;
}
// TBD: Future improvements: create GImage in the temp global heap
// in case it is supposed to be destroyed after pImageCreator->CreateImage().
// It may occur when the renderer never loses textures.
GPtr<GImage> texImage =
*GHEAP_NEW(pFontHeap) GImage(GImage::Image_A_8, texWidth, texHeight);
memset(texImage->pData, 0, texWidth * texHeight);
for(j = 0; j < glyphs->GetSize(); j++)
{
GlyphInfo& gi = (*glyphs)[j];
if (gi.TextureIdx == i)
{
rasterizeGlyph(texImage, &gi);
}
}
Float pixToU = 1.0f / texImage->Width;
Float pixToV = 1.0f / texImage->Height;
GFxResourceId textureId = pTextureIdGen->GenerateNextId();
GFxImageCreateInfo icreateInfo(texImage, GFxResource::Use_FontTexture);
icreateInfo.SetStates(0, pRenderConfig, pLog, NULL,NULL);
icreateInfo.ThreadedLoading = ThreadedLoading;
icreateInfo.pHeap = pFontHeap;
GPtr<GImageInfoBase> pimageInfo = *pImageCreator->CreateImage(icreateInfo);
GPtr<GFxImageResource> pimageRes =
*GHEAP_NEW(pFontHeap) GFxImageResource(pimageInfo.GetPtr(),
GFxResource::Use_FontTexture);
for(j = 0; j < glyphs->GetSize(); j++)
{
GlyphInfo gi = (*glyphs)[j];
if (gi.GlyphReuse != ~0U)
{
// If the glyph is reused switch to the original.
const GlyphInfo& gi2 = (*glyphs)[gi.GlyphReuse];
GASSERT(gi.pFont == gi2.pFont); // Font must be the same
gi.TextureIdx = gi2.TextureIdx;
gi.Bounds = gi2.Bounds;
gi.Origin = gi2.Origin;
}
if (gi.TextureIdx == i)
{
GPtr<GFxTextureGlyph> tg = *GHEAP_NEW(pFontHeap) GFxTextureGlyph();
tg->pImage = pimageRes;
tg->UvBounds.Left = gi.Bounds.Left * pixToU;
tg->UvBounds.Top = gi.Bounds.Top * pixToV;
tg->UvBounds.Right = gi.Bounds.Right * pixToU;
tg->UvBounds.Bottom = gi.Bounds.Bottom * pixToV;
tg->UvOrigin.x = gi.Origin.x * pixToU;
tg->UvOrigin.y = gi.Origin.y * pixToV;
GFxTextureGlyphData* tgd = gi.pFont->GetTextureGlyphData();
tg->SetImageResource(pimageRes.GetPtr());
// The texture is only added to font data.
// This means that VisitFonts will need to collect fonts,
// matching them through a hash-table if necessary.
tgd->AddTextureGlyph(gi.GlyphIndex, *tg);
// add textureId -> texture assoc into the font
tgd->AddTexture(textureId, pimageRes);
}
}
}
}
//#include <time.h>
//------------------------------------------------------------------------
void GFxFontGlyphPacker::GenerateFontBitmaps(const GArray<GFxFontResource*>& fonts)
{
//clock_t cl = clock();
UInt i;
UInt totalNumGlyphs = 0;
for (i = 0; i < fonts.GetSize(); i++)
{
if (fonts[i]->GetTextureGlyphData() != 0)
continue;
if (pFontPackParams->GetGlyphCountLimit() &&
(int)fonts[i]->GetGlyphShapeCount() > pFontPackParams->GetGlyphCountLimit())
continue;
totalNumGlyphs += fonts[i]->GetGlyphShapeCount();
}
GArray<GlyphInfo> glyphInfo;
glyphInfo.Reserve(totalNumGlyphs);
GlyphGeometryHash.Clear();
for (i = 0; i < fonts.GetSize(); i++)
{
if (fonts[i]->GetTextureGlyphData() != 0)
continue;
if (pFontPackParams->GetGlyphCountLimit() &&
(int)fonts[i]->GetGlyphShapeCount() > pFontPackParams->GetGlyphCountLimit())
continue;
generateGlyphInfo(&glyphInfo, fonts[i]);
}
UInt numTextures = packGlyphRects(&glyphInfo);
generateTextures(&glyphInfo, numTextures);
//printf("%d %f\n", glyphInfo.GetSize(), double(clock() - cl) / CLOCKS_PER_SEC);
}
#endif //GFC_NO_FONT_GLYPH_PACKER
///////////////////////////////////////////////////////////
//
// ***** GFxFontPackParams
//
// Size (in TWIPS) of the box that the glyph should stay within.
// this *should* be 1024, but some glyphs in some fonts exceed it!
//------------------------------------------------------------------------
const Float GFxFontPackParams::GlyphBoundBox = 1536.0f;
//------------------------------------------------------------------------
void GFxFontPackParams::SetTextureConfig(const TextureConfig& config)
{
PackTextureConfig = config;
const int minSize = 4;
const int maxSize = PackTextureConfig.TextureHeight / 2;
if (PackTextureConfig.NominalSize < minSize)
{
GFC_DEBUG_WARNING2(1, "SetTextureConfig - NominalSize (%d) too small, clamping to %d\n",
PackTextureConfig.NominalSize, minSize);
PackTextureConfig.NominalSize = minSize;
}
else if (PackTextureConfig.NominalSize > maxSize)
{
GFC_DEBUG_WARNING2(1, "SetTextureConfig - NominalSize (%d) too large, clamping to %d\n",
PackTextureConfig.NominalSize, maxSize);
PackTextureConfig.NominalSize = maxSize;
}
if (config.PadPixels < 1)
{
GFC_DEBUG_WARNING2(1, "SetTextureConfig - PadPixels (%d) too small, clamping to %d\n",
PackTextureConfig.PadPixels, 1);
PackTextureConfig.PadPixels = minSize;
}
}
//------------------------------------------------------------------------
#ifdef GFC_ASSERT_ON_FONT_BITMAP_GEN
#define GASSERT_ON_FONT_BITMAP_GEN GASSERT(0)
#else
#define GASSERT_ON_FONT_BITMAP_GEN ((void)0)
#endif //GFC_ASSERT_ON_FONT_BITMAP_GEN
// Build cached textures from glyph outlines.
//------------------------------------------------------------------------
void GFx_GenerateFontBitmaps(GFxFontPackParams *params,
const GArray<GFxFontResource*>& fonts,
GFxImageCreator *pimageCreator,
GFxRenderConfig *prenderConfig,
GFxLog* plog,
GFxResourceId* pidGenerator,
GMemoryHeap* fontHeap,
bool threadedLoading)
{
if (!params)
{
// User should not be calling us with no font params. Null params are checked
// by caller during movieDef binding, ensuring that correct warning can be issued
// in case when there is also no dynamic cache.
return;
}
if (!pimageCreator)
{
GFC_DEBUG_WARNING(1, "Bitmap font texture gen failed - GFxImageCreator not installed");
return;
}
GASSERT(pidGenerator != 0);
//if (powner->IsRenderingFonts())
{
GASSERT_ON_FONT_BITMAP_GEN;
#ifndef GFC_NO_FONT_GLYPH_PACKER
GPtr<GFxFontGlyphPacker> packer =
*GNEW GFxFontGlyphPacker(params, pimageCreator, prenderConfig,
plog, pidGenerator, fontHeap, threadedLoading);
packer->GenerateFontBitmaps(fonts);
#else
GUNUSED4(fonts, prenderConfig, plog, pidGenerator);
GUNUSED2(threadedLoading, fontHeap);
#endif//GFC_NO_FONT_GLYPH_PACKER
}
}
//------------------------------------------------------------------------
Float GFxFontPackParams::GetDrawGlyphScale(int nominalGlyphHeight)
{
// Scale from uv coords to the 1024x1024 glyph square.
// This equation used to be in GFxFontLibImpl::DrawGlyph
return PackTextureConfig.TextureHeight * GFxFontPackParams::GlyphBoundBox / nominalGlyphHeight;
}
//------------------------------------------------------------------------
Float GFxFontPackParams::GetTextureGlyphMaxHeight(const GFxFontResource* f)
{
TextureConfig conf;
f->GetTextureConfig(&conf);
return 1024.0f / GFxFontPackParams::GlyphBoundBox * conf.NominalSize;
}