4coder-non-source/test_data/lots_of_files/handmade_asset.cpp

842 lines
27 KiB
C++

/* ========================================================================
$File: $
$Date: $
$Revision: $
$Creator: Casey Muratori $
$Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $
======================================================================== */
enum finalize_asset_operation
{
FinalizeAsset_None,
FinalizeAsset_Font,
};
struct load_asset_work
{
task_with_memory *Task;
asset *Asset;
platform_file_handle *Handle;
u64 Offset;
u64 Size;
void *Destination;
finalize_asset_operation FinalizeOperation;
u32 FinalState;
};
internal void
LoadAssetWorkDirectly(load_asset_work *Work)
{
TIMED_FUNCTION();
Platform.ReadDataFromFile(Work->Handle, Work->Offset, Work->Size, Work->Destination);
if(PlatformNoFileErrors(Work->Handle))
{
switch(Work->FinalizeOperation)
{
case FinalizeAsset_None:
{
// NOTE(casey): Nothing to do.
} break;
case FinalizeAsset_Font:
{
loaded_font *Font = &Work->Asset->Header->Font;
hha_font *HHA = &Work->Asset->HHA.Font;
for(u32 GlyphIndex = 1;
GlyphIndex < HHA->GlyphCount;
++GlyphIndex)
{
hha_font_glyph *Glyph = Font->Glyphs + GlyphIndex;
Assert(Glyph->UnicodeCodePoint < HHA->OnePastHighestCodepoint);
Assert((u32)(u16)GlyphIndex == GlyphIndex);
Font->UnicodeMap[Glyph->UnicodeCodePoint] = (u16)GlyphIndex;
}
} break;
}
}
CompletePreviousWritesBeforeFutureWrites;
if(!PlatformNoFileErrors(Work->Handle))
{
ZeroSize(Work->Size, Work->Destination);
}
Work->Asset->State = Work->FinalState;
}
internal PLATFORM_WORK_QUEUE_CALLBACK(LoadAssetWork)
{
load_asset_work *Work = (load_asset_work *)Data;
LoadAssetWorkDirectly(Work);
EndTaskWithMemory(Work->Task);
}
inline asset_file *
GetFile(game_assets *Assets, u32 FileIndex)
{
Assert(FileIndex < Assets->FileCount);
asset_file *Result = Assets->Files + FileIndex;
return(Result);
}
inline platform_file_handle *
GetFileHandleFor(game_assets *Assets, u32 FileIndex)
{
platform_file_handle *Result = &GetFile(Assets, FileIndex)->Handle;
return(Result);
}
internal asset_memory_block *
InsertBlock(asset_memory_block *Prev, u64 Size, void *Memory)
{
Assert(Size > sizeof(asset_memory_block));
asset_memory_block *Block = (asset_memory_block *)Memory;
Block->Flags = 0;
Block->Size = Size - sizeof(asset_memory_block);
Block->Prev = Prev;
Block->Next = Prev->Next;
Block->Prev->Next = Block;
Block->Next->Prev = Block;
return(Block);
}
internal asset_memory_block *
FindBlockForSize(game_assets *Assets, memory_index Size)
{
asset_memory_block *Result = 0;
// TODO(casey): This probably will need to be accelerated in the
// future as the resident asset count grows.
// TODO(casey): Best match block!
for(asset_memory_block *Block = Assets->MemorySentinel.Next;
Block != &Assets->MemorySentinel;
Block = Block->Next)
{
if(!(Block->Flags & AssetMemory_Used))
{
if(Block->Size >= Size)
{
Result = Block;
break;
}
}
}
return(Result);
}
internal b32
MergeIfPossible(game_assets *Assets, asset_memory_block *First, asset_memory_block *Second)
{
b32 Result = false;
if((First != &Assets->MemorySentinel) &&
(Second != &Assets->MemorySentinel))
{
if(!(First->Flags & AssetMemory_Used) &&
!(Second->Flags & AssetMemory_Used))
{
u8 *ExpectedSecond = (u8 *)First + sizeof(asset_memory_block) + First->Size;
if((u8 *)Second == ExpectedSecond)
{
Second->Next->Prev = Second->Prev;
Second->Prev->Next = Second->Next;
First->Size += sizeof(asset_memory_block) + Second->Size;
Result = true;
}
}
}
return(Result);
}
internal b32
GenerationHasCompleted(game_assets *Assets, u32 CheckID)
{
b32 Result = true;
for(u32 Index = 0;
Index < Assets->InFlightGenerationCount;
++Index)
{
if(Assets->InFlightGenerations[Index] == CheckID)
{
Result = false;
break;
}
}
return(Result);
}
internal asset_memory_header *
AcquireAssetMemory(game_assets *Assets, u32 Size, u32 AssetIndex)
{
TIMED_FUNCTION();
asset_memory_header *Result = 0;
BeginAssetLock(Assets);
asset_memory_block *Block = FindBlockForSize(Assets, Size);
for(;;)
{
if(Block && (Size <= Block->Size))
{
Block->Flags |= AssetMemory_Used;
Result = (asset_memory_header *)(Block + 1);
memory_index RemainingSize = Block->Size - Size;
memory_index BlockSplitThreshold = 4096; // TODO(casey): Set this based on the smallest asset?
if(RemainingSize > BlockSplitThreshold)
{
Block->Size -= RemainingSize;
InsertBlock(Block, RemainingSize, (u8 *)Result + Size);
}
else
{
// TODO(casey): Actually record the unused portion of the memory
// in a block so that we can do the merge on blocks when neighbors
// are freed.
}
break;
}
else
{
for(asset_memory_header *Header = Assets->LoadedAssetSentinel.Prev;
Header != &Assets->LoadedAssetSentinel;
Header = Header->Prev)
{
asset *Asset = Assets->Assets + Header->AssetIndex;
if((Asset->State >= AssetState_Loaded) &&
(GenerationHasCompleted(Assets, Asset->Header->GenerationID)))
{
u32 AssetIndex = Header->AssetIndex;
asset *Asset = Assets->Assets + AssetIndex;
Assert(Asset->State == AssetState_Loaded);
RemoveAssetHeaderFromList(Header);
Block = (asset_memory_block *)Asset->Header - 1;
Block->Flags &= ~AssetMemory_Used;
if(MergeIfPossible(Assets, Block->Prev, Block))
{
Block = Block->Prev;
}
MergeIfPossible(Assets, Block, Block->Next);
Asset->State = AssetState_Unloaded;
Asset->Header = 0;
break;
}
}
}
}
if(Result)
{
Result->AssetIndex = AssetIndex;
Result->TotalSize = Size;
InsertAssetHeaderAtFront(Assets, Result);
}
EndAssetLock(Assets);
return(Result);
}
struct asset_memory_size
{
u32 Total;
u32 Data;
u32 Section;
};
internal void
LoadBitmap(game_assets *Assets, bitmap_id ID, b32 Immediate)
{
TIMED_FUNCTION();
asset *Asset = Assets->Assets + ID.Value;
if(ID.Value)
{
if(AtomicCompareExchangeUInt32((uint32 *)&Asset->State, AssetState_Queued, AssetState_Unloaded) ==
AssetState_Unloaded)
{
task_with_memory *Task = 0;
if(!Immediate)
{
Task = BeginTaskWithMemory(Assets->TranState);
}
if(Immediate || Task)
{
asset *Asset = Assets->Assets + ID.Value;
hha_bitmap *Info = &Asset->HHA.Bitmap;
asset_memory_size Size = {};
u32 Width = Info->Dim[0];
u32 Height = Info->Dim[1];
Size.Section = 4*Width;
Size.Data = Height*Size.Section;
Size.Total = Size.Data + sizeof(asset_memory_header);
Asset->Header = AcquireAssetMemory(Assets, Size.Total, ID.Value);
loaded_bitmap *Bitmap = &Asset->Header->Bitmap;
Bitmap->AlignPercentage = V2(Info->AlignPercentage[0], Info->AlignPercentage[1]);
Bitmap->WidthOverHeight = (r32)Info->Dim[0] / (r32)Info->Dim[1];
Bitmap->Width = Info->Dim[0];
Bitmap->Height = Info->Dim[1];
Bitmap->Pitch = Size.Section;
Bitmap->Memory = (Asset->Header + 1);
load_asset_work Work;
Work.Task = Task;
Work.Asset = Assets->Assets + ID.Value;
Work.Handle = GetFileHandleFor(Assets, Asset->FileIndex);
Work.Offset = Asset->HHA.DataOffset;
Work.Size = Size.Data;
Work.Destination = Bitmap->Memory;
Work.FinalizeOperation = FinalizeAsset_None;
Work.FinalState = AssetState_Loaded;
if(Task)
{
load_asset_work *TaskWork = PushStruct(&Task->Arena, load_asset_work);
*TaskWork = Work;
Platform.AddEntry(Assets->TranState->LowPriorityQueue, LoadAssetWork, TaskWork);
}
else
{
LoadAssetWorkDirectly(&Work);
}
}
else
{
Asset->State = AssetState_Unloaded;
}
}
else if(Immediate)
{
// TODO(casey): Do we want to have a more coherent story here
// for what happens when two force-load people hit the load
// at the same time?
asset_state volatile *State = (asset_state volatile *)&Asset->State;
while(*State == AssetState_Queued) {}
}
}
}
internal void
LoadSound(game_assets *Assets, sound_id ID)
{
TIMED_FUNCTION();
asset *Asset = Assets->Assets + ID.Value;
if(ID.Value &&
(AtomicCompareExchangeUInt32((uint32 *)&Asset->State, AssetState_Queued, AssetState_Unloaded) ==
AssetState_Unloaded))
{
task_with_memory *Task = BeginTaskWithMemory(Assets->TranState);
if(Task)
{
asset *Asset = Assets->Assets + ID.Value;
hha_sound *Info = &Asset->HHA.Sound;
asset_memory_size Size = {};
Size.Section = Info->SampleCount*sizeof(int16);
Size.Data = Info->ChannelCount*Size.Section;
Size.Total = Size.Data + sizeof(asset_memory_header);
Asset->Header = (asset_memory_header *)AcquireAssetMemory(Assets, Size.Total, ID.Value);
loaded_sound *Sound = &Asset->Header->Sound;
Sound->SampleCount = Info->SampleCount;
Sound->ChannelCount = Info->ChannelCount;
u32 ChannelSize = Size.Section;
void *Memory = (Asset->Header + 1);
int16 *SoundAt = (int16 *)Memory;
for(u32 ChannelIndex = 0;
ChannelIndex < Sound->ChannelCount;
++ChannelIndex)
{
Sound->Samples[ChannelIndex] = SoundAt;
SoundAt += ChannelSize;
}
load_asset_work *Work = PushStruct(&Task->Arena, load_asset_work);
Work->Task = Task;
Work->Asset = Assets->Assets + ID.Value;
Work->Handle = GetFileHandleFor(Assets, Asset->FileIndex);
Work->Offset = Asset->HHA.DataOffset;
Work->Size = Size.Data;
Work->Destination = Memory;
Work->FinalizeOperation = FinalizeAsset_None;
Work->FinalState = (AssetState_Loaded);
Platform.AddEntry(Assets->TranState->LowPriorityQueue, LoadAssetWork, Work);
}
else
{
Assets->Assets[ID.Value].State = AssetState_Unloaded;
}
}
}
internal void
LoadFont(game_assets *Assets, font_id ID, b32 Immediate)
{
TIMED_FUNCTION();
// TODO(casey): Merge all this boilerplate!!!! Same between LoadBitmap, LoadSound, and LoadFont
asset *Asset = Assets->Assets + ID.Value;
if(ID.Value)
{
if(AtomicCompareExchangeUInt32((uint32 *)&Asset->State, AssetState_Queued, AssetState_Unloaded) ==
AssetState_Unloaded)
{
task_with_memory *Task = 0;
if(!Immediate)
{
Task = BeginTaskWithMemory(Assets->TranState);
}
if(Immediate || Task)
{
asset *Asset = Assets->Assets + ID.Value;
hha_font *Info = &Asset->HHA.Font;
u32 HorizontalAdvanceSize = sizeof(r32)*Info->GlyphCount*Info->GlyphCount;
u32 GlyphsSize = Info->GlyphCount*sizeof(hha_font_glyph);
u32 UnicodeMapSize = sizeof(u16)*Info->OnePastHighestCodepoint;
u32 SizeData = GlyphsSize + HorizontalAdvanceSize;
u32 SizeTotal = SizeData + sizeof(asset_memory_header) + UnicodeMapSize;
Asset->Header = AcquireAssetMemory(Assets, SizeTotal, ID.Value);
loaded_font *Font = &Asset->Header->Font;
Font->BitmapIDOffset = GetFile(Assets, Asset->FileIndex)->FontBitmapIDOffset;
Font->Glyphs = (hha_font_glyph *)(Asset->Header + 1);
Font->HorizontalAdvance = (r32 *)((u8 *)Font->Glyphs + GlyphsSize);
Font->UnicodeMap = (u16 *)((u8 *)Font->HorizontalAdvance + HorizontalAdvanceSize);
ZeroSize(UnicodeMapSize, Font->UnicodeMap);
load_asset_work Work;
Work.Task = Task;
Work.Asset = Assets->Assets + ID.Value;
Work.Handle = GetFileHandleFor(Assets, Asset->FileIndex);
Work.Offset = Asset->HHA.DataOffset;
Work.Size = SizeData;
Work.Destination = Font->Glyphs;
Work.FinalizeOperation = FinalizeAsset_Font;
Work.FinalState = AssetState_Loaded;
if(Task)
{
load_asset_work *TaskWork = PushStruct(&Task->Arena, load_asset_work);
*TaskWork = Work;
Platform.AddEntry(Assets->TranState->LowPriorityQueue, LoadAssetWork, TaskWork);
}
else
{
LoadAssetWorkDirectly(&Work);
}
}
else
{
Asset->State = AssetState_Unloaded;
}
}
else if(Immediate)
{
// TODO(casey): Do we want to have a more coherent story here
// for what happens when two force-load people hit the load
// at the same time?
asset_state volatile *State = (asset_state volatile *)&Asset->State;
while(*State == AssetState_Queued) {}
}
}
}
internal uint32
GetBestMatchAssetFrom(game_assets *Assets, asset_type_id TypeID,
asset_vector *MatchVector, asset_vector *WeightVector)
{
TIMED_FUNCTION();
uint32 Result = 0;
real32 BestDiff = Real32Maximum;
asset_type *Type = Assets->AssetTypes + TypeID;
for(uint32 AssetIndex = Type->FirstAssetIndex;
AssetIndex < Type->OnePastLastAssetIndex;
++AssetIndex)
{
asset *Asset = Assets->Assets + AssetIndex;
real32 TotalWeightedDiff = 0.0f;
for(uint32 TagIndex = Asset->HHA.FirstTagIndex;
TagIndex < Asset->HHA.OnePastLastTagIndex;
++TagIndex)
{
hha_tag *Tag = Assets->Tags + TagIndex;
real32 A = MatchVector->E[Tag->ID];
real32 B = Tag->Value;
real32 D0 = AbsoluteValue(A - B);
real32 D1 = AbsoluteValue((A - Assets->TagRange[Tag->ID]*SignOf(A)) - B);
real32 Difference = Minimum(D0, D1);
real32 Weighted = WeightVector->E[Tag->ID]*Difference;
TotalWeightedDiff += Weighted;
}
if(BestDiff > TotalWeightedDiff)
{
BestDiff = TotalWeightedDiff;
Result = AssetIndex;
}
}
return(Result);
}
internal uint32
GetRandomAssetFrom(game_assets *Assets, asset_type_id TypeID, random_series *Series)
{
TIMED_FUNCTION();
uint32 Result = 0;
asset_type *Type = Assets->AssetTypes + TypeID;
if(Type->FirstAssetIndex != Type->OnePastLastAssetIndex)
{
uint32 Count = (Type->OnePastLastAssetIndex - Type->FirstAssetIndex);
uint32 Choice = RandomChoice(Series, Count);
Result = Type->FirstAssetIndex + Choice;
}
return(Result);
}
internal uint32
GetFirstAssetFrom(game_assets *Assets, asset_type_id TypeID)
{
TIMED_FUNCTION();
uint32 Result = 0;
asset_type *Type = Assets->AssetTypes + TypeID;
if(Type->FirstAssetIndex != Type->OnePastLastAssetIndex)
{
Result = Type->FirstAssetIndex;
}
return(Result);
}
inline bitmap_id
GetBestMatchBitmapFrom(game_assets *Assets, asset_type_id TypeID,
asset_vector *MatchVector, asset_vector *WeightVector)
{
bitmap_id Result = {GetBestMatchAssetFrom(Assets, TypeID, MatchVector, WeightVector)};
return(Result);
}
inline bitmap_id
GetFirstBitmapFrom(game_assets *Assets, asset_type_id TypeID)
{
bitmap_id Result = {GetFirstAssetFrom(Assets, TypeID)};
return(Result);
}
inline bitmap_id
GetRandomBitmapFrom(game_assets *Assets, asset_type_id TypeID, random_series *Series)
{
bitmap_id Result = {GetRandomAssetFrom(Assets, TypeID, Series)};
return(Result);
}
inline sound_id
GetBestMatchSoundFrom(game_assets *Assets, asset_type_id TypeID,
asset_vector *MatchVector, asset_vector *WeightVector)
{
sound_id Result = {GetBestMatchAssetFrom(Assets, TypeID, MatchVector, WeightVector)};
return(Result);
}
inline sound_id
GetFirstSoundFrom(game_assets *Assets, asset_type_id TypeID)
{
sound_id Result = {GetFirstAssetFrom(Assets, TypeID)};
return(Result);
}
inline sound_id
GetRandomSoundFrom(game_assets *Assets, asset_type_id TypeID, random_series *Series)
{
sound_id Result = {GetRandomAssetFrom(Assets, TypeID, Series)};
return(Result);
}
internal font_id
GetBestMatchFontFrom(game_assets *Assets, asset_type_id TypeID, asset_vector *MatchVector, asset_vector *WeightVector)
{
font_id Result = {GetBestMatchAssetFrom(Assets, TypeID, MatchVector, WeightVector)};
return(Result);
}
internal game_assets *
AllocateGameAssets(memory_arena *Arena, memory_index Size, transient_state *TranState)
{
TIMED_FUNCTION();
game_assets *Assets = PushStruct(Arena, game_assets);
Assets->NextGenerationID = 0;
Assets->InFlightGenerationCount = 0;
Assets->MemorySentinel.Flags = 0;
Assets->MemorySentinel.Size = 0;
Assets->MemorySentinel.Prev = &Assets->MemorySentinel;
Assets->MemorySentinel.Next = &Assets->MemorySentinel;
InsertBlock(&Assets->MemorySentinel, Size, PushSize(Arena, Size));
Assets->TranState = TranState;
Assets->LoadedAssetSentinel.Next =
Assets->LoadedAssetSentinel.Prev =
&Assets->LoadedAssetSentinel;
for(uint32 TagType = 0;
TagType < Tag_Count;
++TagType)
{
Assets->TagRange[TagType] = 1000000.0f;
}
Assets->TagRange[Tag_FacingDirection] = Tau32;
Assets->TagCount = 1;
Assets->AssetCount = 1;
// NOTE(casey): This code was written using Snuffleupagus-Oriented Programming (SOP)
{
platform_file_group FileGroup = Platform.GetAllFilesOfTypeBegin(PlatformFileType_AssetFile);
Assets->FileCount = FileGroup.FileCount;
Assets->Files = PushArray(Arena, Assets->FileCount, asset_file);
for(u32 FileIndex = 0;
FileIndex < Assets->FileCount;
++FileIndex)
{
asset_file *File = Assets->Files + FileIndex;
File->FontBitmapIDOffset = 0;
File->TagBase = Assets->TagCount;
ZeroStruct(File->Header);
File->Handle = Platform.OpenNextFile(&FileGroup);
Platform.ReadDataFromFile(&File->Handle, 0, sizeof(File->Header), &File->Header);
u32 AssetTypeArraySize = File->Header.AssetTypeCount*sizeof(hha_asset_type);
File->AssetTypeArray = (hha_asset_type *)PushSize(Arena, AssetTypeArraySize);
Platform.ReadDataFromFile(&File->Handle, File->Header.AssetTypes,
AssetTypeArraySize, File->AssetTypeArray);
if(File->Header.MagicValue != HHA_MAGIC_VALUE)
{
Platform.FileError(&File->Handle, "HHA file has an invalid magic value.");
}
if(File->Header.Version > HHA_VERSION)
{
Platform.FileError(&File->Handle, "HHA file is of a later version.");
}
if(PlatformNoFileErrors(&File->Handle))
{
// NOTE(casey): The first asset and tag slot in every
// HHA is a null (reserved) so we don't count it as
// something we will need space for!
Assets->TagCount += (File->Header.TagCount - 1);
Assets->AssetCount += (File->Header.AssetCount - 1);
}
else
{
// TODO(casey): Eventually, have some way of notifying users of bogus files?
InvalidCodePath;
}
}
Platform.GetAllFilesOfTypeEnd(&FileGroup);
}
// NOTE(casey): Allocate all metadata space
Assets->Assets = PushArray(Arena, Assets->AssetCount, asset);
Assets->Tags = PushArray(Arena, Assets->TagCount, hha_tag);
// NOTE(casey): Reserve one null tag at the beginning
ZeroStruct(Assets->Tags[0]);
// NOTE(casey): Load tags
for(u32 FileIndex = 0;
FileIndex < Assets->FileCount;
++FileIndex)
{
asset_file *File = Assets->Files + FileIndex;
if(PlatformNoFileErrors(&File->Handle))
{
// NOTE(casey): Skip the first tag, since it's null
u32 TagArraySize = sizeof(hha_tag)*(File->Header.TagCount - 1);
Platform.ReadDataFromFile(&File->Handle, File->Header.Tags + sizeof(hha_tag),
TagArraySize, Assets->Tags + File->TagBase);
}
}
// NOTE(casey): Reserve one null asset at the beginning
u32 AssetCount = 0;
ZeroStruct(*(Assets->Assets + AssetCount));
++AssetCount;
// TODO(casey): Excersize for the reader - how would you do this in a way
// that scaled gracefully to hundreds of asset pack files? (or more!)
for(u32 DestTypeID = 0;
DestTypeID < Asset_Count;
++DestTypeID)
{
asset_type *DestType = Assets->AssetTypes + DestTypeID;
DestType->FirstAssetIndex = AssetCount;
for(u32 FileIndex = 0;
FileIndex < Assets->FileCount;
++FileIndex)
{
asset_file *File = Assets->Files + FileIndex;
if(PlatformNoFileErrors(&File->Handle))
{
for(u32 SourceIndex = 0;
SourceIndex < File->Header.AssetTypeCount;
++SourceIndex)
{
hha_asset_type *SourceType = File->AssetTypeArray + SourceIndex;
if(SourceType->TypeID == DestTypeID)
{
if(SourceType->TypeID == Asset_FontGlyph)
{
File->FontBitmapIDOffset = AssetCount - SourceType->FirstAssetIndex;
}
u32 AssetCountForType = (SourceType->OnePastLastAssetIndex -
SourceType->FirstAssetIndex);
temporary_memory TempMem = BeginTemporaryMemory(&TranState->TranArena);
hha_asset *HHAAssetArray = PushArray(&TranState->TranArena,
AssetCountForType, hha_asset);
Platform.ReadDataFromFile(&File->Handle,
File->Header.Assets +
SourceType->FirstAssetIndex*sizeof(hha_asset),
AssetCountForType*sizeof(hha_asset),
HHAAssetArray);
for(u32 AssetIndex = 0;
AssetIndex < AssetCountForType;
++AssetIndex)
{
hha_asset *HHAAsset = HHAAssetArray + AssetIndex;
Assert(AssetCount < Assets->AssetCount);
asset *Asset = Assets->Assets + AssetCount++;
Asset->FileIndex = FileIndex;
Asset->HHA = *HHAAsset;
if(Asset->HHA.FirstTagIndex == 0)
{
Asset->HHA.FirstTagIndex = Asset->HHA.OnePastLastTagIndex = 0;
}
else
{
Asset->HHA.FirstTagIndex += (File->TagBase - 1);
Asset->HHA.OnePastLastTagIndex += (File->TagBase - 1);
}
}
EndTemporaryMemory(TempMem);
}
}
}
}
DestType->OnePastLastAssetIndex = AssetCount;
}
Assert(AssetCount == Assets->AssetCount);
return(Assets);
}
inline u32
GetGlyphFromCodePoint(hha_font *Info, loaded_font *Font, u32 CodePoint)
{
u32 Result = 0;
if(CodePoint < Info->OnePastHighestCodepoint)
{
Result = Font->UnicodeMap[CodePoint];
Assert(Result < Info->GlyphCount);
}
return(Result);
}
internal r32
GetHorizontalAdvanceForPair(hha_font *Info, loaded_font *Font, u32 DesiredPrevCodePoint, u32 DesiredCodePoint)
{
u32 PrevGlyph = GetGlyphFromCodePoint(Info, Font, DesiredPrevCodePoint);
u32 Glyph = GetGlyphFromCodePoint(Info, Font, DesiredCodePoint);
r32 Result = Font->HorizontalAdvance[PrevGlyph*Info->GlyphCount + Glyph];
return(Result);
}
internal bitmap_id
GetBitmapForGlyph(game_assets *Assets, hha_font *Info, loaded_font *Font, u32 DesiredCodePoint)
{
u32 Glyph = GetGlyphFromCodePoint(Info, Font, DesiredCodePoint);
bitmap_id Result = Font->Glyphs[Glyph].BitmapID;
Result.Value += Font->BitmapIDOffset;
return(Result);
}
internal r32
GetLineAdvanceFor(hha_font *Info)
{
r32 Result = Info->AscenderHeight + Info->DescenderHeight + Info->ExternalLeading;
return(Result);
}
internal r32
GetStartingBaselineY(hha_font *Info)
{
r32 Result = Info->AscenderHeight;
return(Result);
}