842 lines
27 KiB
C++
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);
|
|
}
|