1650 lines
56 KiB
C++
1650 lines
56 KiB
C++
/**********************************************************************
|
|
|
|
Filename : GFxFontCacheManager.cpp
|
|
Content :
|
|
Created :
|
|
Authors : Maxim Shemanarev, Artem Bolgar
|
|
|
|
Copyright : (c) 2001-2010 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 "GFxFontCacheManager.h"
|
|
#include "AMP/GFxAmpViewStats.h"
|
|
#include "GFxTextureFont.h"
|
|
|
|
//------------------------------------------------------------------------
|
|
GFxFontCacheManager::GFxFontCacheManager(bool enableDynamicCache, bool debugHeap):
|
|
GFxState(State_FontCacheManager),
|
|
MaxRasterScale(1),
|
|
MaxVectorCacheSize(512),
|
|
NumLockedFrames(1),
|
|
pCache(0)
|
|
{
|
|
for (unsigned i=0; i<NUMBER_OF_RENDER_THREADS; ++i)
|
|
{
|
|
DynamicCacheEnabled[i] = enableDynamicCache;
|
|
Text3DVectorizationEnabled[i] = false;
|
|
FauxItalicAngle[i] = 0.28f;
|
|
FauxBoldRatio[i] = 0.055f;
|
|
OutlineRatio[i] = 0.01f;
|
|
}
|
|
InitManager(TextureConfig(), debugHeap);
|
|
}
|
|
|
|
GFxFontCacheManager::GFxFontCacheManager(const TextureConfig& config,
|
|
bool enableDynamicCache, bool debugHeap):
|
|
GFxState(State_FontCacheManager),
|
|
MaxRasterScale(1),
|
|
MaxVectorCacheSize(512),
|
|
NumLockedFrames(1),
|
|
pCache(0)
|
|
{
|
|
for (unsigned i=0; i<NUMBER_OF_RENDER_THREADS; ++i)
|
|
{
|
|
DynamicCacheEnabled[i] = enableDynamicCache;
|
|
Text3DVectorizationEnabled[i] = false;
|
|
FauxItalicAngle[i] = 0.3f;
|
|
FauxBoldRatio[i] = 0.055f;
|
|
OutlineRatio[i] = 0.01f;
|
|
}
|
|
InitManager(config, debugHeap);
|
|
}
|
|
|
|
GFxFontCacheManager::~GFxFontCacheManager()
|
|
{
|
|
delete pCache;
|
|
}
|
|
|
|
void GFxFontCacheManager::InitManager(const TextureConfig& config, bool debugHeap)
|
|
{
|
|
// Create a heap for the font manager and tie its lifetime to pCache.
|
|
UInt heapFlags = debugHeap ? GMemoryHeap::Heap_UserDebug : 0;
|
|
GMemoryHeap::HeapDesc desc(heapFlags);
|
|
desc.HeapId = GHeapId_FontCache;
|
|
GMemoryHeap* pheap = GMemory::GetGlobalHeap()->CreateHeap("_Font_Cache", desc);
|
|
pCache = GHEAP_NEW(pheap) GFxFontCacheManagerImpl(pheap);
|
|
pheap->ReleaseOnFree(pCache);
|
|
|
|
SetTextureConfig(config);
|
|
}
|
|
|
|
GMemoryHeap* GFxFontCacheManager::GetHeap() const
|
|
{
|
|
return pCache->GetHeap();
|
|
}
|
|
|
|
|
|
void GFxFontCacheManager::SetTextureConfig(const TextureConfig& config)
|
|
{
|
|
CacheTextureConfig = config;
|
|
pCache->Init(config);
|
|
pCache->SetMaxVectorCacheSize(MaxVectorCacheSize);
|
|
pCache->SetFauxItalicAngleAllThreads(FauxItalicAngle[0]);
|
|
pCache->SetFauxBoldRatioAllThreads(FauxBoldRatio[0]);
|
|
pCache->SetNumLockedFrames(NumLockedFrames);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetMaxVectorCacheSize(UInt n)
|
|
{
|
|
pCache->SetMaxVectorCacheSize(MaxVectorCacheSize = n);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetFauxItalicAngleAllThreads(Float a)
|
|
{
|
|
for (unsigned i=0; i<NELEM(FauxItalicAngle); ++i)
|
|
FauxItalicAngle[i] = a;
|
|
pCache->SetFauxItalicAngleAllThreads(a);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetFauxItalicAngle(Float a)
|
|
{
|
|
pCache->SetFauxItalicAngle(FauxItalicAngle[rage::g_RenderThreadIndex] = a);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetFauxBoldRatioAllThreads(Float r)
|
|
{
|
|
for (unsigned i=0; i<NELEM(FauxBoldRatio); ++i)
|
|
FauxBoldRatio[i] = r;
|
|
pCache->SetFauxBoldRatioAllThreads(r);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetFauxBoldRatio(Float r)
|
|
{
|
|
pCache->SetFauxBoldRatio(FauxBoldRatio[rage::g_RenderThreadIndex] = r);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetOutlineRatioAllThreads(Float r)
|
|
{
|
|
for (unsigned i=0; i<NELEM(OutlineRatio); ++i)
|
|
OutlineRatio[i] = r;
|
|
pCache->SetOutlineRatioAllThreads(r);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetOutlineRatio(Float r)
|
|
{
|
|
pCache->SetOutlineRatio(OutlineRatio[rage::g_RenderThreadIndex] = r);
|
|
}
|
|
|
|
void GFxFontCacheManager::InitTextures(GRenderer* ren)
|
|
{
|
|
pCache->InitTextures(ren);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetNumLockedFrames(UInt num)
|
|
{
|
|
if (num == 0)
|
|
num = 1;
|
|
NumLockedFrames = num;
|
|
if (pCache)
|
|
pCache->SetNumLockedFrames(num);
|
|
}
|
|
|
|
UInt GFxFontCacheManager::GetNumRasterizedGlyphs() const
|
|
{
|
|
return pCache ? pCache->GetNumRasterizedGlyphs() : 0;
|
|
}
|
|
|
|
UInt GFxFontCacheManager::GetNumTextures() const
|
|
{
|
|
return pCache ? pCache->GetNumTextures() : 0;
|
|
}
|
|
|
|
void GFxFontCacheManager::VisitGlyphs(GFxGlyphCacheVisitor* visitor) const
|
|
{
|
|
if (pCache)
|
|
pCache->VisitGlyphs(visitor);
|
|
}
|
|
|
|
void GFxFontCacheManager::SetEventHandler(class GFxGlyphCacheEventHandler* h)
|
|
{
|
|
if (pCache)
|
|
pCache->SetEventHandler(h);
|
|
}
|
|
|
|
UInt GFxFontCacheManager::ComputeUsedArea() const
|
|
{
|
|
return pCache ? pCache->ComputeUsedArea() : 0;
|
|
}
|
|
|
|
UInt GFxFontCacheManager::ComputeTotalArea() const
|
|
{
|
|
return CacheTextureConfig.MaxNumTextures *
|
|
CacheTextureConfig.TextureWidth *
|
|
CacheTextureConfig.TextureHeight;
|
|
}
|
|
|
|
void GFxFontCacheManager::ClearRasterCache()
|
|
{
|
|
if (pCache)
|
|
pCache->ClearRasterCache();
|
|
}
|
|
|
|
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
//UInt GFxFontCacheManagerImpl::FontSizeRamp[] = { 0,1,2,4,6,8,11,16,22,32,40,50,64,76,90,108,128,147,168,194,222,256,0};
|
|
UInt GFxFontCacheManagerImpl::FontSizeRamp[] = { 0,0,2,4,6,8,12,16,18,22,26,32,36,40,45,50,56,64,72,80,90,108,128,147,168,194,222,256,0};
|
|
//UInt GFxFontCacheManagerImpl::FontSizeRamp[] = { 0,1,2,4,8,16,32,64,128,256,0};
|
|
#endif
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
GFxFontCacheManagerImpl::GFxFontCacheManagerImpl(GMemoryHeap* pheap)
|
|
: pHeap(pheap),
|
|
pRenderer(0),
|
|
LockAllInFrame(false),
|
|
RasterCacheWarning(true),
|
|
VectorCacheWarning(true),
|
|
NumLockedFrames(1),
|
|
LockedFrame(1),
|
|
MaxVectorCacheSize(512)
|
|
{
|
|
SetFauxItalicAngleAllThreads(0.3f);
|
|
SetFauxBoldRatioAllThreads(0.055f);
|
|
SetOutlineRatioAllThreads(0.01f);
|
|
|
|
FontDisposer.Bind(this);
|
|
FrameHandler.Bind(this);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
UInt i;
|
|
UInt ramp = 0;
|
|
for (i = 0; i < 256; ++i)
|
|
{
|
|
if (i > FontSizeRamp[ramp + 1])
|
|
++ramp;
|
|
FontSizeMap[i] = (UByte)ramp;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
GFxFontCacheManagerImpl::~GFxFontCacheManagerImpl()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::Clear()
|
|
{
|
|
GLock::Locker lock(&StateLock);
|
|
InvalidateAll();
|
|
BatchPackageQueue.Clear();
|
|
BatchPackageStorage.ClearAndRelease();
|
|
FontSetType::Iterator it = KnownFonts.Begin();
|
|
for (; it != KnownFonts.End(); ++it)
|
|
{
|
|
(*it)->RemoveDisposeHandler(&FontDisposer);
|
|
}
|
|
KnownFonts.Clear();
|
|
G_FreeListElements(VectorGlyphShapeList, VectorGlyphShapeStorage);
|
|
VectorGlyphShapeStorage.ClearAndRelease();
|
|
VectorGlyphCache.Clear();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::InvalidateAll()
|
|
{
|
|
GLock::Locker lock(&StateLock);
|
|
GFxBatchPackage* bp = BatchPackageQueue.GetFirst();
|
|
while(!BatchPackageQueue.IsNull(bp))
|
|
{
|
|
delete bp->Package;
|
|
bp->Package = 0;
|
|
bp = bp->pNext;
|
|
}
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.Clear();
|
|
#endif
|
|
if (pRenderer && LockAllInFrame)
|
|
{
|
|
pRenderer->RemoveEventHandler(&FrameHandler);
|
|
pRenderer = NULL;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::ClearRasterCache()
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
GLock::Locker lock(&StateLock);
|
|
InvalidateAll();
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::CleanUpFont(GFxFontResource* font)
|
|
{
|
|
GLock::Locker lock(&StateLock);
|
|
GFxFontResource** knownFont = KnownFonts.Get(font);
|
|
if (knownFont)
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.CleanUpFont(font);
|
|
#endif
|
|
font->RemoveDisposeHandler(&FontDisposer);
|
|
KnownFonts.Remove(font);
|
|
GFxBatchPackage* bp = BatchPackageQueue.GetFirst();
|
|
while(!BatchPackageQueue.IsNull(bp))
|
|
{
|
|
bool removeFlag = false;
|
|
if (bp->Package)
|
|
{
|
|
for (UInt i = 0; i < bp->Package->BatchVerifier.GetSize(); ++i)
|
|
{
|
|
if (bp->Package->BatchVerifier[i].GlyphParam.pFont == font)
|
|
{
|
|
removeFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (removeFlag)
|
|
{
|
|
// Invalidate BatchPackage.
|
|
delete bp->Package;
|
|
bp->Package = 0;
|
|
}
|
|
bp = bp->pNext;
|
|
}
|
|
}
|
|
VectorGlyphShape* sh = VectorGlyphShapeList.GetFirst();
|
|
while(!VectorGlyphShapeList.IsNull(sh))
|
|
{
|
|
VectorGlyphShape* next = sh->pNext;
|
|
if (sh->pFont == font)
|
|
{
|
|
VectorGlyphCache.Remove(VectorGlyphKey(sh->pFont, sh->GlyphIndex, sh->HintedGlyphSize, sh->Flags, sh->Outline));
|
|
VectorGlyphShapeList.Remove(sh);
|
|
VectorGlyphShapeStorage.Free(sh);
|
|
}
|
|
sh = next;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::Init(const GFxFontCacheManager::TextureConfig& config)
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.Init(config.TextureWidth,
|
|
config.TextureHeight,
|
|
config.MaxNumTextures,
|
|
config.MaxSlotHeight,
|
|
config.SlotPadding,
|
|
config.TexUpdWidth,
|
|
config.TexUpdHeight);
|
|
#else
|
|
GUNUSED(config);
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
|
|
UInt GFxFontCacheManagerImpl::GetTextureWidth() const { return Cache.GetTextureWidth(); }
|
|
UInt GFxFontCacheManagerImpl::GetTextureHeight() const { return Cache.GetTextureHeight();}
|
|
|
|
#else
|
|
|
|
UInt GFxFontCacheManagerImpl::GetTextureWidth() const { return 0; }
|
|
UInt GFxFontCacheManagerImpl::GetTextureHeight() const { return 0; }
|
|
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::SetFauxItalicAngleAllThreads(Float a)
|
|
{
|
|
for (unsigned i=0; i<NELEM(FauxItalicAngle); ++i)
|
|
{
|
|
FauxItalicAngle[i] = a;
|
|
ItalicMtx[i] = GMatrix2D::Shearing(0, -a);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::SetFauxItalicAngle(Float a)
|
|
{
|
|
const unsigned rti = rage::g_RenderThreadIndex;
|
|
FauxItalicAngle[rti] = a;
|
|
ItalicMtx[rti] = GMatrix2D::Shearing(0, -a);
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
inline UInt GFxFontCacheManagerImpl::snapFontSizeToRamp(UInt fontSize) const
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
fontSize = fontSize + ((fontSize + 3) >> 2);
|
|
fontSize =(fontSize <= 255) ? FontSizeRamp[FontSizeMap[fontSize] + 1] : 255;
|
|
#endif
|
|
return fontSize;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool GFxFontCacheManagerImpl::GlyphFits(const GRectF& bounds,
|
|
UInt fontSize,
|
|
Float heightRatio,
|
|
const GFxGlyphParam& param,
|
|
Float maxRasterScale) const
|
|
{
|
|
if (param.BlurX || param.BlurY)
|
|
return true;
|
|
|
|
fontSize = UInt(heightRatio * fontSize + 0.5f);
|
|
if (!param.IsOptRead())
|
|
fontSize = snapFontSizeToRamp(fontSize);
|
|
|
|
UInt h = (UInt)(ceilf (bounds.Bottom * fontSize / 1024.0f) -
|
|
floorf(bounds.Top * fontSize / 1024.0f)) + 1;
|
|
return h < GetTextureGlyphMaxHeight() * maxRasterScale;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::setRenderer(GRenderer* ren)
|
|
{
|
|
if (pRenderer != ren)
|
|
{
|
|
if (pRenderer)
|
|
InvalidateAll();
|
|
|
|
pRenderer = ren;
|
|
LockAllInFrame = false;
|
|
|
|
GRenderer::RenderCaps caps;
|
|
pRenderer->GetRenderCaps(&caps);
|
|
if (caps.CapBits & (GRenderer::Cap_KeepVertexData | GRenderer::Cap_NoTexOverwrite))
|
|
{
|
|
LockAllInFrame = true;
|
|
pRenderer->AddEventHandler(&FrameHandler);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool GFxFontCacheManagerImpl::VerifyBatchPackage(const GFxBatchPackage* bp,
|
|
GFxDisplayContext &context,
|
|
Float heightRatio)
|
|
{
|
|
GLock::Locker lock(&StateLock);
|
|
setRenderer(context.GetRenderer());
|
|
|
|
// Verify the package and the owner
|
|
if (!bp || bp->Owner != this || !bp->Package || bp->Package->FailedGlyphs)
|
|
return false;
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
// Verify glyphs
|
|
const GFxBatchPackageData* bpd = bp->Package;
|
|
|
|
UInt i;
|
|
bool verifySize = heightRatio != 0;
|
|
for (i = 0; i < bpd->BatchVerifier.GetSize(); ++i)
|
|
{
|
|
const GFxBatchPackageData::GlyphVerifier& gv = bpd->BatchVerifier[i];
|
|
|
|
if (verifySize && gv.pGlyph != 0)
|
|
{
|
|
UInt oldSize = gv.GlyphParam.GetFontSize();
|
|
if (oldSize)
|
|
{
|
|
UInt newSize = snapFontSizeToRamp(UInt(gv.FontSize * heightRatio + 0.5f));
|
|
if (newSize != oldSize)
|
|
return false;
|
|
}
|
|
}
|
|
if (gv.pGlyph)
|
|
{
|
|
if (!Cache.VerifyGlyphAndSendBack(gv.GlyphParam, gv.pGlyph))
|
|
return false;
|
|
if (LockAllInFrame)
|
|
Cache.LockGlyph(gv.pGlyph);
|
|
}
|
|
}
|
|
#else
|
|
GUNUSED(heightRatio);
|
|
#endif
|
|
//printf("*"); // DBG
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
GFxBatchPackage*
|
|
GFxFontCacheManagerImpl::CreateBatchPackage(GMemoryHeap* pheap,
|
|
GFxBatchPackage* bp,
|
|
const GFxTextLineBuffer::Iterator& linesIt,
|
|
GFxDisplayContext &context,
|
|
const GPointF& lnOffset,
|
|
GFxLineBufferGeometry* geom,
|
|
const GFxTextFieldParam& param,
|
|
UInt numGlyphsInBatch)
|
|
{
|
|
#ifdef GFX_AMP_SERVER
|
|
ScopeFunctionTimer timer(context.pStats, NativeCodeSwdHandle, Func_GFxFontCacheManagerImpl_CreateBatchPackage, Amp_Profile_Level_Low);
|
|
#endif
|
|
|
|
GLock::Locker lock(&StateLock);
|
|
|
|
if (pheap == 0)
|
|
pheap = pHeap;
|
|
|
|
setRenderer(context.GetRenderer());
|
|
|
|
if (bp && bp->Owner == this)
|
|
{
|
|
//BatchPackageQueue.SendToBack(bp);
|
|
if (bp->Package)
|
|
{
|
|
if (numGlyphsInBatch)
|
|
bp->Package->Clear();
|
|
}
|
|
else
|
|
bp->Package = GHEAP_NEW(pheap) GFxBatchPackageData;
|
|
}
|
|
else
|
|
{
|
|
if (bp)
|
|
delete bp->Package;
|
|
|
|
bp = BatchPackageStorage.Alloc();
|
|
bp->Owner = this;
|
|
bp->Package = GHEAP_NEW(pheap) GFxBatchPackageData;
|
|
BatchPackageQueue.PushBack(bp);
|
|
}
|
|
|
|
fillBatchPackage(bp->Package,
|
|
linesIt,
|
|
context,
|
|
lnOffset,
|
|
geom,
|
|
param,
|
|
numGlyphsInBatch);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.UpdateTextures(context.GetRenderer());
|
|
#endif
|
|
return bp;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::ReleaseBatchPackage(GFxBatchPackage* bp)
|
|
{
|
|
GLock::Locker lock(&StateLock);
|
|
if (bp)
|
|
{
|
|
if (bp->Owner != this)
|
|
{
|
|
delete bp->Package;
|
|
bp->Package = 0;
|
|
}
|
|
else
|
|
{
|
|
delete bp->Package;
|
|
BatchPackageQueue.Remove(bp);
|
|
BatchPackageStorage.Free(bp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::DisplayBatchPackage(GFxBatchPackage* bp,
|
|
GFxDisplayContext &context,
|
|
const Matrix& displayMatrix,
|
|
const Cxform& cx)
|
|
{
|
|
if (bp && bp->Owner == this && bp->Package)
|
|
{
|
|
GFxBatchPackageData* pd = bp->Package;
|
|
|
|
if (pd->Batch.GetSize() > 0)
|
|
{
|
|
GRenderer* prenderer = context.GetRenderer();
|
|
prenderer->SetCxform(cx);
|
|
|
|
// Render the batch piece by piece.
|
|
GRenderer::CacheProvider cacheProvider(&pd->BatchCache);
|
|
|
|
for (UInt layer = 0; layer < 2; ++layer)
|
|
{
|
|
GFxBatchPackageData::BatchDescHash::Iterator it = pd->BatchDesc.Begin();
|
|
UInt batchIndex;
|
|
for (batchIndex = 0; it != pd->BatchDesc.End(); ++it)
|
|
{
|
|
if (it->First.Layer == layer)
|
|
{
|
|
if (it->Second.DrawFlags)
|
|
{
|
|
prenderer->DrawDistanceFieldBitmaps(&pd->Batch[0], (int)pd->Batch.GetSize(),
|
|
it->Second.Index, it->Second.Count,
|
|
it->Second.pTexture, displayMatrix, pd->DistFieldParams, &cacheProvider);
|
|
}
|
|
else
|
|
prenderer->DrawBitmaps(&pd->Batch[0], (int)pd->Batch.GetSize(),
|
|
it->Second.Index, it->Second.Count,
|
|
it->Second.pTexture, displayMatrix, &cacheProvider);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
inline bool
|
|
GFxFontCacheManagerImpl::resolveTextureGlyph(GFxBatchPackageData::GlyphVerifier* gv,
|
|
const GFxGlyphParam& gp,
|
|
bool canUseRaster,
|
|
const GFxShapeBase* shape,
|
|
GFxDisplayContext &context,
|
|
bool canUseTgd)
|
|
{
|
|
gv->GlyphParam = gp;
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
gv->pGlyph = 0;
|
|
#else
|
|
GUNUSED(canUseRaster);
|
|
#endif
|
|
gv->pTexture = 0;
|
|
gv->DrawFlags = 0;
|
|
gv->TextureWidth = 0;
|
|
gv->TextureHeight = 0;
|
|
GFxTextureGlyphData* tgData = 0;
|
|
|
|
if (canUseTgd || shape == 0)
|
|
tgData = gp.pFont->GetTextureGlyphData();
|
|
|
|
if (tgData)
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
gv->FontSize = 0;
|
|
gv->GlyphScale = 256;
|
|
#endif
|
|
const GFxTextureGlyph& tg = tgData->GetTextureGlyph(gp.GlyphIndex);
|
|
GImageInfoBase* pimage = tg.GetImageInfo(gp.pFont->GetBinding());
|
|
if (pimage)
|
|
{
|
|
gv->pTexture = pimage->GetTexture(context.GetRenderer());
|
|
gv->DrawFlags = tgData->GetTextureFlags();
|
|
gv->TextureWidth = (UInt16)pimage->GetWidth();
|
|
gv->TextureHeight = (UInt16)pimage->GetHeight();
|
|
}
|
|
}
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
else
|
|
{
|
|
gv->pGlyph = Cache.GetGlyph(context.GetRenderer(),
|
|
gp,
|
|
canUseRaster,
|
|
shape,
|
|
gv->FontSize,
|
|
context.pLog);
|
|
if (gv->pGlyph)
|
|
{
|
|
gv->pTexture = Cache.GetGlyphTexture(gv->pGlyph);
|
|
Cache.LockGlyph(gv->pGlyph);
|
|
}
|
|
else
|
|
{
|
|
if (shape != 0 && context.pLog && RasterCacheWarning)
|
|
context.pLog->LogWarning("Warning: Increase raster glyph cache capacity - TextureConfig.\n");
|
|
RasterCacheWarning = false;
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
static bool GFx_ClipBitmapDesc(GFxBatchPackageData::BitmapDesc* bd,
|
|
const GRectF& visibleRect)
|
|
{
|
|
GRectF clipped = bd->Coords;
|
|
clipped.Intersect(visibleRect);
|
|
if (clipped.IsEmpty())
|
|
{
|
|
bd->Coords = visibleRect;
|
|
bd->Coords.Right = bd->Coords.Left;
|
|
bd->Coords.Bottom = bd->Coords.Top;
|
|
bd->TextureCoords.Right = bd->TextureCoords.Left;
|
|
bd->TextureCoords.Bottom = bd->TextureCoords.Top;
|
|
return false;
|
|
}
|
|
|
|
if (clipped != bd->Coords)
|
|
{
|
|
GRectF clippedTexture = bd->TextureCoords;
|
|
|
|
if (bd->Coords.Left != clipped.Left)
|
|
clippedTexture.Left = bd->TextureCoords.Left +
|
|
(clipped.Left - bd->Coords.Left) *
|
|
bd->TextureCoords.Width() /
|
|
bd->Coords.Width();
|
|
|
|
if (bd->Coords.Top != clipped.Top)
|
|
clippedTexture.Top = bd->TextureCoords.Top +
|
|
(clipped.Top - bd->Coords.Top) *
|
|
bd->TextureCoords.Height() /
|
|
bd->Coords.Height();
|
|
|
|
if (bd->Coords.Right != clipped.Right)
|
|
clippedTexture.Right = bd->TextureCoords.Right -
|
|
(bd->Coords.Right - clipped.Right) *
|
|
bd->TextureCoords.Width() /
|
|
bd->Coords.Width();
|
|
|
|
if (bd->Coords.Bottom != clipped.Bottom)
|
|
clippedTexture.Bottom = bd->TextureCoords.Bottom -
|
|
(bd->Coords.Bottom - clipped.Bottom) *
|
|
bd->TextureCoords.Height() /
|
|
bd->Coords.Height();
|
|
bd->Coords = clipped;
|
|
bd->TextureCoords = clippedTexture;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::fillBatchPackage(GFxBatchPackageData* pd,
|
|
const GFxTextLineBuffer::Iterator& linesIter,
|
|
GFxDisplayContext &context,
|
|
const GPointF& lnOffset,
|
|
GFxLineBufferGeometry* geom,
|
|
const GFxTextFieldParam& textFieldParam,
|
|
UInt numGlyphsInBatch)
|
|
{
|
|
// DBG
|
|
//printf("C");
|
|
|
|
#ifdef GFC_NO_GLYPH_CACHE
|
|
GUNUSED(geom);
|
|
#endif
|
|
GFxFontResource* pprevFont = NULL;
|
|
GFxResourceBindData fontData;
|
|
|
|
if (textFieldParam.ShadowColor != 0)
|
|
numGlyphsInBatch *= 2;
|
|
|
|
// Value numGlyphsInBatch is just a hint to the allocator that
|
|
// helps avoid extra reallocs.
|
|
pd->BatchVerifier.Resize(0); // Avoid possible extra copying
|
|
pd->BatchVerifier.Reserve(numGlyphsInBatch);
|
|
|
|
pd->VectorRenderingRequired = false;
|
|
pd->FailedGlyphs = false;
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Matrix globalMatrixDir;
|
|
Matrix globalMatrixInv;
|
|
if (textFieldParam.TextParam.IsOptRead())
|
|
{
|
|
globalMatrixDir = geom->SourceTextMatrix;
|
|
globalMatrixDir.Append(context.ViewportMatrix);
|
|
globalMatrixInv = globalMatrixDir;
|
|
globalMatrixInv.Invert();
|
|
}
|
|
|
|
// This flag is used to snap the glyphs to pixels in the X direction.
|
|
// If the text is not oriented along the axes (freely rotated) it
|
|
// does not make sense to snap it.
|
|
bool freeRotation = geom->SourceTextMatrix.IsFreeRotation();
|
|
#endif
|
|
|
|
UInt pass;
|
|
enum { PassShadow = 0, PassText = 1 };
|
|
UInt passStart = PassText;
|
|
UInt passEnd = PassText;
|
|
|
|
if (textFieldParam.ShadowColor != 0)
|
|
{
|
|
passStart = PassShadow;
|
|
if (textFieldParam.ShadowParam.IsKnockOut() ||
|
|
textFieldParam.ShadowParam.IsHiddenObject())
|
|
{
|
|
passEnd = PassShadow;
|
|
}
|
|
}
|
|
|
|
geom->BlurX = 0;
|
|
geom->BlurY = 0;
|
|
UInt layer;
|
|
|
|
GFxTextLineBuffer::Iterator linesIt;
|
|
for (pass = passStart; pass <= passEnd; ++pass)
|
|
{
|
|
// When using a single texture it will be a single batch.
|
|
// If there are many textures and text has a shadow, it is
|
|
// possible than the shadow will be drawn after text. So that,
|
|
// it is necessary to break the batches so that they would have
|
|
// separately shadow and glyph bitmaps
|
|
//-----------------------
|
|
layer = passStart;
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
const GFxGlyphParam& textParam = (pass == PassText)?
|
|
textFieldParam.TextParam:
|
|
textFieldParam.ShadowParam;
|
|
|
|
if (Cache.GetMaxNumTextures() > 1)
|
|
layer = pass;
|
|
#endif
|
|
|
|
for(linesIt = linesIter; linesIt.IsVisible(); ++linesIt)
|
|
{
|
|
GFxTextLineBuffer::Line& line = *linesIt;
|
|
GFxTextLineBuffer::GlyphIterator glyphIt = line.Begin();
|
|
for (; !glyphIt.IsFinished(); ++glyphIt)
|
|
{
|
|
GFxTextLineBuffer::GlyphEntry& glyph = glyphIt.GetGlyph();
|
|
|
|
if (pass == PassShadow)
|
|
glyph.ClearShadowInBatch();
|
|
|
|
if (glyph.GetIndex() == ~0u)
|
|
{
|
|
pd->VectorRenderingRequired = true;
|
|
continue;
|
|
}
|
|
|
|
if (glyph.IsCharInvisible())
|
|
continue;
|
|
|
|
if (!glyph.IsInBatch() && pass == PassText)
|
|
{
|
|
pd->VectorRenderingRequired = true;
|
|
continue;
|
|
}
|
|
|
|
GFxFontResource* presolvedFont = glyphIt.GetFont();
|
|
GASSERT(presolvedFont);
|
|
|
|
// Create a BatchDesc entry for each different image.
|
|
if (presolvedFont != pprevFont)
|
|
{
|
|
//fontData = context.pResourceBinding->GetResourceData(presolvedFont);
|
|
pprevFont = presolvedFont;
|
|
if (!KnownFonts.Get(presolvedFont))
|
|
{
|
|
KnownFonts.Add(presolvedFont);
|
|
presolvedFont->AddDisposeHandler(&FontDisposer);
|
|
}
|
|
}
|
|
|
|
GFxGlyphParam glyphParam;
|
|
|
|
glyphParam.Clear();
|
|
glyphParam.pFont = presolvedFont;
|
|
glyphParam.GlyphIndex = UInt16(glyph.GetIndex());
|
|
|
|
GFxBatchPackageData::GlyphVerifier gv;
|
|
GPtr<GFxShapeBase> shape;
|
|
bool canUseRaster = false;
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Float screenFontSize = geom->HeightRatio * glyph.GetFontSize();
|
|
if (screenFontSize < 1)
|
|
screenFontSize = 1;
|
|
UInt snappedFontSize = UInt(screenFontSize + 0.5f);
|
|
|
|
if (glyphParam.pFont->GetTextureGlyphData() == 0)// || pass == PassShadow)
|
|
{
|
|
canUseRaster =
|
|
textParam.IsOptRead() &&
|
|
textParam.GetBlurX() == 0 &&
|
|
textParam.GetBlurY() == 0 &&
|
|
pass == PassText &&
|
|
glyphParam.pFont->IsHintedRasterGlyph(glyphParam.GlyphIndex, glyphParam.FontSize);
|
|
|
|
// apply glyph offsets if they were provided by fontmap into the fonthandle...
|
|
GFxFontHandle* pfontHandle = glyphIt.GetFontHandle();
|
|
Float h = presolvedFont->GetAscent() + presolvedFont->GetDescent();
|
|
Float offx = pfontHandle->GetGlyphOffsetX() * h;
|
|
Float offy = pfontHandle->GetGlyphOffsetY() * h;
|
|
|
|
shape = *GetGlyphShape_NoLock(glyphParam.pFont,
|
|
glyphParam.GlyphIndex,
|
|
(textParam.IsOptRead() && pass == PassText)?
|
|
snappedFontSize : 0,
|
|
glyphIt.IsFauxBold() | textParam.IsFauxBold(),
|
|
glyphIt.IsFauxItalic() | textParam.IsFauxItalic(),
|
|
offx, offy, textParam.GetOutline(),
|
|
context.pLog);
|
|
}
|
|
|
|
if (shape)
|
|
{
|
|
GRectF bounds;
|
|
if (shape->HasValidBounds())
|
|
bounds = shape->GetBound();
|
|
else
|
|
presolvedFont->GetGlyphBounds(glyph.GetIndex(), &bounds);
|
|
|
|
if (!textParam.IsOptRead())
|
|
snappedFontSize = snapFontSizeToRamp(snappedFontSize);
|
|
|
|
Float blurX = textParam.GetBlurX();
|
|
Float blurY = textParam.GetBlurY();
|
|
if (blurX > geom->BlurX) geom->BlurX = blurX;
|
|
if (blurY > geom->BlurY) geom->BlurY = blurY;
|
|
|
|
Cache.CalcGlyphParam(screenFontSize,
|
|
snappedFontSize,
|
|
geom->HeightRatio,
|
|
bounds.Bottom - bounds.Top,
|
|
textParam,
|
|
&glyphParam,
|
|
&gv.FontSize,
|
|
&gv.GlyphScale);
|
|
|
|
if ( textParam.IsOptRead() &&
|
|
!textParam.IsBitmapFont() &&
|
|
shape->GetHintedGlyphSize() == 0 &&
|
|
!canUseRaster &&
|
|
textParam.BlurX == 0)
|
|
{
|
|
Float glyphWidth = (bounds.Right - bounds.Left) * glyphParam.FontSize / 1024.0f;
|
|
if (3*glyphWidth < Cache.GetMaxGlyphHeight())
|
|
{
|
|
glyphParam.SetStretch(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyphParam.SetFontSize(snappedFontSize);
|
|
glyphParam.Flags = textParam.Flags;
|
|
gv.FontSize = UInt16(snappedFontSize * SubpixelSizeScale);
|
|
gv.GlyphScale = 256;
|
|
}
|
|
#endif
|
|
|
|
if (glyphIt.IsAutoFitDisabled())
|
|
glyphParam.SetAutoFit(false);
|
|
glyphParam.SetFauxBold (glyphIt.IsFauxBold() | textParam.IsFauxBold());
|
|
glyphParam.SetFauxItalic(glyphIt.IsFauxItalic() | textParam.IsFauxItalic());
|
|
glyphParam.FlagsEx = textParam.FlagsEx;
|
|
if (!resolveTextureGlyph(&gv, glyphParam, canUseRaster, shape, context, true))//pass == PassText))
|
|
pd->FailedGlyphs = true;
|
|
|
|
if (gv.pTexture)
|
|
{
|
|
if ((gv.DrawFlags & GFxTextureFont::TF_DistanceFieldAlpha) == 0 || pass == PassText)
|
|
{
|
|
if (pass == PassShadow)
|
|
glyph.SetShadowInBatch();
|
|
|
|
pd->BatchVerifier.PushBack(gv);
|
|
|
|
// Find the batch with the same texture and color.
|
|
GFxBatchPackageData::BatchInfoKey bik(gv.pTexture, layer);
|
|
GFxBatchPackageData::BatchInfo* pbi = pd->BatchDesc.Get(bik);
|
|
if (pbi == NULL)
|
|
{
|
|
// Not found? Add new record.
|
|
GFxBatchPackageData::BatchInfo bi;
|
|
bi.Clear();
|
|
bi.pTexture = bik.pTexture;
|
|
bi.DrawFlags = gv.DrawFlags;
|
|
bi.ImageUseCount = 1;
|
|
pd->BatchDesc.Add(bik, bi);
|
|
}
|
|
else
|
|
++pbi->ImageUseCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pass == PassText)
|
|
glyph.ClearInBatch();
|
|
|
|
pd->VectorRenderingRequired = true;
|
|
geom->SetCheckPreciseScale();
|
|
}
|
|
}
|
|
}
|
|
} // for(pass...)
|
|
|
|
// Distance Field effects.
|
|
bool UseDistFieldShadow = false;
|
|
pd->DistFieldParams.ShadowColor = textFieldParam.ShadowColor;
|
|
if (pd->DistFieldParams.ShadowColor != 0)
|
|
{
|
|
UseDistFieldShadow = true;
|
|
|
|
Float blurSize = G_Min(3.f, textFieldParam.ShadowParam.GetBlurX());
|
|
pd->DistFieldParams.ShadowWidth = 6.f * blurSize;
|
|
pd->DistFieldParams.ShadowOffset = GPointF(textFieldParam.ShadowOffsetX*0.05f, textFieldParam.ShadowOffsetY*0.05f);
|
|
Float Offset2 = pd->DistFieldParams.ShadowOffset.DistanceSquared();
|
|
if (Offset2 > 4)
|
|
{
|
|
Float Offset = sqrtf(Offset2);
|
|
pd->DistFieldParams.ShadowOffset.x *= (2.f/Offset);
|
|
pd->DistFieldParams.ShadowOffset.y *= (2.f/Offset);
|
|
}
|
|
}
|
|
pd->DistFieldParams.GlowColor = textFieldParam.GlowColor;
|
|
if (pd->DistFieldParams.GlowColor != 0)
|
|
{
|
|
UseDistFieldShadow = true;
|
|
pd->DistFieldParams.GlowSize[0] = 0;
|
|
pd->DistFieldParams.GlowSize[1] = GFxTextFilter::Fixed44toFloat(textFieldParam.GlowSize);
|
|
}
|
|
|
|
// Assign start indices to batches.
|
|
GFxBatchPackageData::BatchDescHash::Iterator it = pd->BatchDesc.Begin();
|
|
UInt batchIndex;
|
|
for (batchIndex = 0; it != pd->BatchDesc.End(); ++it)
|
|
{
|
|
it->Second.Index = batchIndex;
|
|
batchIndex += it->Second.ImageUseCount;
|
|
}
|
|
|
|
// Allocate bitmap coordinate batch.
|
|
pd->Batch.Resize(0); // Avoid possible extra copying
|
|
pd->Batch.Resize(pd->BatchVerifier.GetSize());
|
|
|
|
// Assign texture coordinates to right slots in the batch.
|
|
UInt gvIdx = 0;
|
|
for (pass = passStart; pass <= passEnd; ++pass)
|
|
{
|
|
layer = passStart;
|
|
|
|
GRectF visibleRect = geom->VisibleRect;
|
|
|
|
if (textFieldParam.ShadowColor.GetAlpha() != 0)
|
|
{
|
|
if (pass == PassShadow || UseDistFieldShadow)
|
|
{
|
|
// Extend visual rectangle in case of shadow/glow.
|
|
visibleRect.Left -= geom->BlurX * 20;
|
|
visibleRect.Top -= geom->BlurY * 20;
|
|
visibleRect.Right += geom->BlurX * 20;
|
|
visibleRect.Bottom += geom->BlurY * 20;
|
|
}
|
|
if (pass == PassShadow)
|
|
{
|
|
visibleRect.Left += (Float)textFieldParam.ShadowOffsetX;
|
|
visibleRect.Top += (Float)textFieldParam.ShadowOffsetY;
|
|
visibleRect.Right += (Float)textFieldParam.ShadowOffsetX;
|
|
visibleRect.Bottom += (Float)textFieldParam.ShadowOffsetY;
|
|
}
|
|
}
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
const GFxGlyphParam& textParam = (pass == PassText)?
|
|
textFieldParam.TextParam:
|
|
textFieldParam.ShadowParam;
|
|
|
|
if (Cache.GetMaxNumTextures() > 1)
|
|
layer = pass;
|
|
#endif
|
|
|
|
for(linesIt = linesIter; linesIt.IsVisible(); ++linesIt)
|
|
{
|
|
GFxTextLineBuffer::Line& line = *linesIt;
|
|
GPointF offset;
|
|
offset.x = Float(line.GetOffsetX());
|
|
offset.y = Float(line.GetOffsetY());
|
|
|
|
offset += lnOffset;
|
|
offset.x -= Float(geom->HScrollOffset);
|
|
offset.y += line.GetBaseLineOffset();
|
|
SInt advance = 0;
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
if (textParam.IsOptRead())
|
|
{
|
|
offset = globalMatrixDir.Transform(offset);
|
|
offset.x = floorf(offset.x + 0.5f);
|
|
offset.y = floorf(offset.y + 0.5f);
|
|
offset = globalMatrixInv.Transform(offset);
|
|
}
|
|
#endif
|
|
GFxTextLineBuffer::GlyphIterator glyphIt = line.Begin(linesIt.GetHighlighter());
|
|
for (; !glyphIt.IsFinished(); ++glyphIt, offset.x += advance)
|
|
{
|
|
GFxTextLineBuffer::GlyphEntry& glyph = glyphIt.GetGlyph();
|
|
advance = glyph.GetAdvance();
|
|
|
|
if (glyph.GetIndex() == ~0u)
|
|
{
|
|
pd->VectorRenderingRequired = true;
|
|
continue;
|
|
}
|
|
|
|
if (glyph.IsCharInvisible())
|
|
continue;
|
|
|
|
if (pass == PassShadow)
|
|
{
|
|
if (!glyph.IsShadowInBatch())
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!glyph.IsInBatch())
|
|
{
|
|
pd->VectorRenderingRequired = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// do batching
|
|
GFxBatchPackageData::GlyphVerifier& gv = pd->BatchVerifier[gvIdx++];
|
|
|
|
// Find the batch to which the glyph belongs to.
|
|
GFxBatchPackageData::BatchInfoKey bik(gv.pTexture, layer);
|
|
GFxBatchPackageData::BatchInfo* pbi = pd->BatchDesc.Get(bik);
|
|
GASSERT(pbi != NULL);
|
|
|
|
GFxBatchPackageData::BitmapDesc& bd = pd->Batch[pbi->Index + pbi->Count];
|
|
Float scale;
|
|
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
bool snapX = false;
|
|
if (gv.pGlyph)
|
|
{
|
|
if (!LockAllInFrame)
|
|
Cache.UnlockGlyph(gv.pGlyph);
|
|
|
|
const GFxGlyphRect& r = gv.pGlyph->Rect;
|
|
const GPoint<SInt16>& o = gv.pGlyph->Origin;
|
|
|
|
bd.TextureCoords.Left = (r.x + 1) * Cache.GetScaleU();
|
|
bd.TextureCoords.Top = (r.y + 1) * Cache.GetScaleV();
|
|
bd.TextureCoords.Right = bd.TextureCoords.Left + (r.w - 2) * Cache.GetScaleU();
|
|
bd.TextureCoords.Bottom = bd.TextureCoords.Top + (r.h - 2) * Cache.GetScaleV();
|
|
|
|
UInt stretch = gv.GlyphParam.GetStretch();
|
|
bd.Coords.Left = (r.x + 1 - o.x) * 20.0f / stretch;
|
|
bd.Coords.Top = (r.y + 1 - o.y) * 20.0f;
|
|
bd.Coords.Right = bd.Coords.Left + (r.w - 2) * 20.0f / stretch;
|
|
bd.Coords.Bottom = bd.Coords.Top + (r.h - 2) * 20.0f;
|
|
snapX = stretch == 1 && !freeRotation && gv.GlyphParam.BlurX == 0;
|
|
|
|
if (gv.GlyphParam.IsOptRead())
|
|
{
|
|
scale = 1.0f / (Float(gv.GlyphScale) * geom->HeightRatio / 256.0f);
|
|
}
|
|
else
|
|
{
|
|
scale = Float(glyph.GetFontSize() * SubpixelSizeScale) / Float(gv.FontSize);
|
|
snapX = false;
|
|
}
|
|
|
|
bd.Coords.Left *= scale;
|
|
bd.Coords.Top *= scale;
|
|
bd.Coords.Right *= scale;
|
|
bd.Coords.Bottom *= scale;
|
|
|
|
if (pass == PassShadow)
|
|
{
|
|
bd.Coords.Left += (Float)textFieldParam.ShadowOffsetX;
|
|
bd.Coords.Top += (Float)textFieldParam.ShadowOffsetY;
|
|
bd.Coords.Right += (Float)textFieldParam.ShadowOffsetX;
|
|
bd.Coords.Bottom += (Float)textFieldParam.ShadowOffsetY;
|
|
}
|
|
gv.FontSize = (UInt16)glyph.GetFontSize();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
const GFxTextureGlyphData *gd = gv.GlyphParam.pFont->GetTextureGlyphData();
|
|
const GFxTextureGlyph& tg = gd->GetTextureGlyph(glyph.GetIndex());
|
|
|
|
// Scale from uv coords to the 1024x1024 glyph square.
|
|
// @@ need to factor this out!
|
|
scale = PixelsToTwips(glyph.GetFontSize()) / 1024.0f; // the EM square is 1024 x 1024
|
|
Float textureXScale = gd->GetTextureGlyphScale() * scale * gv.TextureWidth;
|
|
Float textureYScale = gd->GetTextureGlyphScale() * scale * gv.TextureHeight;
|
|
bd.Coords = tg.UvBounds;
|
|
bd.TextureCoords = tg.UvBounds;
|
|
bd.Coords -= tg.UvOrigin;
|
|
bd.Coords.Left *= textureXScale;
|
|
bd.Coords.Top *= textureYScale;
|
|
bd.Coords.Right *= textureXScale;
|
|
bd.Coords.Bottom *= textureYScale;
|
|
|
|
if (UseDistFieldShadow && gv.DrawFlags)
|
|
{
|
|
Float pad = (Float)5*(gd->GetCharPadding()-2);
|
|
Float blur = G_Min<Float>(pad, pd->DistFieldParams.ShadowWidth * 20.f/6.f);
|
|
Float offsetX = blur + G_Min<Float>(pad-blur, pd->DistFieldParams.ShadowOffset.x * 20.f);
|
|
Float offsetY = blur + G_Min<Float>(pad-blur, pd->DistFieldParams.ShadowOffset.y * 20.f);
|
|
|
|
bd.Coords.Left -= blur;
|
|
bd.Coords.Top -= blur;
|
|
bd.Coords.Right += offsetX;
|
|
bd.Coords.Bottom += offsetY;
|
|
|
|
bd.TextureCoords.Left -= blur / textureXScale;
|
|
bd.TextureCoords.Top -= blur / textureYScale;
|
|
bd.TextureCoords.Right += offsetX / textureXScale;
|
|
bd.TextureCoords.Bottom += offsetY / textureYScale;
|
|
}
|
|
}
|
|
|
|
GPointF p(offset);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
if (snapX)
|
|
{
|
|
p = globalMatrixDir.Transform(p);
|
|
p.x = floorf(p.x + 0.5f);
|
|
p = globalMatrixInv.Transform(p);
|
|
}
|
|
#endif
|
|
bd.Coords += p;
|
|
bd.Color = (pass == PassText) ? glyphIt.GetColor() : textFieldParam.ShadowColor;
|
|
// DBG
|
|
//bd.Color = (pass == PassText) ? 0xFFFFFFFF : textFieldParam.ShadowColor;
|
|
|
|
// The following logic simulates Flash shadow and glow clipping.
|
|
// It's not possible to achieve 100% visual compatibility, so that,
|
|
// it's a compromise between performance and visual consistency.
|
|
//
|
|
// The idea is as follows.
|
|
// All glyphs bitmaps are stored unclipped. Clipping is applied
|
|
// to the bitmap rectangles. In case of glow/shadow we extend the
|
|
// visual rectangle. But it may draw extra glow/shadow glyphs.
|
|
// The best trade-off is to draw glow/shadow in case if more than a half
|
|
// of it is visible in the original (non extended) visual rectangle.
|
|
//--------------------------
|
|
if (pass == PassText)
|
|
{
|
|
if (!geom->IsNoClipping())
|
|
GFx_ClipBitmapDesc(&bd, visibleRect);
|
|
}
|
|
else
|
|
{
|
|
GFxBatchPackageData::BitmapDesc tmp = bd;
|
|
GFx_ClipBitmapDesc(&tmp, geom->VisibleRect);
|
|
if (tmp.Coords.Width()*2 > bd.Coords.Width())
|
|
{
|
|
GFx_ClipBitmapDesc(&bd, visibleRect);
|
|
}
|
|
else
|
|
{
|
|
bd.Coords = geom->VisibleRect;
|
|
bd.Coords.Right = bd.Coords.Left;
|
|
bd.Coords.Bottom = bd.Coords.Top;
|
|
bd.TextureCoords.Right = bd.TextureCoords.Left;
|
|
bd.TextureCoords.Bottom = bd.TextureCoords.Top;
|
|
}
|
|
}
|
|
pbi->Count++;
|
|
}
|
|
}
|
|
} // for(pass...)
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
bool GFxFontCacheManagerImpl::isOuterContourCW(const GCompoundShape& shape) const
|
|
{
|
|
UInt i, j;
|
|
|
|
Float minX1 = 1e10f;
|
|
Float minY1 = 1e10f;
|
|
Float maxX1 = -1e10f;
|
|
Float maxY1 = -1e10f;
|
|
Float minX2 = 1e10f;
|
|
Float minY2 = 1e10f;
|
|
Float maxX2 = -1e10f;
|
|
Float maxY2 = -1e10f;
|
|
bool cw = true;
|
|
|
|
for(i = 0; i < shape.GetNumPaths(); ++i)
|
|
{
|
|
const GCompoundShape::SPath& path = shape.GetPath(i);
|
|
if(path.GetNumVertices() > 2)
|
|
{
|
|
GPointType v1 = path.GetVertex(path.GetNumVertices() - 1);
|
|
Float sum = 0;
|
|
for(j = 0; j < path.GetNumVertices(); ++j)
|
|
{
|
|
const GPointType& v2 = path.GetVertex(j);
|
|
if(v2.x < minX1) minX1 = v2.x;
|
|
if(v2.y < minY1) minY1 = v2.y;
|
|
if(v2.x > maxX1) maxX1 = v2.x;
|
|
if(v2.y > maxY1) maxY1 = v2.y;
|
|
sum += v1.x * v2.y - v1.y * v2.x;
|
|
v1 = v2;
|
|
}
|
|
|
|
if(minX1 < minX2 || minY1 < minY2 || maxX1 > maxX2 || maxY1 > maxY2)
|
|
{
|
|
minX2 = minX1;
|
|
minY2 = minY1;
|
|
maxX2 = maxX1;
|
|
maxY2 = maxY1;
|
|
cw = sum > 0;
|
|
}
|
|
}
|
|
}
|
|
return cw;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
const GRectF& GFxFontCacheManagerImpl::AdjustBounds(GRectF* bounds,
|
|
bool fauxBold, bool fauxItalic) const
|
|
{
|
|
const unsigned rti = rage::g_RenderThreadIndex;
|
|
if (fauxBold)
|
|
{
|
|
Float fauxBoldWidth = FauxBoldRatio[rti] * 1024.0f;
|
|
bounds->Left -= fauxBoldWidth;
|
|
bounds->Right += fauxBoldWidth;
|
|
}
|
|
if (fauxItalic)
|
|
{
|
|
GPointF p1, p2;
|
|
p1.x = bounds->Left;
|
|
p1.y = bounds->Top;
|
|
p2.x = bounds->Right;
|
|
p2.y = bounds->Bottom;
|
|
p1 = ItalicMtx[rti].Transform(p1);
|
|
p2 = ItalicMtx[rti].Transform(p2);
|
|
bounds->Left = p1.x;
|
|
bounds->Right = p2.x;
|
|
}
|
|
return *bounds;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::copyAndTransformShape(GFxShapeNoStyles* dst,
|
|
const GFxShapeBase* src,
|
|
GRectF* bbox,
|
|
bool fauxBold, bool fauxItalic,
|
|
Float offx, Float offy, UInt outline)
|
|
{
|
|
if (!src)
|
|
return;
|
|
GFxPathPacker path;
|
|
|
|
const unsigned rti = rage::g_RenderThreadIndex;
|
|
|
|
#ifndef GFC_NO_FXPLAYER_STROKER
|
|
Float fauxBoldWidth = FauxBoldRatio[rti] * 1024.0f;
|
|
GPtr<GFxShapeNoStyles> fauxBoldShape;
|
|
if (fauxBold)
|
|
{
|
|
fauxBoldShape = *new GFxShapeNoStyles(ShapePageSize * 4);
|
|
src->MakeCompoundShape(&TmpShape1[rti], 10);
|
|
TmpShape1[rti].ScaleAndTranslate(1, 1000, 0, 0);
|
|
Stroker[rti].SetWidth(isOuterContourCW(TmpShape1[rti]) ? fauxBoldWidth : -fauxBoldWidth);
|
|
Stroker[rti].SetLineJoin(GStrokerTypes::MiterJoin);
|
|
TmpShape2[rti].Clear();
|
|
Stroker[rti].GenerateContour(TmpShape1[rti], -1, TmpShape2[rti]);
|
|
UInt i, j;
|
|
for(i = 0; i < TmpShape2[rti].GetNumPaths(); ++i)
|
|
{
|
|
const GCompoundShape::SPath& path2 = TmpShape2[rti].GetPath(i);
|
|
if (path2.GetNumVertices() > 2)
|
|
{
|
|
const GPointType& p = path2.GetVertex(0);
|
|
path.Reset();
|
|
path.SetFill0(1);
|
|
path.SetFill1(0);
|
|
path.SetMoveTo(SInt(p.x), SInt(p.y * 0.001f));
|
|
for(j = 1; j < path2.GetNumVertices(); ++j)
|
|
{
|
|
const GPointType& p = path2.GetVertex(j);
|
|
path.LineToAbs(SInt(p.x), SInt(p.y * 0.001f));
|
|
}
|
|
fauxBoldShape->AddPath(&path);
|
|
}
|
|
}
|
|
src = fauxBoldShape;
|
|
}
|
|
else
|
|
if (outline > 0.0f)
|
|
{
|
|
fauxBoldShape = *new GFxShapeNoStyles(ShapePageSize * 4);
|
|
src->MakeCompoundShape(&TmpShape1[rti], 10);
|
|
Stroker[rti].SetWidth(Float(outline) * OutlineRatio[rti] * 1024.0f);
|
|
Stroker[rti].SetLineJoin(GStrokerTypes::MiterJoin);
|
|
TmpShape2[rti].Clear();
|
|
Stroker[rti].GenerateStroke(TmpShape1[rti], -1, TmpShape2[rti]);
|
|
UInt i, j;
|
|
for(i = 0; i < TmpShape2[rti].GetNumPaths(); ++i)
|
|
{
|
|
const GCompoundShape::SPath& path2 = TmpShape2[rti].GetPath(i);
|
|
if (path2.GetNumVertices() > 2)
|
|
{
|
|
const GPointType& p = path2.GetVertex(0);
|
|
path.Reset();
|
|
path.SetFill0(1);
|
|
path.SetFill1(0);
|
|
path.SetMoveTo(SInt(p.x), SInt(p.y));
|
|
for(j = 1; j < path2.GetNumVertices(); ++j)
|
|
{
|
|
const GPointType& p = path2.GetVertex(j);
|
|
path.LineToAbs(SInt(p.x), SInt(p.y));
|
|
}
|
|
fauxBoldShape->AddPath(&path);
|
|
}
|
|
}
|
|
src = fauxBoldShape;
|
|
}
|
|
|
|
|
|
|
|
#endif //#ifndef GFC_NO_FXPLAYER_STROKER
|
|
|
|
GPtr<GFxShapeBase::PathsIterator> ppathsIt = *src->GetPathsIterator();
|
|
GFxShapeBase::PathsIterator::StateInfo info;
|
|
|
|
GPointF p1, p2;
|
|
|
|
while(ppathsIt->GetNext(&info))
|
|
{
|
|
switch (info.State)
|
|
{
|
|
case GFxShapeBase::PathsIterator::StateInfo::St_Setup:
|
|
{
|
|
Float ax = info.Setup.MoveX, ay = info.Setup.MoveY;
|
|
|
|
path.Reset();
|
|
path.SetFill0(1);
|
|
path.SetFill1(0);
|
|
|
|
p1.x = ax + offx;
|
|
p1.y = ay + offy;
|
|
if (fauxItalic)
|
|
p1 = ItalicMtx[rti].Transform(p1);
|
|
path.SetMoveTo(SInt(p1.x), SInt(p1.y));
|
|
}
|
|
break;
|
|
case GFxShapeBase::PathsIterator::StateInfo::St_Edge:
|
|
if (info.Edge.Curve)
|
|
{
|
|
p1.x = info.Edge.Cx + offx;
|
|
p1.y = info.Edge.Cy + offy;
|
|
p2.x = info.Edge.Ax + offx;
|
|
p2.y = info.Edge.Ay + offy;
|
|
if (fauxItalic)
|
|
{
|
|
p1 = ItalicMtx[rti].Transform(p1);
|
|
p2 = ItalicMtx[rti].Transform(p2);
|
|
}
|
|
path.CurveToAbs(SInt(p1.x), SInt(p1.y), SInt(p2.x), SInt(p2.y));
|
|
}
|
|
else
|
|
{
|
|
p1.x = info.Edge.Ax + offx;
|
|
p1.y = info.Edge.Ay + offy;
|
|
if (fauxItalic)
|
|
p1 = ItalicMtx[rti].Transform(p1);
|
|
path.LineToAbs(SInt(p1.x), SInt(p1.y));
|
|
}
|
|
break;
|
|
case GFxShapeBase::PathsIterator::StateInfo::St_NewPath:
|
|
case GFxShapeBase::PathsIterator::StateInfo::St_NewShape:
|
|
dst->AddPath(&path);
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
if (fauxBold || fauxItalic)
|
|
{
|
|
dst->ComputeBound(bbox);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
GFxShapeBase*
|
|
GFxFontCacheManagerImpl::GetGlyphShape(GFxFontResource* font, UInt index, UInt size,
|
|
bool fauxBold, bool fauxItalic,
|
|
Float offx, Float offy, UInt outline,
|
|
GFxLog* log)
|
|
{
|
|
//return font->GetGlyphShape(index, 0); // DBG
|
|
GLock::Locker guard(&StateLock);
|
|
return GetGlyphShape_NoLock(font,index, size, fauxBold, fauxItalic, offx, offy, outline, log);
|
|
}
|
|
GFxShapeBase*
|
|
GFxFontCacheManagerImpl::GetGlyphShape_NoLock(GFxFontResource* font, UInt index, UInt size,
|
|
bool fauxBold, bool fauxItalic,
|
|
Float offx, Float offy, UInt outline,
|
|
GFxLog* log)
|
|
{
|
|
//return font->GetGlyphShape(index, 0); // DBG
|
|
if (!KnownFonts.Get(font))
|
|
{
|
|
KnownFonts.Add(font);
|
|
font->AddDisposeHandler(&FontDisposer);
|
|
}
|
|
|
|
// At this point the font size makes sense only if the font
|
|
// supports native hinting. Otherwise it will be just a waste
|
|
// of cache capacity. Also, check for the maximum hinted size.
|
|
if (!font->HasNativeHinting() ||
|
|
!font->IsHintedVectorGlyph(index, size))
|
|
{
|
|
size = 0;
|
|
}
|
|
|
|
UInt flags = (fauxBold ? GFxFont::FF_Bold : 0) |
|
|
(fauxItalic ? GFxFont::FF_Italic : 0);
|
|
|
|
VectorGlyphShape** shapePtr = VectorGlyphCache.Get(VectorGlyphKey(font, index, size, flags, UInt8(outline)));
|
|
if (shapePtr)
|
|
{
|
|
VectorGlyphShapeList.SendToBack(*shapePtr);
|
|
if (LockAllInFrame)
|
|
(*shapePtr)->Flags |= VectorGlyphShape::LockGlyphFlag;
|
|
(*shapePtr)->pShape->AddRef();
|
|
return (*shapePtr)->pShape;
|
|
}
|
|
|
|
GPtr<GFxShapeBase> srcShapePtr = *font->GetGlyphShape(index, size);
|
|
if (srcShapePtr.GetPtr() == 0)
|
|
return 0;
|
|
|
|
VectorGlyphShape* shape;
|
|
if (VectorGlyphCache.GetSize() >= MaxVectorCacheSize)
|
|
{
|
|
shape = VectorGlyphShapeList.GetFirst();
|
|
if (shape->Flags & VectorGlyphShape::LockGlyphFlag)
|
|
{
|
|
if (log && VectorCacheWarning)
|
|
log->LogWarning("Warning: Increase vector glyph cache capacity - SetMaxVectorCacheSize().\n");
|
|
VectorCacheWarning = false;
|
|
return 0;
|
|
}
|
|
|
|
VectorGlyphCache.Remove(VectorGlyphKey(shape->pFont,
|
|
shape->GlyphIndex,
|
|
shape->HintedGlyphSize,
|
|
shape->Flags,
|
|
shape->Outline));
|
|
VectorGlyphShapeList.Remove(shape);
|
|
VectorGlyphShapeStorage.Free(shape);
|
|
}
|
|
|
|
shape = VectorGlyphShapeStorage.Alloc();
|
|
shape->pFont = font;
|
|
shape->GlyphIndex = (UInt16)index;
|
|
shape->HintedGlyphSize = (UInt8)size;
|
|
shape->Flags = (UInt8)flags;
|
|
shape->Outline = (UInt8)outline;
|
|
shape->pShape = *GHEAP_NEW(pHeap) GFxShapeNoStyles(ShapePageSize);
|
|
|
|
GRectF bbox;
|
|
|
|
if (srcShapePtr->GetHintedGlyphSize())
|
|
srcShapePtr->ComputeBound(&bbox);
|
|
else
|
|
font->GetGlyphBounds(index, &bbox);
|
|
|
|
copyAndTransformShape(shape->pShape, srcShapePtr, &bbox, fauxBold, fauxItalic, offx, offy, outline);
|
|
shape->pShape->SetBound(bbox);
|
|
shape->pShape->SetValidBoundsFlag(true);
|
|
shape->pShape->SetNonZeroFill(true);
|
|
shape->pShape->SetHintedGlyphSize(srcShapePtr->GetHintedGlyphSize());
|
|
|
|
VectorGlyphShapeList.PushBack(shape);
|
|
VectorGlyphCache.Add(VectorGlyphKey(font, index, size, flags, UInt8(outline)), shape);
|
|
|
|
if (LockAllInFrame)
|
|
shape->Flags |= VectorGlyphShape::LockGlyphFlag;
|
|
|
|
shape->pShape->AddRef();
|
|
return shape->pShape;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::UnlockAllGlyphs()
|
|
{
|
|
GLock::Locker guard(&StateLock);
|
|
|
|
if (LockAllInFrame)
|
|
{
|
|
if (--LockedFrame == 0)
|
|
{
|
|
LockedFrame = NumLockedFrames;
|
|
VectorGlyphShape* shape = VectorGlyphShapeList.GetFirst();
|
|
while(!VectorGlyphShapeList.IsNull(shape))
|
|
{
|
|
shape->Flags &= ~VectorGlyphShape::LockGlyphFlag;
|
|
shape = VectorGlyphShapeList.GetNext(shape);
|
|
}
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.UnlockAllGlyphs();
|
|
#endif
|
|
}
|
|
}
|
|
RasterCacheWarning = true;
|
|
VectorCacheWarning = true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::RenEventHandler::OnEvent(GRenderer* prenderer, GRendererEventHandler::EventType eventType)
|
|
{
|
|
pSelf->UnlockAllGlyphs();
|
|
if (eventType == GRendererEventHandler::Event_RendererReleased)
|
|
{
|
|
if (pSelf->pRenderer && pSelf->pRenderer == prenderer)
|
|
{
|
|
pSelf->pRenderer->RemoveEventHandler(&pSelf->FrameHandler);
|
|
pSelf->pRenderer = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::InitTextures(GRenderer* ren)
|
|
{
|
|
setRenderer(ren);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
Cache.InitTextures(ren);
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::VisitGlyphs(GFxGlyphCacheVisitor* visitor) const
|
|
{
|
|
GUNUSED(visitor);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
GLock::Locker locker(&StateLock);
|
|
Cache.VisitGlyphs(visitor);
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GFxFontCacheManagerImpl::SetEventHandler(class GFxGlyphCacheEventHandler* h)
|
|
{
|
|
GUNUSED(h);
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
GLock::Locker locker(&StateLock);
|
|
Cache.SetEventHandler(h);
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
UInt GFxFontCacheManagerImpl::ComputeUsedArea() const
|
|
{
|
|
#ifndef GFC_NO_GLYPH_CACHE
|
|
GLock::Locker locker(&StateLock);
|
|
return Cache.ComputeUsedArea();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|