Files
GTASource/rage/scaleform/Src/GFxPlayer/GFxGlyphCache.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2061 lines
66 KiB
C++

/*********************************************************************
Filename : GFxGlyphCache.cpp
Content :
Created :
Authors : Maxim Shemanarev
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.
----------------------------------------------------------------------
The code of stackBlur and recursiveBlur was taken from the Anti-Grain
Geometry Project and modified for the use by Scaleform.
Permission to use without restrictions is hereby granted to
Scaleform Corporation by the author of Anti-Grain Geometry Project.
See http://antigtain.com for details.
**********************************************************************/
#ifndef GFC_NO_GLYPH_CACHE
#include "GFxGlyphCache.h"
#include "GFxDisplayContext.h"
#include "GFxShape.h"
#include "GFxFont.h"
#include "GFxAmpServer.h"
//------------------------------------------------------------------------
GFxGlyphSlotQueue::GFxGlyphSlotQueue() :
MinSlotSpace(10),
FirstTexture(0),
NumTextures(0),
TextureWidth(0),
TextureHeight(0),
MaxSlotHeight(0),
NumBandsInTexture(0),
SlotQueueSize(0),
NumUsedBands(0),
pEventHandler(0)
{}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::Clear()
{
GlyphHTable.Clear();
SlotQueue.Clear();
ActiveSlots.Clear();
for (UInt i = 0; i < NumUsedBands; ++i)
Bands[i].Slots.Clear();
Slots.ClearAndRelease();
Glyphs.ClearAndRelease();
SlotQueueSize = 0;
NumUsedBands = 0;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::Init(UInt firstTexture, UInt numTextures,
UInt textureWidth, UInt textureHeight,
UInt maxSlotHeight)
{
Clear();
FirstTexture = firstTexture;
NumTextures = numTextures;
TextureWidth = textureWidth;
TextureHeight = textureHeight;
MaxSlotHeight = maxSlotHeight;
NumBandsInTexture = textureHeight / maxSlotHeight;
Bands.Resize(NumBandsInTexture * NumTextures);
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::packGlyph(UInt w, UInt h, GFxGlyphNode* glyph)
{
// A recursive procedure that performs Binary Space Partitioning.
// It finds an appropriate suitable rectangle and subdivides the
// rest of the space.
// The method is described in details here:
// http://www.blackpawn.com/texts/lightmaps/default.html
//
// If glyph->pFont is zero it means an empty space. The children nodes
// are pNext and pNex2. Name pNext is required by the GPodStructAllocator
// and reused in the tree.
//--------------------------
if (glyph->Param.pFont)
{
GFxGlyphNode* newGlyph = 0;
if (glyph->pNext)
newGlyph = packGlyph(w, h, glyph->pNext);
if (newGlyph)
return newGlyph;
if (glyph->pNex2)
newGlyph = packGlyph(w, h, glyph->pNex2);
return newGlyph;
}
if (w <= glyph->Rect.w && h <= glyph->Rect.h)
{
UInt dw = glyph->Rect.w - w;
UInt dh = glyph->Rect.h - h;
// Subdivide space only if it's big enough. If it's not,
// just use it as a whole.
//----------------
if (dw >= MinSlotSpace || dh >= MinSlotSpace)
{
if (dw > dh)
{
glyph->pNext = Glyphs.Alloc(*glyph);
glyph->pNext->Rect.x = UInt16(glyph->Rect.x + w);
glyph->pNext->Rect.w = UInt16(dw);
if (dh >= MinSlotSpace)
{
glyph->pNex2 = Glyphs.Alloc(*glyph);
glyph->pNex2->pNext = 0;
glyph->pNex2->Rect.y = UInt16(glyph->Rect.y + h);
glyph->pNex2->Rect.h = UInt16(dh);
glyph->pNex2->Rect.w = UInt16(w);
}
}
else
{
glyph->pNext = Glyphs.Alloc(*glyph);
glyph->pNext->Rect.y = UInt16(glyph->Rect.y + h);
glyph->pNext->Rect.h = UInt16(dh);
if (dw >= MinSlotSpace)
{
glyph->pNex2 = Glyphs.Alloc(*glyph);
glyph->pNex2->pNext = 0;
glyph->pNex2->Rect.x = UInt16(glyph->Rect.x + w);
glyph->pNex2->Rect.w = UInt16(dw);
glyph->pNex2->Rect.h = UInt16(h);
}
}
}
glyph->Rect.w = UInt16(w);
glyph->Rect.h = UInt16(h);
return glyph;
}
return 0;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::splitSlot(GFxGlyphDynaSlot* slot, UInt w)
{
// Split the slot as "w" and "slot->w - w". The slot must be
// empty, but with the initialized BSP root. These conditions
// are preserved by the callers.
//-----------------------
GFxGlyphDynaSlot* newSlot = initNewSlot(slot->pBand, slot->x + w, slot->w - w);
slot->w = UInt16(w);
slot->pRoot->Rect.w = UInt16(w);
SlotQueue.PushFront(newSlot);
SlotQueueSize++;
slot->pBand->Slots.InsertAfter(slot, newSlot);
ActiveSlots.PushFront(newSlot);
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::splitGlyph(GFxGlyphDynaSlot* slot, bool left, UInt w)
{
GFxGlyphNode* root = slot->pRoot;
GFxGlyphDynaSlot* newSlot;
UInt newW = root->Rect.w - w;
if (left)
{
newSlot = initNewSlot(slot->pBand, root->Rect.x, newW);
slot->pBand->Slots.InsertBefore(slot, newSlot);
slot->x = UInt16(slot->x + newW);
root->Rect.x = slot->x;
}
else
{
newSlot = initNewSlot(slot->pBand, root->Rect.x + w, newW);
slot->pBand->Slots.InsertAfter(slot, newSlot);
}
root->Rect.w = UInt16(w);
slot->w = UInt16(slot->w - newW);
SlotQueue.PushFront(newSlot);
SlotQueueSize++;
ActiveSlots.PushFront(newSlot);
}
//------------------------------------------------------------------------
inline GFxGlyphNode* GFxGlyphSlotQueue::packGlyph(UInt w, UInt h, GFxGlyphDynaSlot* slot)
{
// Check to see if the slot is empty (no glyph allocated and
// no BSP tree), and its width is twice as much than requested,
// split it. This splits considerably reduce the number of
// BSP tree operations.
//-------------------------
GFxGlyphNode* glyph = slot->pRoot;
if (glyph->Param.pFont == 0 && slot->w > 2*w)
{
if (glyph->pNext == 0 && glyph->pNex2 == 0)
{
splitSlot(slot, w);
}
else
if (glyph->pNex2 == 0 &&
glyph->Rect.h == slot->pBand->h &&
glyph->Rect.w > 2*w)
{
// The slot may have a big empty glyph after merging,
// which is also good to split.
//---------------------
bool left = glyph->Rect.x == slot->x;
bool right = glyph->Rect.x + glyph->Rect.w == slot->x + slot->w;
if (left != right)
splitGlyph(slot, left, w);
}
}
// An attempt to pack the glyph. First, the recursive packGlyph()
// is called, then, if it fails, the number of failures is increased.
// If this number exceeds a certain value (MaxSlotFailures) the slot
// is excluded from search. In case packing succeeds the number of failures
// is decreased to keep the slot active.
//-----------------------
glyph = packGlyph(w, h, slot->pRoot);
if (glyph == 0)
slot->Failures++;
if (slot->Failures > MaxSlotFailures)
{
ActiveSlots.Remove(slot);
slot->TextureId |= GFxGlyphDynaSlot::FullFlag;
}
else
if (slot->Failures && glyph)
slot->Failures--;
return glyph;
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::findSpaceInSlots(UInt w, UInt h)
{
// Search for empty space in the active slots.
//-----------------------------
//// Iterate trough the active slots and try to pack. If
//// packing fails, but the slot still remains active, move
//// it to the back of the active slot chain, as most probably
//// the next glyph will fail too.
////---------------------------
//GFxGlyphDynaSlot* slot = ActiveSlots.GetFirst();
//GFxGlyphDynaSlot* last = ActiveSlots.GetLast();
//while (!ActiveSlots.IsNull(slot) && slot != last)
//{
// GFxGlyphDynaSlot* next = ActiveSlots.GetNext(slot);
// GFxGlyphNode* glyph = packGlyph(w, h, slot);
// if (glyph)
// return glyph;
// if ((slot->TextureId & GFxGlyphDynaSlot::FullFlag) == 0)
// ActiveSlots.SendToBack(slot);
// slot = next;
//}
//return ActiveSlots.IsNull(last) ? 0 : packGlyph(w, h, last);
// Simple variant. Just iterate trough the active slots
// and try to pack, keeping the active slot chain as is.
//----------------------------
GFxGlyphDynaSlot* slot = ActiveSlots.GetFirst();
while (!ActiveSlots.IsNull(slot))
{
GFxGlyphDynaSlot* next = ActiveSlots.GetNext(slot);
GFxGlyphNode* glyph = packGlyph(w, h, slot);
if (glyph)
return glyph;
slot = next;
}
return 0;
}
//------------------------------------------------------------------------
GFxGlyphDynaSlot* GFxGlyphSlotQueue::initNewSlot(GFxGlyphBand* band, UInt x, UInt w)
{
// Allocate and init a new slot and the BSP tree root.
//----------------------
GFxGlyphDynaSlot* slot = Slots.Alloc();
GFxGlyphNode* glyph = Glyphs.Alloc();
slot->pRoot = glyph;
slot->pBand = band;
slot->TextureId = band->TextureId;
slot->x = UInt16(x);
slot->w = UInt16(w);
slot->Failures = 0;
glyph->Param.Clear();
glyph->pSlot = slot;
glyph->pNext = 0;
glyph->pNex2 = 0;
glyph->Rect = GFxGlyphRect(slot->x, band->y, slot->w, band->h);
glyph->Origin.x = 0;
glyph->Origin.y = 0;
return slot;
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::allocateNewSlot(UInt w, UInt h)
{
// Allocate a new slot if available. First, check for the necessity
// to initialize a new band.
//----------------------
if (NumUsedBands == 0 || Bands[NumUsedBands-1].RightSpace < w)
{
if (NumUsedBands < Bands.GetSize())
{
UInt bandInTexture = NumUsedBands % NumBandsInTexture;
GFxGlyphBand& band = Bands[NumUsedBands];
band.TextureId = UInt16(FirstTexture + NumUsedBands / NumBandsInTexture);
band.y = UInt16(bandInTexture * MaxSlotHeight);
band.h = UInt16((bandInTexture+1 == NumBandsInTexture) ?
TextureHeight - band.y : MaxSlotHeight);
band.RightSpace = UInt16(TextureWidth);
band.Slots.Clear();
++NumUsedBands;
}
}
// Check for the empty space in the right of the last band.
// If there is any and it fits the glyph, initialize the new slot.
// Also, check for the rest of the space. If it's less than 2*w
// merge the rest with the slot.
// -----------------------
GFxGlyphBand& band = Bands[NumUsedBands-1];
if (w <= band.RightSpace)
{
UInt dw = band.RightSpace - w;
UInt ws = (dw < w) ? band.RightSpace : w;
GFxGlyphDynaSlot* slot = initNewSlot(&band, TextureWidth - band.RightSpace, ws);
band.RightSpace = UInt16(band.RightSpace - slot->w);
SlotQueue.PushBack(slot);
SlotQueueSize++;
band.Slots.PushBack(slot);
ActiveSlots.PushFront(slot);
return packGlyph(w, h, slot);
}
return 0;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::releaseGlyphTree(GFxGlyphNode* glyph)
{
// Recursively release the glyph BSP tree and remove the
// glyphs from the hash table.
//----------------------
if (glyph)
{
releaseGlyphTree(glyph->pNext);
releaseGlyphTree(glyph->pNex2);
if (glyph->Param.pFont)
{
if (pEventHandler)
{
GRectF uvRect;
uvRect.Left = Float(glyph->Rect.x);
uvRect.Top = Float(glyph->Rect.y);
uvRect.Right = Float(glyph->Rect.x + glyph->Rect.w);
uvRect.Bottom = Float(glyph->Rect.y + glyph->Rect.h);
pEventHandler->OnEvictGlyph(glyph->Param, uvRect,
glyph->pSlot->TextureId & ~GFxGlyphDynaSlot::Mask);
}
GlyphHTable.Remove(glyph->Param);
}
glyph->Param.pFont = 0;
Glyphs.Free(glyph);
}
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::releaseSlot(GFxGlyphDynaSlot* slot)
{
// Release the slot. The whole BSP tree is being released, except
// for the root. The root glyph is also released (if any), but
// the space is initialized as empty to be reused later.
//-----------------------
releaseGlyphTree(slot->pRoot->pNext);
releaseGlyphTree(slot->pRoot->pNex2);
if (slot->pRoot->Param.pFont)
GlyphHTable.Remove(slot->pRoot->Param);
GFxGlyphBand* band = slot->pBand;
if (band->RightSpace && band->Slots.IsLast(slot))
{
// Merge the right space with the slot.
slot->w = UInt16(slot->w + band->RightSpace);
band->RightSpace = 0;
}
slot->pRoot->Param.pFont = 0;
slot->pRoot->Rect = GFxGlyphRect(slot->x, slot->pBand->y, slot->w, slot->pBand->h);
slot->pRoot->pNext = 0;
slot->pRoot->pNex2 = 0;
slot->Failures = 0;
if (slot->TextureId & GFxGlyphDynaSlot::FullFlag)
{
slot->TextureId &= ~GFxGlyphDynaSlot::FullFlag;
ActiveSlots.PushFront(slot);
}
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::mergeSlots(GFxGlyphDynaSlot* from, GFxGlyphDynaSlot* to, UInt w)
{
// Merge slots "from...to" in the band. All slots are being released
// and the ones on the right of "from" are removed.
//---------------------
GFxGlyphDynaSlot* slot = from;
GFxGlyphBand* band = slot->pBand;
GUNUSED(band); // Suppress silly MSVC warning
for (;;)
{
GFxGlyphDynaSlot* next = band->Slots.GetNext(slot);
releaseSlot(slot);
if (slot != from)
{
Glyphs.Free(slot->pRoot);
SlotQueue.Remove(slot);
SlotQueueSize--;
if ((slot->TextureId & GFxGlyphDynaSlot::FullFlag) == 0)
{
ActiveSlots.Remove(slot);
}
band->Slots.Remove(slot);
Slots.Free(slot);
}
if (slot == to)
break;
slot = next;
}
from->w = UInt16(w);
from->pRoot->Rect.w = UInt16(w);
SlotQueue.BringToFront(from);
}
//------------------------------------------------------------------------
GFxGlyphDynaSlot* GFxGlyphSlotQueue::mergeSlotWithNeighbor(GFxGlyphDynaSlot* slot)
{
// Merge the slot with its right or left neighbor in the band.
// This procedure prevents the old slots from "hanging" in the queue
// for a long time.
//-----------------------
GFxGlyphBand* band = slot->pBand;
GFxGlyphDynaSlot* target = band->Slots.GetNext(slot);
bool toRight = true;
if (band->Slots.IsNull(target))
{
target = band->Slots.GetPrev(slot);
toRight = false;
}
if (!band->Slots.IsNull(target))
{
GFxGlyphNode* slotGlyph = slot->pRoot;
GFxGlyphNode* targetGlyph = target->pRoot;
releaseSlot(slot);
UInt x = slot->x;
UInt w = slot->w;
SlotQueue.Remove(slot);
SlotQueueSize--;
if ((slot->TextureId & GFxGlyphDynaSlot::FullFlag) == 0)
{
ActiveSlots.Remove(slot);
}
band->Slots.Remove(slot);
Slots.Free(slot);
bool mergeRect = false;
if (targetGlyph->Param.pFont == 0 &&
targetGlyph->pNex2 == 0 &&
targetGlyph->Rect.h == band->h)
{
mergeRect = toRight ?
(x + w == UInt(targetGlyph->Rect.x)) :
(UInt(targetGlyph->Rect.x + targetGlyph->Rect.w) == x);
}
if (mergeRect)
{
Glyphs.Free(slotGlyph);
if (toRight)
targetGlyph->Rect.x = UInt16(x);
targetGlyph->Rect.w = UInt16(targetGlyph->Rect.w + w);
}
else
{
slotGlyph->pNext = targetGlyph;
slotGlyph->pNex2 = 0;
slotGlyph->pSlot = target;
slotGlyph->Rect = GFxGlyphRect(x, band->y, w, band->h);
target->pRoot = slotGlyph;
}
if (toRight)
target->x = UInt16(x);
target->w = UInt16(target->w + w);
if (target->TextureId & GFxGlyphDynaSlot::FullFlag)
{
target->Failures = 0;
target->TextureId &= ~GFxGlyphDynaSlot::FullFlag;
ActiveSlots.PushFront(target);
}
return target;
}
return 0;
}
//------------------------------------------------------------------------
bool GFxGlyphSlotQueue::checkDistance(GFxGlyphDynaSlot* slot) const
{
// Check the distance to the beginning. The function returns
// true if the slot is closer to the beginning of the queue.
//------------------
GFxGlyphDynaSlot* slot1 = slot;
GFxGlyphDynaSlot* slot2 = slot;
for(;;)
{
if (SlotQueue.IsNull(slot1))
return true;
if (SlotQueue.IsNull(slot2))
break;
slot1 = SlotQueue.GetPrev(slot1);
slot2 = SlotQueue.GetNext(slot2);
}
return false;
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::extrudeOldSlot(UInt w, UInt h)
{
// Try to find an appropriate slot. First, iterate through the
// slot queue and find the slot of the required width. To keep
// the LRU strategy in action, we iterate through only the first
// half of the queue. Then, if the attempt fails, try to
// merge slots.
//-------------------
GFxGlyphDynaSlot* slot = SlotQueue.GetFirst();
UInt i;
UInt queueLimit = SlotQueueSize / 2;
for (i = 0; i <= queueLimit && !SlotQueue.IsNull(slot); ++i)
{
if ((slot->TextureId & GFxGlyphDynaSlot::LockFlag) == 0)
{
if (UInt(slot->w) >= w)
{
releaseSlot(slot); // Slot is found, release it and pack.
return packGlyph(w, h, slot);
}
else
{
// In case the slot is narrower than requested,
// merge it with its neighbor. It prevents small glyphs
// from "hanging" in the beginning of the queue for a
// long time.
//--------------------
GFxGlyphDynaSlot* mergedWith = mergeSlotWithNeighbor(slot);
if (mergedWith)
{
if (mergedWith->pRoot->Rect.w >= w)
{
return packGlyph(w, h, mergedWith);
}
break;
}
}
}
slot = SlotQueue.GetNext(slot);
}
// An attempt to find the existing slot failed. Try to merge.
// To do this we iterate the slots along the band until the
// required width is reached.
//--------------------
slot = SlotQueue.GetFirst();
while (!SlotQueue.IsNull(slot))
{
if ((slot->TextureId & GFxGlyphDynaSlot::LockFlag) == 0)
{
UInt w2 = 0;
GFxGlyphDynaSlot* to = slot;
GFxGlyphBand* band = slot->pBand;
while (!band->Slots.IsNull(to) &&
(to->TextureId & GFxGlyphDynaSlot::LockFlag) == 0)
{
if (to != slot && !checkDistance(to))
break;
// Check to see if there's enough space in the slot.
// If there is, reuse it without merging.
// It prevents from obtaining too wide slots.
//-----------------------
if (to->w >= w)
{
releaseSlot(to);
return packGlyph(w, h, to);
}
w2 += to->w;
// Try to incorporate space on the right of the band
// if there is any.
//-----------------------
if (band->RightSpace &&
band->Slots.IsLast(to) &&
w2 + band->RightSpace >= w)
{
w2 += band->RightSpace;
band->RightSpace = 0;
}
// Merge slots slot...to in the band.
if (w2 >= w)
{
mergeSlots(slot, to, w2);
return packGlyph(w, h, slot);
}
to = band->Slots.GetNext(to);
}
}
slot = SlotQueue.GetNext(slot);
}
return 0;
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::FindGlyph(const GFxGlyphParam& gp)
{
GFxGlyphNode** f = GlyphHTable.Get(gp);
return f ? *f : 0;
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphSlotQueue::AllocateGlyph(const GFxGlyphParam& gp, UInt w, UInt h)
{
if (h > MaxSlotHeight || w == 0 || h == 0)
{
return 0; // Prevent from storing too tall and zero glyphs
}
if (h < MinSlotSpace) MinSlotSpace = h;
if (w < MinSlotSpace) MinSlotSpace = w;
GFxGlyphNode* glyph = findSpaceInSlots(w, h);
if (glyph == 0)
{
glyph = allocateNewSlot(w, h);
if (glyph == 0)
glyph = extrudeOldSlot(w, h);
// DBG
//if(glyph) printf("X");
}
if (glyph)
{
glyph->Param = gp;
glyph->Origin.x = 0;
glyph->Origin.y = 0;
SendGlyphToBack(glyph);
if(!GlyphHTable.Get(gp))
GlyphHTable.Add(gp, glyph);
}
return glyph;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::UnlockAllGlyphs()
{
GFxGlyphDynaSlot* slot = SlotQueue.GetFirst();
while(!SlotQueue.IsNull(slot))
{
slot->TextureId &= ~GFxGlyphDynaSlot::LockFlag;
slot = SlotQueue.GetNext(slot);
}
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::computeGlyphArea(const GFxGlyphNode* glyph, UInt* used) const
{
if (glyph)
{
if (glyph->Param.pFont)
*used += glyph->Rect.w * glyph->Rect.h;
computeGlyphArea(glyph->pNext, used);
computeGlyphArea(glyph->pNex2, used);
}
}
//------------------------------------------------------------------------
UInt GFxGlyphSlotQueue::ComputeUsedArea() const
{
UInt totalUsed = 0;
const GFxGlyphDynaSlot* slot = SlotQueue.GetFirst();
while(!SlotQueue.IsNull(slot))
{
UInt used = 0;
computeGlyphArea(slot->pRoot, &used);
totalUsed += used;
slot = SlotQueue.GetNext(slot);
}
return totalUsed;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::CleanUpTexture(UInt textureId)
{
GFxGlyphDynaSlot* slot = SlotQueue.GetFirst();
while(!SlotQueue.IsNull(slot))
{
GFxGlyphDynaSlot* next = SlotQueue.GetNext(slot);
if (UInt(slot->TextureId & ~GFxGlyphDynaSlot::Mask) == textureId)
{
releaseSlot(slot);
SlotQueue.BringToFront(slot);
}
slot = next;
}
}
//------------------------------------------------------------------------
const GFxGlyphNode* GFxGlyphSlotQueue::findFontInSlot(GFxGlyphNode* glyph,
const GFxFontResource* font)
{
if (glyph)
{
if (glyph->Param.pFont == font)
return glyph;
const GFxGlyphNode* g2 = findFontInSlot(glyph->pNext, font);
if (g2)
return g2;
return findFontInSlot(glyph->pNex2, font);
}
return 0;
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::CleanUpFont(const GFxFontResource* font)
{
GFxGlyphDynaSlot* slot = SlotQueue.GetFirst();
while(!SlotQueue.IsNull(slot))
{
GFxGlyphDynaSlot* next = SlotQueue.GetNext(slot);
if (findFontInSlot(slot->pRoot, font))
{
releaseSlot(slot);
SlotQueue.BringToFront(slot);
}
slot = next;
}
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::VisitGlyphs(GFxGlyphCacheVisitor* visitor) const
{
CachedGlyphsType::ConstIterator it = GlyphHTable.Begin();
const GFxGlyphNode* node;
GRectF uvRect;
for (; it != GlyphHTable.End(); ++it)
{
node = it->Second;
uvRect.Left = Float(node->Rect.x);
uvRect.Top = Float(node->Rect.y);
uvRect.Right = Float(node->Rect.x + node->Rect.w);
uvRect.Bottom = Float(node->Rect.y + node->Rect.h);
visitor->Visit(it->First, uvRect, node->pSlot->TextureId & ~GFxGlyphDynaSlot::Mask);
}
}
//------------------------------------------------------------------------
void GFxGlyphSlotQueue::SetEventHandler(GFxGlyphCacheEventHandler* h)
{
pEventHandler = h;
}
//------------------------------------------------------------------------
GFxGlyphRasterCache::GFxGlyphRasterCache():
TextureWidth(0),
TextureHeight(0),
MaxNumTextures(0),
MaxSlotHeight(0),
SlotPadding(0),
ScaleU(0),
ScaleV(0),
GlyphsToUpdate(),
RectsToUpdate(),
TexUpdBuffer(),
TexUpdPacker(0, 0),
KnockOutCopy(),
Shape(),
Rasterizer(),
ScanlineFilter(4.5f/9.0f, 2.0f/9.0f, 1.0f/19.0f),
Fitter(),
SlotQueue(),
pEventHandler(0)
{
for(UInt i = 0; i < TexturePoolSize; i++)
{
Textures[i].pTexture = 0;
Textures[i].NumGlyphsToUpdate = 0;
}
}
//------------------------------------------------------------------------
GFxGlyphRasterCache::~GFxGlyphRasterCache()
{
Clear();
}
// Clear the cache
//------------------------------------------------------------------------
void GFxGlyphRasterCache::Clear()
{
releaseAllTextures();
GlyphsToUpdate.Clear();
RectsToUpdate.Clear();
TexUpdPacker.Clear();
SlotQueue.Clear();
}
//
// Initialize or re-initialize the cache. The physical structure of the cache
// slots is shown above. The function also clears the cache.
//
// texWidth, texHeight - Texture size. Rounded to the nearest power of 2.
//
// maxNumTextures - The number of textures that is allowed to allocate.
// The cache can be extended later with ExtendCache().
//
// maxSlotHeight - Slot size in pixels.
//
// slotPadding - Size decrement value to compute the glyph rectangles.
//
// texUpdWidth, texUpdHeight - The buffer size to update textures. It allocates
// texUpdWidth*texUpdHeight bytes in system memory. The
// In Average 256x256 is capable to update about 30 big
// glyphs at a time. Size 256x256 is good enough for
// all practical purposes.
//------------------------------------------------------------------------
void GFxGlyphRasterCache::Init(UInt texWidth, UInt texHeight, UInt maxNumTextures,
UInt maxSlotHeight, UInt slotPadding,
UInt texUpdWidth, UInt texUpdHeight)
{
Clear();
UInt w = (texWidth < 64) ? 63 : texWidth - 1;
UInt h = (texHeight < 64) ? 63 : texHeight - 1;
UInt sw = 0;
UInt sh = 0;
while(w) { ++sw; w >>= 1; }
while(h) { ++sh; h >>= 1; }
if (maxNumTextures > TexturePoolSize)
maxNumTextures = TexturePoolSize;
TextureWidth = 1 << sw;
TextureHeight = 1 << sh;
MaxNumTextures = maxNumTextures;
MaxSlotHeight = maxSlotHeight;
SlotPadding = slotPadding;
ScaleU = 1.0f / (Float)TextureWidth;
ScaleV = 1.0f / (Float)TextureHeight;
TexUpdBuffer = *GHEAP_AUTO_NEW(this)
GImage(GImageBase::Image_A_8, (texUpdWidth + 3) & ~3, texUpdHeight);
TexUpdPacker = GFxTextureUpdatePacker(texUpdWidth, texUpdHeight);
SlotQueue.Init(0, maxNumTextures, texWidth, texHeight, maxSlotHeight);
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::releaseAllTextures()
{
for(UInt i = 0; i < MaxNumTextures; ++i)
{
if (Textures[i].pTexture)
{
Textures[i].pTexture->RemoveChangeHandler(&Textures[i].Handler);
Textures[i].pTexture->Release();
Textures[i].pTexture = 0;
}
Textures[i].NumGlyphsToUpdate = 0;
}
}
//------------------------------------------------------------------------
inline bool GFxGlyphRasterCache::initTextureIfRequired(GRenderer* ren,
UInt textureId)
{
TextureType* tex = &Textures[textureId];
if (tex->pTexture == 0)
{
tex->pTexture = ren->CreateTexture();
if (tex->pTexture == 0)
return false;
tex->Handler.Bind(this, textureId);
tex->pTexture->AddChangeHandler(&tex->Handler);
if (!tex->pTexture->InitDynamicTexture(TextureWidth,
TextureHeight,
GImage::Image_A_8,
0, GTexture::Usage_Update))
{
return false;
}
}
return true;
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::InitTextures(GRenderer* ren)
{
for (UInt i = 0; i < MaxNumTextures; ++i)
initTextureIfRequired(ren, i);
}
//------------------------------------------------------------------------
bool GFxGlyphRasterCache::RecreateTexture(UInt textureId)
{
TextureType* tex = &Textures[textureId];
return tex->pTexture->InitDynamicTexture(TextureWidth, TextureHeight,
GImage::Image_A_8, 0, GTexture::Usage_Update);
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::CleanUpTexture(UInt textureId, bool release)
{
SlotQueue.CleanUpTexture(textureId);
UInt i, j;
for(i = j = 0; i < GlyphsToUpdate.GetSize(); i++)
{
if (GlyphsToUpdate[i].TextureId != textureId)
GlyphsToUpdate[j++] = GlyphsToUpdate[i];
}
GlyphsToUpdate.CutAt(j);
Textures[textureId].NumGlyphsToUpdate = 0;
if (release)
{
Textures[textureId].pTexture->RemoveChangeHandler(&Textures[textureId].Handler);
Textures[textureId].pTexture->Release();
Textures[textureId].pTexture = 0;
}
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::CleanUpFont(const GFxFontResource* font)
{
SlotQueue.CleanUpFont(font);
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::filterScanline(const UByte* src, UByte* dst, UInt len)
{
UInt i;
for(i = 2; i+2 < len; ++i)
{
ScanlineFilter.Filter(src++, dst++);
}
}
//------------------------------------------------------------------------
// The code of stackBlur and recursiveBlur was taken from the Anti-Grain
// Geometry Project and modified for the use by Scaleform.
// Permission to use without restrictions is hereby granted to
// Scaleform Corporation by the author of Anti-Grain Geometry Project.
// See http://antigtain.com for details.
//------------------------------------------------------------------------
//------------------------------------------------------------------------
static UInt16 const GFx_StackBlurMul[255] =
{
512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
289,287,285,282,280,278,275,273,271,269,267,265,263,261,259
};
//------------------------------------------------------------------------
static UInt8 const GFx_StackBlurShr[255] =
{
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
};
//------------------------------------------------------------------------
void GFxGlyphRasterCache::stackBlur(GImage& img, UInt sx, UInt sy, UInt w, UInt h, UInt rx, UInt ry)
{
UInt x, y, xp, yp, i;
UInt stackPtr;
UInt stackStart;
const UInt8* srcPixPtr;
UInt8* dstPixPtr;
UInt pix;
UInt stackPix;
UInt sum;
UInt sumIn;
UInt sumOut;
UInt wm = w - 1;
UInt hm = h - 1;
UInt div;
UInt mulSum;
UInt shrSum;
if(rx > 0)
{
if(rx > 254) rx = 254;
div = rx * 2 + 1;
mulSum = GFx_StackBlurMul[rx];
shrSum = GFx_StackBlurShr[rx];
BlurStack.Resize(div);
for(y = 0; y < h; y++)
{
sum = sumIn = sumOut = 0;
srcPixPtr = img.GetScanline(sy + y) + sx;
pix = *srcPixPtr;
for(i = 0; i <= rx; i++)
{
BlurStack[i] = UInt8(pix);
sum += pix * (i + 1);
sumOut += pix;
}
for(i = 1; i <= rx; i++)
{
if(i <= wm) ++srcPixPtr;
pix = *srcPixPtr;
BlurStack[i + rx] = UInt8(pix);
sum += pix * (rx + 1 - i);
sumIn += pix;
}
stackPtr = rx;
xp = rx;
if(xp > wm) xp = wm;
srcPixPtr = img.GetScanline(sy + y) + sx + xp;
dstPixPtr = img.GetScanline(sy + y) + sx;
for(x = 0; x < w; x++)
{
*dstPixPtr++ = UInt8((sum * mulSum) >> shrSum);
sum -= sumOut;
stackStart = stackPtr + div - rx;
if(stackStart >= div) stackStart -= div;
sumOut -= BlurStack[stackStart];
if(xp < wm)
{
++srcPixPtr;
pix = *srcPixPtr;
++xp;
}
BlurStack[stackStart] = UInt8(pix);
sumIn += pix;
sum += sumIn;
++stackPtr;
if(stackPtr >= div) stackPtr = 0;
stackPix = BlurStack[stackPtr];
sumOut += stackPix;
sumIn -= stackPix;
}
}
}
if(ry > 0)
{
if(ry > 254) ry = 254;
div = ry * 2 + 1;
mulSum = GFx_StackBlurMul[ry];
shrSum = GFx_StackBlurShr[ry];
BlurStack.Resize(div);
int stride = img.Pitch;
for(x = 0; x < w; x++)
{
sum = sumIn = sumOut = 0;
srcPixPtr = img.GetScanline(sy) + sx + x;
pix = *srcPixPtr;
for(i = 0; i <= ry; i++)
{
BlurStack[i] = UInt8(pix);
sum += pix * (i + 1);
sumOut += pix;
}
for(i = 1; i <= ry; i++)
{
if(i <= hm) srcPixPtr += stride;
pix = *srcPixPtr;
BlurStack[i + ry] = UInt8(pix);
sum += pix * (ry + 1 - i);
sumIn += pix;
}
stackPtr = ry;
yp = ry;
if(yp > hm) yp = hm;
srcPixPtr = img.GetScanline(sy + yp) + sx + x;
dstPixPtr = img.GetScanline(sy) + sx + x;
for(y = 0; y < h; y++)
{
*dstPixPtr = UInt8((sum * mulSum) >> shrSum);
dstPixPtr += stride;
sum -= sumOut;
stackStart = stackPtr + div - ry;
if(stackStart >= div) stackStart -= div;
sumOut -= BlurStack[stackStart];
if(yp < hm)
{
srcPixPtr += stride;
pix = *srcPixPtr;
++yp;
}
BlurStack[stackStart] = UInt8(pix);
sumIn += pix;
sum += sumIn;
++stackPtr;
if(stackPtr >= div) stackPtr = 0;
stackPix = BlurStack[stackPtr];
sumOut += stackPix;
sumIn -= stackPix;
}
}
}
}
//------------------------------------------------------------------------
class GFxImgBlurWrapperX
{
public:
GFxImgBlurWrapperX(GImage& img, UInt x, UInt y, UInt w, UInt h) :
Img(img), Sx(x), Sy(y), W(w), H(h) {}
UInt GetWidth() const { return W; }
UInt GetHeight() const { return H; }
UInt8 GetPixel(UInt x, UInt y) const { return Img.GetScanline(Sy+y)[Sx+x]; }
void CopySpanTo(UInt x, UInt y, UInt len, const UInt8* buf)
{
memcpy(Img.GetScanline(Sy+y) + Sx+x, buf, len);
}
private:
GFxImgBlurWrapperX(const GFxImgBlurWrapperX&);
const GFxImgBlurWrapperX& operator = (const GFxImgBlurWrapperX&);
GImage& Img;
UInt Sx, Sy, W, H;
};
//------------------------------------------------------------------------
class GFxImgBlurWrapperY
{
public:
GFxImgBlurWrapperY(GImage& img, UInt x, UInt y, UInt w, UInt h) :
Img(img), Sx(x), Sy(y), W(w), H(h) {}
UInt GetWidth() const { return H; }
UInt GetHeight() const { return W; }
UInt8 GetPixel(UInt x, UInt y) const { return Img.GetScanline(Sy+x)[Sx+y]; }
void CopySpanTo(UInt x, UInt y, UInt len, const UInt8* buf)
{
UInt8* p = Img.GetScanline(Sy+x) + Sx+y;
do
{
*p = *buf++;
p += Img.Pitch;
}
while(--len);
}
private:
GFxImgBlurWrapperY(const GFxImgBlurWrapperY&);
const GFxImgBlurWrapperY& operator = (const GFxImgBlurWrapperY&);
GImage& Img;
UInt Sx, Sy, W, H;
};
//------------------------------------------------------------------------
template<class Img, class SumBuf, class ColorBuf>
void GFx_RecursiveBlur(Img& img, Float radius, SumBuf& sum, ColorBuf& buf)
{
if (radius < 0.62f)
radius = 0.62f;
int w = img.GetWidth();
int h = img.GetHeight();
int pad = int(ceilf(radius)) + 3;
int x, y;
float s = radius * 0.5f;
float q = (s < 2.5f) ?
3.97156f - 4.14554f * sqrtf(1 - 0.26891f * s) :
0.98711f * s - 0.96330f;
float q2 = q * q;
float q3 = q2 * q;
float b0 = 1.0f / (1.578250f + 2.444130f * q + 1.428100f * q2 + 0.422205f * q3);
float b2 = 2.44413f * q + 2.85619f * q2 + 1.26661f * q3;
float b3 = -1.42810f * q2 + -1.26661f * q3;
float b4 = 0.422205f * q3;
float b1 = 1.0f - (b2 + b3 + b4) * b0;
b2 *= b0;
b3 *= b0;
b4 *= b0;
sum.Resize(w + 2*pad);
buf.Resize(w + 2*pad);
for(y = 0; y < h; y++)
{
register float a1=0, a2=0, a3=0, a4=0;
for(x = 0; x < pad; ++x)
sum[x] = 0;
for(x = 0; x < w; ++x)
{
a1 = sum[x+pad] = b1*img.GetPixel(x, y) + b2*a2 + b3*a3 + b4*a4;
a4 = a3;
a3 = a2;
a2 = a1;
}
for(x = 0; x < pad; ++x)
{
a1 = sum[x+w+pad] = b2*a2 + b3*a3 + b4*a4;
a4 = a3;
a3 = a2;
a2 = a1;
}
a1 = a2 = a3 = a4 = 0;
for(x = w+pad+pad-1; x >= pad; --x)
{
a1 = b1*sum[x] + b2*a2 + b3*a3 + b4*a4;
a4 = a3;
a3 = a2;
a2 = a1;
buf[x] = UInt8(a1 + 0.5f);
}
img.CopySpanTo(0, y, w, &buf[pad]);
}
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::recursiveBlur(GImage& img,
UInt sx, UInt sy,
UInt w, UInt h,
Float rx, Float ry)
{
GFxImgBlurWrapperX imgWX(img, sx, sy, w, h);
GFx_RecursiveBlur( imgWX, rx, BlurSum, BlurStack);
GFxImgBlurWrapperY imgWY(img, sx, sy, w, h);
GFx_RecursiveBlur( imgWY, ry, BlurSum, BlurStack);
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::strengthenImage(GImage& img,
UInt sx, UInt sy,
UInt w, UInt h,
Float ratio,
SInt bias)
{
if (ratio != 1.0f)
{
UInt x, y;
for(y = 0; y < h; ++y)
{
UInt8* p = img.GetScanline(sy + y) + sx;
for(x = 0; x < w; ++x)
{
SInt v = SInt((int(*p) - bias) * ratio + 0.5f) + bias;
if (v < 0) v = 0;
if (v > 255) v = 255;
*p++ = UInt8(v);
}
}
}
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::makeKnockOutCopy(const GImage& img,
UInt sx, UInt sy,
UInt w, UInt h)
{
if (KnockOutCopy.GetPtr() == 0 ||
KnockOutCopy->Width < w ||
KnockOutCopy->Height < h)
{
KnockOutCopy = *GHEAP_AUTO_NEW(this)
GImage(GImageBase::Image_A_8, w + 16, h + 16);
}
UInt y;
for(y = 0; y < h; ++y)
{
const UInt8* src = img.GetScanline(sy + y) + sx;
UInt8* dst = KnockOutCopy->GetScanline(y);
memcpy(dst, src, w);
}
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::knockOut(GImage& img,
UInt sx, UInt sy,
UInt w, UInt h)
{
if (KnockOutCopy.GetPtr() != 0)
{
UInt x, y;
for(y = 0; y < h; ++y)
{
const UInt8* src = KnockOutCopy->GetScanline(y);
UInt8* dst = img.GetScanline(sy + y) + sx;
for(x = 0; x < w; ++x)
{
UInt s = 255 - *src++;
UInt d = *dst;
*dst++ = UInt8((s * d + 255) >> 8);
}
}
}
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::CalcGlyphParam(Float originalFontSize,
UInt snappedFontSize,
Float heightRatio,
Float glyphHeight,
const GFxGlyphParam& textParam,
GFxGlyphParam* glyphParam,
UInt16* subpixelSize,
UInt16* glyphScale) const
{
glyphParam->SetFontSize(snappedFontSize);
glyphParam->Flags = textParam.Flags;
glyphParam->BlurStrength = textParam.BlurStrength;
*subpixelSize = UInt16(snappedFontSize * SubpixelSizeScale);
Float k = Float(snappedFontSize) / originalFontSize;
if (k < 1.0f) k = 1.0f;
Float blurX = textParam.GetBlurX() * heightRatio * k;
Float blurY = textParam.GetBlurY() * heightRatio * k;
k = snappedFontSize / 1024.0f;
Float d = blurY;
Float height = glyphHeight * k + 2*d;
Float maxHeight = (Float)(GetMaxGlyphHeight() - 2);
*glyphScale = 256;
if (height > maxHeight)
{
k = Float(maxHeight) / Float(height);
*subpixelSize = UInt16(*subpixelSize * k);
*glyphScale = UInt16(k * 256.0f + 0.5f);
blurX *= k;
blurY *= k;
glyphParam->SetAutoFit(false);
}
if (blurX > GFxGlyphParam::GetMaxBlur())
blurX = GFxGlyphParam::GetMaxBlur();
if (blurY > GFxGlyphParam::GetMaxBlur())
blurY = GFxGlyphParam::GetMaxBlur();
glyphParam->SetBlurX(blurX);
glyphParam->SetBlurY(blurY);
// Correction of scale when Stack Blur is in use. The Stack
// Blur cannot have fractional radius, so that, we correct the
// glyph rectangles to make it look almost identical to
// fractional blurring radius.
//---------------------------------
if (!textParam.IsFineBlur() && !textParam.IsOptRead())
{
UInt intBlurY = UInt(glyphParam->GetBlurY() + 0.5f);
if (intBlurY == 0 && glyphParam->BlurY) intBlurY = 1;
if (Float(intBlurY) < blurY)
{
*subpixelSize = UInt16(*subpixelSize * intBlurY / blurY);
*glyphScale = UInt16(*glyphScale * intBlurY / blurY);
}
}
}
/*
// The code of this class was taken from the Anti-Grain Geometry
// Project and modified for the use by Scaleform.
// Permission to use without restrictions is hereby granted to
// Scaleform Corporation by the author of Anti-Grain Geometry Project.
// See http://antigrain.com for details.
//------------------------------------------------------------------------
class GFxSubpixelBresenhamLine
{
public:
enum SubpixelScale_e
{
SubpixelShift = 8,
SubpixelScale = 1 << SubpixelShift,
SubpixelMask = SubpixelScale - 1,
};
//--------------------------------------------------------------------
static int LineLowRes(int v) { return v >> SubpixelShift; }
//--------------------------------------------------------------------
GFxSubpixelBresenhamLine(int x1, int y1, int x2, int y2) :
X1LowRes(LineLowRes(x1)),
Y1LowRes(LineLowRes(y1)),
X2LowRes(LineLowRes(x2)),
Y2LowRes(LineLowRes(y2)),
Ver(abs(X2LowRes - X1LowRes) < abs(Y2LowRes - Y1LowRes)),
Len(Ver ? abs(Y2LowRes - Y1LowRes) :
abs(X2LowRes - X1LowRes)),
Inc(Ver ? ((y2 > y1) ? 1 : -1) : ((x2 > x1) ? 1 : -1)),
Interpolator(Ver ? x1 : y1, Ver ? x2 : y2, Len ? Len : 1)
{}
//--------------------------------------------------------------------
bool IsVer() const { return Ver; }
unsigned GetLen() const { return Len; }
int GetInc() const { return Inc; }
//--------------------------------------------------------------------
void HorStep()
{
++Interpolator;
X1LowRes += Inc;
}
//--------------------------------------------------------------------
void VerStep()
{
++Interpolator;
Y1LowRes += Inc;
}
//--------------------------------------------------------------------
int GetX1() const { return X1LowRes; }
int GetY1() const { return Y1LowRes; }
int GetX2() const { return LineLowRes(Interpolator.y()); }
int GetY2() const { return LineLowRes(Interpolator.y()); }
int GetX2HiRes() const { return Interpolator.y(); }
int GetY2HiRes() const { return Interpolator.y(); }
private:
int X1LowRes;
int Y1LowRes;
int X2LowRes;
int Y2LowRes;
bool Ver;
unsigned Len;
int Inc;
GLinearInterpolator Interpolator;
};
//------------------------------------------------------------------------
static void GFxDrawBresenhamLine(GImage* img, int originX, int originY,
int x1, int y1, int x2, int y2, UByte value)
{
GFxSubpixelBresenhamLine line(x1, y1, x2, y2);
unsigned len = line.GetLen();
if (len)
{
if(line.IsVer())
{
do
{
img->GetScanline(originY + line.GetY1())[originX + line.GetX2()] = value;
line.VerStep();
}
while(--len);
}
else
{
do
{
img->GetScanline(originY + line.GetY2())[originX + line.GetX1()] = value;
line.HorStep();
}
while(--len);
}
}
}
//------------------------------------------------------------------------
static void GFxDrawGlyphOutline(GImage* img, int originX, int originY,
const GCompoundShape& shape)
{
unsigned i, j;
int x1, y1, x2, y2;
for(i = 0; i < shape.GetNumPaths(); i++)
{
const GCompoundShape::SPath& path = shape.GetPath(i);
if(path.GetLeftStyle() != path.GetRightStyle())
{
x1 = int(path.GetVertex(0).x * 256);
y1 = int(path.GetVertex(0).y * 256);
for(j = 1; j < path.GetNumVertices(); j++)
{
x2 = int(path.GetVertex(j).x * 256);
y2 = int(path.GetVertex(j).y * 256);
GFxDrawBresenhamLine(img, originX, originY, x1, y1, x2, y2, 255);
x1 = x2;
y1 = y2;
}
}
}
}
//------------------------------------------------------------------------
static const UInt8 GFx_HintedGamma[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 23, 25, 27, 29,
31, 32, 34, 36, 38, 39, 41, 43, 45, 46, 48, 50, 51, 53, 55, 56,
58, 60, 61, 63, 64, 66, 68, 69, 71, 72, 74, 75, 77, 78, 80, 81,
83, 84, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99,101,102,103,105,
106,108,109,111,112,113,115,116,117,119,120,122,123,124,126,127,
128,130,131,132,134,135,136,137,139,140,141,143,144,145,146,148,
149,150,151,153,154,155,156,158,159,160,161,163,164,165,166,167,
169,170,171,172,173,175,176,177,178,179,181,182,183,184,185,186,
188,189,190,191,192,193,194,196,197,198,199,200,201,202,203,204,
206,207,208,209,210,211,212,213,214,215,217,218,219,220,221,222,
223,224,225,226,227,228,229,230,231,233,234,235,236,237,238,239,
240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
};
*/
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphRasterCache::rasterizeAndPack(GRenderer* ren,
const GFxGlyphParam& gp,
bool canUseRaster,
const GFxShapeBase* shape,
UInt subpixelSize,
GFxLog* log)
{
const GFxGlyphRaster* raster = 0;
if (canUseRaster)
raster = gp.pFont->GetGlyphRaster(gp.GlyphIndex, gp.FontSize);
if (shape == 0 && raster == 0)
return 0;
UInt i,j;
UInt stretch = gp.GetStretch();
UInt intBlurX = UInt(gp.GetBlurX() + 0.5f);
UInt intBlurY = UInt(gp.GetBlurY() + 0.5f);
if (intBlurX == 0 && gp.BlurX) intBlurX = 1;
if (intBlurY == 0 && gp.BlurY) intBlurY = 1;
Rasterizer.Clear();
if (raster == 0)
{
if (shape->GetHintedGlyphSize() == 0)
{
int lowerCaseTop = 0;
int upperCaseTop = 0;
bool autoFit = false;
if (gp.GetOutline() == 0 && gp.IsOptRead() && gp.IsAutoFit() &&
subpixelSize > 6*SubpixelSizeScale &&
(gp.pFont->HasLayout() || gp.pFont->IsDeviceFont()))
{
lowerCaseTop = gp.pFont->GetLowerCaseTop(log);
upperCaseTop = gp.pFont->GetUpperCaseTop(log);
if (lowerCaseTop && upperCaseTop)
autoFit = true;
}
shape->MakeCompoundShape(&Shape, GFxFontPackParams::GlyphBoundBox / MaxSlotHeight * 0.5f);
if (autoFit)
{
Fitter.Clear();
SInt nominalSize = subpixelSize * (64 / SubpixelSizeScale);
if (nominalSize > 2048)
nominalSize = 2048;
Fitter.SetNominalFontHeight(nominalSize);
Float k = nominalSize / 1024.0f;
for (i = 0; i < Shape.GetNumPaths(); ++i)
{
const GCompoundShape::SPath& path = Shape.GetPath(i);
if (path.GetNumVertices() > 2)
{
Fitter.MoveTo( SInt16(path.GetVertex(0).x * k),
-SInt16(path.GetVertex(0).y * k));
for (j = 1; j < path.GetNumVertices(); ++j)
{
Fitter.LineTo( SInt16(path.GetVertex(j).x * k),
-SInt16(path.GetVertex(j).y * k));
}
}
}
Fitter.FitGlyph(subpixelSize / SubpixelSizeScale, 0, int(lowerCaseTop * k), int(upperCaseTop * k));
k = 1.0f / Fitter.GetUnitsPerPixelY();
for(i = 0; i < Fitter.GetNumContours(); ++i)
{
const GFxGlyphFitter::ContourType& c = Fitter.GetContour(i);
if(c.NumVertices > 2)
{
GFxGlyphFitter::VertexType v = Fitter.GetVertex(c, 0);
Fitter.SnapVertex(v);
Rasterizer.MoveTo(v.x * k * stretch, -v.y * k);
for(j = 1; j < c.NumVertices; ++j)
{
v = Fitter.GetVertex(c, j);
Fitter.SnapVertex(v);
Rasterizer.LineTo(v.x * k * stretch, -v.y * k);
}
Rasterizer.ClosePolygon();
}
}
}
else
{
Rasterizer.AddShapeScaled(Shape,
subpixelSize * stretch / (1024.0f * SubpixelSizeScale),
subpixelSize / (1024.0f * SubpixelSizeScale),
0, 0);
}
}
else
{
shape->MakeCompoundShape(&Shape, 10);
// k is the scaling ratio. Normally it should be just 1/20, but when
// rendering a shadow the gp.FontSize (and corresponding subpixelSize)
// may be smaller because the shadow does not fit the maximal height.
// In this case the coefficient should consider it and scale the shape
// accordingly, as gp.FontSize/shape->GetHintedGlyphSize(), or more
// accurate, using the subpixelSize value.
Float k = Float(subpixelSize) / Float(shape->GetHintedGlyphSize() * SubpixelSizeScale * 20);
Rasterizer.AddShapeScaled(Shape, k * stretch, k, 0, 0);
}
}
GFxGlyphNode* glyph = 0;
SInt padX = SlotPadding + intBlurX;
SInt padY = SlotPadding + intBlurY;
SInt imgX1 = -padX;
SInt imgX2 = padX;
SInt imgY1 = -padY;
SInt imgY2 = padY;
bool cellsToSweep = false;
if (raster)
{
imgX1 = -raster->OriginX - padX;
imgY1 = -raster->OriginY - padY;
imgX2 = raster->Width - 1 - raster->OriginX + padX;
imgY2 = raster->Height - 1 - raster->OriginY + padY;
}
else
{
if (Rasterizer.SortCells())
{
imgX1 = Rasterizer.GetMinX() - padX;
imgX2 = Rasterizer.GetMaxX() + padX;
imgY1 = Rasterizer.GetMinY() - padY;
imgY2 = Rasterizer.GetMaxY() + padY;
cellsToSweep = true;
}
}
UInt imgW = imgX2 - imgX1 + 1;
UInt imgH = imgY2 - imgY1 + 1;
GImage& img = *TexUpdBuffer;
// RAGE - start - actually fail if the glyph is too big to be rasterized. AllocateGlyph will fail, and it will then be vectorized instead.
//if (imgH > GetMaxSlotHeight())
// imgH = GetMaxSlotHeight();
// RAGE - end
if (imgW > img.Width)
imgW = img.Width;
glyph = SlotQueue.AllocateGlyph(gp, imgW, imgH);
if (glyph == 0)
{
if (pEventHandler)
pEventHandler->OnFailure(gp, imgW, imgH);
return 0;
}
if (!initTextureIfRequired(ren, GetTextureId(glyph)))
return 0;
UInt imgX, imgY;
if (!TexUpdPacker.Allocate(imgW, imgH, &imgX, &imgY))
{
UpdateTextures(ren);
if (!TexUpdPacker.Allocate(imgW, imgH, &imgX, &imgY))
return 0;
}
glyph->Origin.x = SInt16(SInt(glyph->Rect.x) - imgX1);
glyph->Origin.y = SInt16(SInt(glyph->Rect.y) - imgY1);
for (i = 0; i < imgH; ++i)
memset(img.GetScanline(imgY + i) + imgX, 0, imgW);
// DBG
//for (i = 1; i < imgH-1; ++i)
//{
// memset(img.GetScanline(imgY + i) + imgX + 1, 255, imgW-2);
//}
//if(imgH > 4 && imgW > 4)
//{
// for (i = 2; i < imgH-2; ++i)
// {
// memset(img.GetScanline(imgY + i) + imgX + 2, 0, imgW-4);
// img.GetScanline(imgY + i)[imgX + imgW / 2] = 255;
// }
// memset(img.GetScanline(imgY + imgH/2) + imgX + 2, 255, imgW-4);
//}
// Sweep the raster writing the scan lines to the image.
if (raster)
{
for(i = 0; i < raster->Height; ++i)
{
memcpy(img.GetScanline(imgY + padY + i) + imgX + padX,
&raster->Raster[i * raster->Width],
raster->Width);
}
}
else
if (cellsToSweep)
{
Float gamma = 1.0f;
if (gp.BlurX || gp.BlurY)
{
// Gamma correction improves the "strength" of glow and shadow
// TO DO: Think of smarter control of it.
gamma = 0.4f;
}
if (gamma != Rasterizer.GetGamma())
Rasterizer.SetGamma(gamma);
if (imgW < 5 || stretch < 3)
{
for(i = 0; i < Rasterizer.GetNumScanlines(); ++i)
{
Rasterizer.SweepScanline(i, img.GetScanline(imgY + padY + i) + imgX + padX);
}
}
else
{
for(i = 0; i < Rasterizer.GetNumScanlines(); ++i)
{
memset(img.GetScanline(imgY) + imgX, 0, imgW);
Rasterizer.SweepScanline(i, img.GetScanline(imgY) + imgX + padX);
filterScanline(img.GetScanline(imgY) + imgX,
img.GetScanline(imgY + padY + i) + imgX, imgW);
}
memset(img.GetScanline(imgY) + imgX, 0, imgW);
}
// Too Black!
//GFxDrawGlyphOutline(&img, imgX + padX - Rasterizer.GetMinX(),
// imgY + padY - Rasterizer.GetMinY(), Shape);
// In a practical average case the best Gamma is No Gamma!
//if (shape->GetHintedGlyphSize())
//{
// // Apply gamma correction to the hinted glyph
// for (i = 0; i < imgH; ++i)
// {
// UInt8* p = img.GetScanline(imgY + i) + imgX;
// for (j = 0; j < imgW; ++j)
// p[j] = GFx_HintedGamma[p[j]];
// }
//}
SInt bias = 0;
if (gp.IsKnockOut())
makeKnockOutCopy(img, imgX, imgY, imgW, imgH);
if (gp.BlurX || gp.BlurY)
{
if (gp.IsFineBlur())
{
recursiveBlur(img, imgX, imgY, imgW, imgH, gp.GetBlurX(), gp.GetBlurY());
bias = 8;
}
else
{
stackBlur(img, imgX, imgY, imgW, imgH, intBlurX, intBlurY);
bias = 2;
}
}
if (gp.BlurStrength != 16)
strengthenImage(img, imgX, imgY, imgW, imgH,
gp.GetBlurStrength(),
(gp.GetBlurStrength() <= 1) ? 0 : bias);
if (gp.IsKnockOut())
knockOut(img, imgX, imgY, imgW, imgH);
}
//static int gln = 1;
//char buf[20];
//sprintf(buf, "%03d.pgm", gln);
//FILE*fd = fopen(buf, "wb");
//fprintf(fd, "P5\n%d %d\n255\n", imgW, imgH);
//for(int i = 0; i < imgH; ++i)
//fwrite(img.GetScanline(imgY+i) + imgX, imgW, 1, fd);
//fclose(fd);
//gln++;
GlyphUpdateType gu;
gu.TextureId = GetTextureId(glyph);
gu.Rect.src.Left = imgX;
gu.Rect.src.Top = imgY;
gu.Rect.src.Right = imgX + imgW;
gu.Rect.src.Bottom = imgY + imgH;
gu.Rect.dest.x = glyph->Rect.x;
gu.Rect.dest.y = glyph->Rect.y;
GlyphsToUpdate.PushBack(gu);
Textures[gu.TextureId].NumGlyphsToUpdate++;
if (pEventHandler)
{
GRectF uvRect;
uvRect.Left = Float(glyph->Rect.x);
uvRect.Top = Float(glyph->Rect.y);
uvRect.Right = Float(glyph->Rect.x + glyph->Rect.w);
uvRect.Bottom = Float(glyph->Rect.y + glyph->Rect.h);
pEventHandler->OnUpdateGlyph(glyph->Param, uvRect, GetTextureId(glyph),
img.GetScanline(imgY) + imgX, imgW, imgH, img.Pitch);
}
// DBG simulate 4bpp textures
//for(UInt y = 0; y < imgH; ++y)
// for(UInt x = 0; x < imgW; ++x)
// img.GetScanline(y + imgY)[x + imgX] &= 0xF0;
return glyph;
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::UpdateTextures(GRenderer* ren)
{
UInt i, j;
for(i = 0; i < MaxNumTextures; ++i)
{
TextureType& tex = Textures[i];
if (tex.NumGlyphsToUpdate)
{
if (initTextureIfRequired(ren, i))
{
RectsToUpdate.Resize(tex.NumGlyphsToUpdate, 32);
UInt numRects = 0;
for (j = 0; j < (UInt)GlyphsToUpdate.GetSize(); j++)
{
if (GlyphsToUpdate[j].TextureId == i)
{
GASSERT(numRects < (UInt)RectsToUpdate.GetSize());
RectsToUpdate[numRects++] = GlyphsToUpdate[j].Rect;
}
}
#ifdef GFX_AMP_SERVER
GFxAmpServer::GetInstance().IncrementFontCacheTextureUpdate();
#endif
Textures[i].pTexture->Update(0, numRects, &RectsToUpdate[0], TexUpdBuffer);
// DBG
//printf("\nUpd %d Packed %d\n", numRects, SlotQueue.GetNumPacked());
}
tex.NumGlyphsToUpdate = 0;
}
}
GlyphsToUpdate.Clear();
TexUpdPacker.Clear();
}
//------------------------------------------------------------------------
GFxGlyphNode* GFxGlyphRasterCache::GetGlyph(GRenderer* ren,
const GFxGlyphParam& gp,
bool canUseRaster,
const GFxShapeBase* shape,
UInt subpixelSize,
GFxLog* log)
{
GFxGlyphNode* glyph = SlotQueue.FindGlyph(gp);
if (glyph)
{
// DBG
//printf("+");
//static int c=0;
//if((++c % 1000) == 0)
//{
//printf("\nPacked %d\n", SlotQueue.GetNumPacked());
//}
SlotQueue.SendGlyphToBack(glyph);
return glyph;
}
glyph = rasterizeAndPack(ren, gp, canUseRaster, shape, subpixelSize, log);
return glyph;
}
UInt GFxGlyphRasterCache::GetNumRasterizedGlyphs() const
{
return SlotQueue.GetNumPacked();
}
UInt GFxGlyphRasterCache::GetNumTextures() const
{
UInt numTextures = 0;
for (UPInt i = 0; i < MaxNumTextures; ++i)
{
if (Textures[i].pTexture != NULL)
{
++numTextures;
}
}
return numTextures;
}
//------------------------------------------------------------------------
void GFxGlyphRasterCache::VisitGlyphs(GFxGlyphCacheVisitor* visitor) const
{
SlotQueue.VisitGlyphs(visitor);
}
void GFxGlyphRasterCache::SetEventHandler(class GFxGlyphCacheEventHandler* h)
{
pEventHandler = h;
SlotQueue.SetEventHandler(h);
}
UInt GFxGlyphRasterCache::ComputeUsedArea() const
{
return SlotQueue.ComputeUsedArea();
}
#endif // GFC_NO_GLYPH_CACHE