diff --git a/src/data_win.c b/src/data_win.c index ad529757c..988e1319a 100644 --- a/src/data_win.c +++ b/src/data_win.c @@ -2328,7 +2328,7 @@ static void parseSTRG(BinaryReader* reader, DataWin* dw) { free(ptrs); } -static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd) { +static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd, bool loadTextureDataLazily) { Txtr* t = &dw->txtr; uint32_t count; @@ -2404,10 +2404,39 @@ static void parseTXTR(BinaryReader* reader, DataWin* dw, size_t chunkEnd) { } // Load blob data into owned buffers - repeat(count, i) { - if (t->textures[i].blobOffset == 0 || t->textures[i].blobSize == 0) continue; - t->textures[i].blobData = BinaryReader_readBytesAt(reader, t->textures[i].blobOffset, t->textures[i].blobSize); + if (!loadTextureDataLazily) { + repeat(count, i) { + if (t->textures[i].blobOffset == 0 || t->textures[i].blobSize == 0) continue; + t->textures[i].blobData = BinaryReader_readBytesAt(reader, t->textures[i].blobOffset, t->textures[i].blobSize); + } + } +} + +void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId) { + Txtr* t = &dw->txtr; + Texture* tex = &t->textures[textureId]; + + if (tex->blobOffset == 0 || tex->blobSize == 0) return; + if (tex->blobData != nullptr) return; + + if (!dw->lazyLoadFile) { + fprintf(stderr, "%s: called without a lazy load file.\n", __func__); + return; + } + + tex->blobData = (uint8_t *)safeMalloc(tex->blobSize); + + memset(tex->blobData, 0, tex->blobSize); + long old_seek = ftell(dw->lazyLoadFile); + fseek(dw->lazyLoadFile, tex->blobOffset, SEEK_SET); + size_t read = fread(tex->blobData, 1, tex->blobSize, dw->lazyLoadFile); + fseek(dw->lazyLoadFile, old_seek, SEEK_SET); + + if (read != tex->blobSize) { + fprintf(stderr, "%s: couldn't read %u bytes to load a texture.\n", __func__, tex->blobSize); } + + fprintf(stderr, "%s: loaded texture data for page %u\n", __func__, textureId); } static void parseAUDO(BinaryReader* reader, DataWin* dw) { @@ -2590,13 +2619,15 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { // Bulk-read the chunk data into memory for fast parsing uint8_t* chunkBuffer = nullptr; if (shouldParse && chunkLength > 0 && options.loadType != DATAWINLOADTYPE_LOAD_IN_MEMORY_AHEAD_OF_TIME) { - chunkBuffer = (uint8_t *)safeMalloc(chunkLength); - size_t read = fread(chunkBuffer, 1, chunkLength, reader.file); - if (read != chunkLength) { - fprintf(stderr, "DataWin: short read on chunk %.4s (expected %u, got %zu)\n", chunkName, chunkLength, read); - exit(1); + chunkBuffer = (uint8_t *)malloc(chunkLength); + if (chunkBuffer) { + size_t read = fread(chunkBuffer, 1, chunkLength, reader.file); + if (read != chunkLength) { + fprintf(stderr, "DataWin: short read on chunk %.4s (expected %u, got %zu)\n", chunkName, chunkLength, read); + exit(1); + } + BinaryReader_setBuffer(&reader, chunkBuffer, chunkDataStart, chunkLength); } - BinaryReader_setBuffer(&reader, chunkBuffer, chunkDataStart, chunkLength); } if (options.parseGen8 && memcmp(chunkName, "GEN8", 4) == 0) { @@ -2661,7 +2692,7 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { } else if (options.parseStrg && memcmp(chunkName, "STRG", 4) == 0) { parseSTRG(&reader, dw); } else if (options.parseTxtr && memcmp(chunkName, "TXTR", 4) == 0) { - parseTXTR(&reader, dw, chunkEnd); + parseTXTR(&reader, dw, chunkEnd, options.lazyLoadTextures); } else if (options.parseAudo && memcmp(chunkName, "AUDO", 4) == 0) { parseAUDO(&reader, dw); } else { @@ -2694,7 +2725,8 @@ DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options) { // If lazy-loading rooms, keep the file handle open for DataWin_loadRoomPayload, otherwise close it now dw->lazyLoadRooms = options.lazyLoadRooms; - if (options.lazyLoadRooms) { + dw->lazyLoadTextures = options.lazyLoadTextures; + if (options.lazyLoadRooms || options.lazyLoadTextures) { dw->lazyLoadFile = file; dw->lazyLoadFilePath = safeStrdup(filePath); dw->fileSize = (size_t) fileSize; @@ -2916,7 +2948,7 @@ void DataWin_free(DataWin* dw) { free(dw->strgBuffer); free(dw->bytecodeBuffer); - // Close the lazy-load file handle (only open when lazyLoadRooms was enabled) + // Close the lazy-load file handle (only open when lazyLoadRooms/lazyLoadTextures was enabled) if (dw->lazyLoadFile != nullptr) { fclose(dw->lazyLoadFile); dw->lazyLoadFile = nullptr; diff --git a/src/data_win.h b/src/data_win.h index a0ab86f8d..24c6f8101 100644 --- a/src/data_win.h +++ b/src/data_win.h @@ -47,6 +47,8 @@ typedef struct { // If true, Room payloads (backgrounds, views, gameObjects, tiles, layers) are parsed on demand via DataWin_loadRoomPayload during gameplay. bool lazyLoadRooms; + // If true, TXTR objects will be loaded on demand via DataWin_loadTxtrIfNeeded, and unloaded if memory is tight. + bool lazyLoadTextures; // When lazyLoadRooms is true, this list indicates which rooms should be loaded during load time instead of demand. They will also not be freed. StringBooleanEntry* eagerlyLoadedRooms; @@ -930,6 +932,7 @@ struct DataWin { char* lazyLoadFilePath; // owned strdup of the original file path, for diagnostics size_t fileSize; // cached size of the DataWin, captured at parse time. Used for platforms where fseek(SEEK_END)+ftell is unreliable due to buffering (like the PlayStation 2). bool lazyLoadRooms; // mirrors the parser option so Runner can branch without re-reading options + bool lazyLoadTextures; // ditto, but with TXTR pages }; DataWin* DataWin_parse(const char* filePath, DataWinParserOptions options); @@ -949,5 +952,6 @@ bool DataWin_isVersionAtLeast(const DataWin* dw, uint32_t major, uint32_t minor, void DataWin_bumpVersionTo(DataWin* dw, uint32_t major, uint32_t minor, uint32_t release, uint32_t build); void GamePath_computeInternal(GamePath* path); PathPositionResult GamePath_getPosition(GamePath* path, float t); +void DataWin_loadTxtrIfNeeded(DataWin* dw, uint32_t textureId); #endif /* _BS_DATA_WIN_H_ */ diff --git a/src/desktop/main.c b/src/desktop/main.c index 554d3bf8a..68233749b 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -1009,6 +1009,7 @@ int main(int argc, char* argv[]) { options.skipLoadingPreciseMasksForNonPreciseSprites = true; options.loadType = args.loadType; options.lazyLoadRooms = args.lazyRooms; + options.lazyLoadTextures = args.lazyTextures; options.eagerlyLoadedRooms = args.eagerRooms; DataWin* dataWin = DataWin_parse(currentDataWinPath, options); diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index c9863fd3c..473b987d6 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -803,6 +803,8 @@ bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) { DataWin* dw = gl->base.dataWin; Texture* txtr = &dw->txtr.textures[pageId]; + DataWin_loadTxtrIfNeeded(dw, pageId); + int w, h; bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0); uint8_t* pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h); diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 4feba91b4..501e5fd1a 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -323,6 +323,8 @@ bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId) DataWin* dw = gl->base.dataWin; Texture* txtr = &dw->txtr.textures[pageId]; + DataWin_loadTxtrIfNeeded(dw, pageId); + bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0); uint8_t* pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h); if (pixels == nullptr) {