477 lines
17 KiB
C++
477 lines
17 KiB
C++
/**********************************************************************
|
|
|
|
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;
|
|
}
|
|
|