Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Source/Entities/MOSRotating.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1696,13 +1696,20 @@ void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode
}

if (m_HFlipped && pFlipBitmap) {
bool drawIntermediate = mode != g_DrawMOID;
bool cachedColor = false;
if (mode == g_DrawColor && !m_SpriteModified && pFlipBitmap->w == m_aSprite[m_Frame]->w && pFlipBitmap->h == m_aSprite[m_Frame]->h) {
if (BITMAP* cachedBitmap = ContentFile::GetFlippedBitmap(m_aSprite[m_Frame])) {
pFlipBitmap = cachedBitmap;
cachedColor = true;
}
}
bool drawIntermediate = !cachedColor && mode != g_DrawMOID && mode != g_DrawTrans;
if (drawIntermediate) {
// Don't size the intermediate bitmaps to the m_Scale, because the scaling happens after they are done
clear_to_color(pFlipBitmap, keyColor);

// Draw either the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap
if (mode == g_DrawColor || mode == g_DrawTrans) {
if (mode == g_DrawColor) {
draw_sprite_h_flip(pFlipBitmap, m_aSprite[m_Frame], 0, 0);
} else {
// If using the temp bitmap (which is always larger than the sprite) make sure the flipped image ends up in the upper right corner as if it was just as small as the sprite bitmap
Expand Down
33 changes: 32 additions & 1 deletion Source/System/ContentFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ using namespace RTE;
const std::string ContentFile::c_ClassName = "ContentFile";

std::array<std::unordered_map<std::string, BITMAP*>, ContentFile::BitDepths::BitDepthCount> ContentFile::s_LoadedBitmaps;
std::unordered_map<BITMAP*, BITMAP*> ContentFile::s_FlippedBitmaps;
std::unordered_map<std::string, SDL_Surface*> ContentFile::s_MemoryPNGs;
std::unordered_map<std::string, FMOD::Sound*> ContentFile::s_LoadedSamples;
std::unordered_map<size_t, std::string> ContentFile::s_PathHashes;
Expand Down Expand Up @@ -53,6 +54,10 @@ int ContentFile::Create(const ContentFile& reference) {
}

void ContentFile::FreeAllLoaded() {
for (const auto& [source, flipped]: s_FlippedBitmaps) {
destroy_bitmap(flipped);
}
s_FlippedBitmaps.clear();
for (int depth = BitDepths::Eight; depth < BitDepths::BitDepthCount; ++depth) {
for (const auto& [bitmapPath, bitmapPtr]: s_LoadedBitmaps[depth]) {
destroy_bitmap(bitmapPtr);
Expand Down Expand Up @@ -210,7 +215,8 @@ void ContentFile::ManuallyLoadDataPNG(const std::string& filePath, SDL_Surface*
surface->w * SDL_BYTESPERPIXEL(surface->format));
}

s_LoadedBitmaps[BitDepths::Eight].try_emplace(filePath, bitmap);
auto [entry, inserted] = s_LoadedBitmaps[BitDepths::Eight].try_emplace(filePath, bitmap);
s_FlippedBitmaps.try_emplace(entry->second, nullptr);
}

void ContentFile::ReloadAllBitmaps() {
Expand All @@ -222,6 +228,27 @@ void ContentFile::ReloadAllBitmaps() {
g_ConsoleMan.PrintString("SYSTEM: Sprites reloaded");
}

BITMAP* ContentFile::GetFlippedBitmap(BITMAP* bitmap) {
auto entry = s_FlippedBitmaps.find(bitmap);
if (entry == s_FlippedBitmaps.end()) {
return nullptr;
}
if (!entry->second) {
entry->second = create_bitmap_ex(8, bitmap->w, bitmap->h);
clear_to_color(entry->second, ColorKeys::g_MaskColor);
draw_sprite_h_flip(entry->second, bitmap, 0, 0);
}
return entry->second;
}

void ContentFile::RemoveFlippedBitmap(BITMAP* bitmap) {
auto entry = s_FlippedBitmaps.find(bitmap);
if (entry != s_FlippedBitmaps.end()) {
destroy_bitmap(entry->second);
s_FlippedBitmaps.erase(entry);
}
}

BITMAP* ContentFile::GetAsBitmap(int conversionMode, bool storeBitmap, const std::string& dataPathToSpecificFrame) {
if (m_DataPath.empty()) {
return nullptr;
Expand All @@ -243,6 +270,7 @@ BITMAP* ContentFile::GetAsBitmap(int conversionMode, bool storeBitmap, const std
std::unordered_map<std::string, BITMAP*>::iterator foundBitmap = s_LoadedBitmaps[BitDepths::Eight].find(dataPathToLoad);
if (foundBitmap != s_LoadedBitmaps[BitDepths::Eight].end()) {
returnBitmap = foundBitmap->second;
RemoveFlippedBitmap(returnBitmap);
s_LoadedBitmaps[BitDepths::Eight].erase(dataPathToLoad);
}

Expand All @@ -269,6 +297,7 @@ BITMAP* ContentFile::GetAsBitmap(int conversionMode, bool storeBitmap, const std
// Insert the bitmap into the map, PASSING OVER OWNERSHIP OF THE LOADED DATAFILE
if (storeBitmap) {
s_LoadedBitmaps[bitDepth].try_emplace(dataPathToLoad, returnBitmap);
s_FlippedBitmaps.try_emplace(returnBitmap, nullptr);
}
}

Expand Down Expand Up @@ -448,6 +477,8 @@ void ContentFile::ReloadBitmap(const std::string& filePath, int conversionMode)
set_color_conversion((conversionMode == COLORCONV_NONE) ? COLORCONV_NONE : conversionMode);

BITMAP* loadedBitmap = (*bmpItr).second;
RemoveFlippedBitmap(loadedBitmap);
s_FlippedBitmaps.try_emplace(loadedBitmap, nullptr);

SDL_Surface* newImage = LoadImageAsSurface(conversionMode, filePath);

Expand Down
5 changes: 5 additions & 0 deletions Source/System/ContentFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ namespace RTE {
/// Reloads all BITMAPs in the cache from disk, allowing any changes to be reflected at runtime.
static void ReloadAllBitmaps();

/// Gets a cached horizontal flip of a loaded sprite bitmap, or nullptr for a bitmap outside this cache. Ownership stays with ContentFile.
static BITMAP* GetFlippedBitmap(BITMAP* bitmap);

/// Gets the data represented by this ContentFile object as an Allegro BITMAP, loading it into the static maps if it's not already loaded. Note that ownership of the BITMAP is NOT transferred!
/// @param conversionMode The Allegro color conversion mode to use when loading this bitmap.
/// @param storeBitmap Whether to store the BITMAP in the relevant static map after loading it or not. If this is false, ownership of the BITMAP IS transferred!
Expand Down Expand Up @@ -180,6 +183,7 @@ namespace RTE {

static std::unordered_map<size_t, std::string> s_PathHashes; //!< Static map containing the hash values of paths of all loaded data files.
static std::array<std::unordered_map<std::string, BITMAP*>, BitDepths::BitDepthCount> s_LoadedBitmaps; //!< Static map containing all the already loaded BITMAPs and their paths for each bit depth.
static std::unordered_map<BITMAP*, BITMAP*> s_FlippedBitmaps; //!< Horizontal flips of unmodified shared sprite bitmaps.
static std::unordered_map<std::string, SDL_Surface*> s_MemoryPNGs; //!< Static map containing in-memory PNG files for save/load
static std::unordered_map<std::string, FMOD::Sound*> s_LoadedSamples; //!< Static map containing all the already loaded FSOUND_SAMPLEs and their paths.

Expand Down Expand Up @@ -239,6 +243,7 @@ namespace RTE {
/// @param filePath The filepath to the bitmap we want to reload.
/// @param conversionMode The Allegro color conversion mode to use when reloading this bitmap.
static void ReloadBitmap(const std::string& filePath, int conversionMode = 0);
static void RemoveFlippedBitmap(BITMAP* bitmap);

/// Set alpha value of non mask color pixels to 255 for 32-bit bitmaps. (WARN: would override existing alpha values!)
static void AddAlphaChannel(BITMAP* bitmap);
Expand Down
64 changes: 64 additions & 0 deletions Tools/Benchmarks/CaptureSpriteGame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
import argparse
import json
import pathlib
import shlex
import subprocess

parser = argparse.ArgumentParser(description='Build seeded game captures from the FlippedSprites engine objects.')
parser.add_argument('build_directory', type=pathlib.Path)
parser.add_argument('output_directory', type=pathlib.Path)
parser.add_argument('--replace-object', action='append', default=[])
args = parser.parse_args()
build = args.build_directory.resolve()
output = args.output_directory.resolve()
repo = pathlib.Path(__file__).resolve().parents[2]
commands = json.loads((build / 'compile_commands.json').read_text())
movable = (repo / 'Source/Managers/MovableMan.cpp').read_text()
movable = 'namespace RTE { long long profileTravel=0, profileColor=0, profileClear=0; }\n' + movable
movable = movable.replace('void MovableMan::Update() {', 'void MovableMan::Update() {\n profileTravel=profileColor=profileClear=0;')
movable = movable.replace('g_SceneMan.ClearMOColorLayer();', '{ const auto started=g_TimerMan.GetAbsoluteTime(); g_SceneMan.ClearMOColorLayer(); profileClear=g_TimerMan.GetAbsoluteTime()-started; }')
movable = movable.replace('\tTravel();', '\t{ const auto started=g_TimerMan.GetAbsoluteTime(); Travel(); profileTravel=g_TimerMan.GetAbsoluteTime()-started; }')
movable = movable.replace('Draw(g_SceneMan.GetMOColorBitmap());', '{ const auto started=g_TimerMan.GetAbsoluteTime(); Draw(g_SceneMan.GetMOColorBitmap()); profileColor=g_TimerMan.GetAbsoluteTime()-started; }')
main = (repo / 'Source/Main.cpp').read_text()
main = main.replace('void RunGameLoop() {', '''namespace RTE { extern long long profileTravel, profileColor, profileClear; }
void RunGameLoop() {
struct CaptureSample { long long simulation, movable; int particles; long long travel, color, clear, gridWait; };
std::vector<CaptureSample> captureSamples;
''')
main = main.replace('g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::SimTotal);', '''g_RandomGenerator.Seed(731 + captureSamples.size());
const auto captureStart=g_TimerMan.GetAbsoluteTime();
g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::SimTotal);''')
main = main.replace('g_MovableMan.CompleteQueuedMOIDDrawings();', '''const auto waitStart=g_TimerMan.GetAbsoluteTime();
g_MovableMan.CompleteQueuedMOIDDrawings();
const auto gridWait=g_TimerMan.GetAbsoluteTime()-waitStart;''')
main = main.replace('g_MovableMan.Update();', '''const auto movableStart=g_TimerMan.GetAbsoluteTime();
g_MovableMan.Update();
const auto movableEnd=g_TimerMan.GetAbsoluteTime();''')
main = main.replace('g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::SimTotal);', '''g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::SimTotal);
captureSamples.push_back({g_TimerMan.GetAbsoluteTime()-captureStart, movableEnd-movableStart, g_MovableMan.GetParticleCount(), profileTravel, profileColor, profileClear, gridWait});''')
main = main.replace('g_PerformanceMan.UpdateMSPF(updateTotalTime, drawTotalTime);', '''g_PerformanceMan.UpdateMSPF(updateTotalTime, drawTotalTime);
if (captureSamples.size() >= 240) {
for (size_t i=0; i<captureSamples.size(); ++i) {
const auto& v=captureSamples[i];
std::printf("PERF_PROFILE,%zu,%lld,%lld,%d,%lld,%lld,%lld,%lld\\n", i, v.simulation, v.movable, v.particles, v.travel, v.color, v.clear, v.gridWait);
}
std::printf("PERF_CAPTURE_DONE\\n"); std::fflush(stdout); System::SetQuit(true);
}''')
for name, source in [('Main', main), ('MovableMan', movable)]:
path = output / (name + '-capture.cpp')
path.write_text(source)
command = shlex.split(next(c['command'] for c in commands if c['file'].endswith('/' + name + '.cpp')))
command = command[:command.index('-MD')]
command += ['-c', str(path), '-o', str(output / (name + '-capture.o'))]
subprocess.run(command, cwd=build, check=True)
base_link = shlex.split(subprocess.check_output(['ninja', '-C', str(build), '-t', 'commands', 'CortexCommand'], text=True).splitlines()[-1])
for variant in ['baseline', 'changed']:
replacements = dict(argument.split('=', 1) for argument in args.replace_object)
replacements.update({name: str(output / (name + '-capture.o')) for name in ['Main', 'MovableMan']})
replacements.update({name: str(output / (name + '-' + variant + '.o')) for name in ['MOSRotating', 'ContentFile']})
link = base_link.copy()
link[link.index('-o') + 1] = str(output / ('game-' + variant))
for name, path in replacements.items():
link[next(i for i, arg in enumerate(link) if arg.endswith('_' + name + '.cpp.o'))] = str(pathlib.Path(path).resolve())
subprocess.run(link, cwd=build, check=True)
Loading