diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 3c929007ed4..97dfefa97c3 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -1,7 +1,7 @@ --- name: Sega Dreamcast -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy push: branches: - master @@ -84,6 +84,18 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make + # diabdat.mpq isn't available so this doesn't do anything, but I'll leave it here for documentation purposes + - name: Patch RAM-heavy assets + if: ${{ hashFiles('DIABDAT.MPQ') != '' }} + run: | + unpack_and_minify_mpq DIABDAT.MPQ && \ + curl -LO https://gist.github.com/azihassan/85a7bb8c49ff23fe4d8cf7cb9fdfc8c4/raw/d88f732c2dc44f9ee9f3d56dd942ff0b0b849811/clx.cpp && \ + g++ -std=c++11 clx.cpp && \ + ./a.out diabdat/monsters/black/blackd.clx && \ + ./a.out diabdat/monsters/mage/maged.clx && \ + mv diabdat/monsters/black/blackd.clx.stripped diabdat/monsters/black/blackd.clx && \ + mv diabdat/monsters/mage/maged.clx.stripped diabdat/monsters/mage/maged.clx + - name: Generate .cdi run: | source /opt/toolchains/dc/kos/environ.sh && \ diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 3161c23f131..3a40c147241 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -21,7 +21,7 @@ set(DEVILUTIONX_DISABLE_STRIP ON) set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/data/") set(BUILD_ASSETS_MPQ OFF) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") +#list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS __DREAMCAST__) add_compile_options(-fpermissive) @@ -47,4 +47,4 @@ set(JOY_BUTTON_START 3) set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) -add_compile_options(-flto=none) +#add_compile_options(-flto=none) diff --git a/Dockerfile b/Dockerfile index 6c327a9fa6f..bf27d4c16f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ RUN echo "Cloning project..." WORKDIR /opt/toolchains/dc/kos/ -RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git +RUN git clone -b fix/ISSUE-7-ram-limitations https://github.com/azihassan/devilutionX.git RUN echo "Uninstall kos-ports SDL 1.2..." RUN source /opt/toolchains/dc/kos/environ.sh && \ @@ -25,20 +25,21 @@ RUN git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ cp include/* /usr/include/SDL/ WORKDIR /opt/toolchains/dc/kos/devilutionX -RUN echo "Downloading and unpacking spawn.mpq..." -RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ - unpack_and_minify_mpq spawn.mpq && \ - rm spawn.mpq - RUN echo "Downloading and unpacking fonts.mpq..." RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ unpack_and_minify_mpq fonts.mpq && \ rm fonts.mpq -#WORKDIR /opt/toolchains/dc/kos/devilutionX -#RUN echo "Copying and unpacking diabdat.mpq..." -#COPY DIABDAT.MPQ . -#RUN unpack_and_minify_mpq DIABDAT.MPQ +#spawn version +#RUN echo "Downloading and unpacking spawn.mpq..." +#RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ +# unpack_and_minify_mpq spawn.mpq && \ +# rm spawn.mpq + +#full version +RUN echo "Copying and unpacking diabdat.mpq..." +COPY DIABDAT.MPQ . +RUN unpack_and_minify_mpq DIABDAT.MPQ RUN echo "Configuring CMake..." RUN source /opt/toolchains/dc/kos/environ.sh && \ @@ -50,11 +51,20 @@ RUN source /opt/toolchains/dc/kos/environ.sh && \ RUN echo "Compiling..." RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make +RUN echo "Patching RAM-heavy assets..." +RUN [ -e diabdat ] && \ + curl -LO https://gist.github.com/azihassan/85a7bb8c49ff23fe4d8cf7cb9fdfc8c4/raw/d88f732c2dc44f9ee9f3d56dd942ff0b0b849811/clx.cpp && \ + g++ -std=c++11 clx.cpp && \ + ./a.out diabdat/monsters/black/blackd.clx && \ + ./a.out diabdat/monsters/mage/maged.clx && \ + mv diabdat/monsters/black/blackd.clx.stripped diabdat/monsters/black/blackd.clx && \ + mv diabdat/monsters/mage/maged.clx.stripped diabdat/monsters/mage/maged.clx + RUN echo "Generating CDI" RUN source /opt/toolchains/dc/kos/environ.sh && \ - mv spawn build/data/spawn && \ + #mv spawn build/data/spawn && \ mv fonts/fonts/ build/data/fonts/ && \ - #mv diabdat build/data/diabdat && \ + mv diabdat build/data/diabdat && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f291841044c..804a38b9c43 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -281,6 +281,9 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG) utils/surface_to_png.cpp ) endif() +if(PLATFORM_DREAMCAST) + list(APPEND libdevilutionx_SRCS memory_stats.cpp) +endif() add_devilutionx_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index d8974ce86fc..2009e8a35a9 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -105,6 +105,9 @@ #ifdef GPERF_HEAP_FIRST_GAME_ITERATION #include #endif +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#endif namespace devilution { @@ -2546,6 +2549,9 @@ int DiabloMain(int argc, char **argv) #ifdef _DEBUG SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); #endif +#ifdef __DREAMCAST__ + set_system_ram(); +#endif DiabloParseFlags(argc, argv); InitKeymapActions(); @@ -3249,6 +3255,10 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) CompleteProgress(); LoadGameLevelCalculateCursor(); +#ifdef __DREAMCAST__ + Log("Level loaded"); + print_ram_stats(); +#endif } bool game_loop(bool bStartup) diff --git a/Source/memory_stats.cpp b/Source/memory_stats.cpp new file mode 100644 index 00000000000..4e31385240c --- /dev/null +++ b/Source/memory_stats.cpp @@ -0,0 +1,63 @@ +// DREAMCAST memory stats related code + +#include "memory_stats.h" + +#include + +#include +#include + +static unsigned long systemRam = 0x00000000; +static unsigned long elfOffset = 0x00000000; +static unsigned long stackSize = 0x00000000; + +extern unsigned long end; +extern unsigned long start; + +#define _end end +#define _start start +void print_VRAM_stats() +{ + pvr_mem_available(); +} + +void set_system_ram() +{ + systemRam = 0x8d000000 - 0x8c000000; + elfOffset = 0x8c000000; + + stackSize = (int)&_end - (int)&_start + ((int)&_start - elfOffset); +} + +unsigned long get_system_ram() +{ + return systemRam; +} + +unsigned long get_free_ram() +{ + struct mallinfo mi = mallinfo(); + return systemRam - (mi.usmblks + stackSize); +} + +void print_ram_stats() +{ + float sys_ram, free_ram, used_ram, pvr_ram; + sys_ram = (float)get_system_ram() / (float)(1024 * 1024); + free_ram = (float)get_free_ram() / (float)(1024 * 1024); + used_ram = (sys_ram - free_ram); + // pvr_ram = (float)pvr_mem_available() / (float)(1024*1024); + + printf("\n---------\nRAM stats (MB):\nTotal: %.2f, Free: %.2f, Used: %.2f, PVR: %.2f\n---------\n", sys_ram, free_ram, used_ram, pvr_ram); + // printf("\n---------\nRAM stats (MB):\nTotal: %.2f, Free: %.2f, Used: %.2f\n---------\n", sys_ram, free_ram, used_ram); +} + +void get_ram_stats(float *sys_ram, float *free_ram, float *used_ram, float *pvr_ram) +{ + *sys_ram = (float)get_system_ram() / (float)(1024 * 1024); + *free_ram = (float)get_free_ram() / (float)(1024 * 1024); + *used_ram = (*sys_ram - *free_ram); + if (pvr_ram) { + //*pvr_ram = (float)pvr_mem_available() / (float)(1024*1024); + } +} diff --git a/Source/memory_stats.h b/Source/memory_stats.h new file mode 100644 index 00000000000..895007b70fa --- /dev/null +++ b/Source/memory_stats.h @@ -0,0 +1,11 @@ +#ifndef __MEMORY_STATS_H__ +#define __MEMORY_STATS_H__ 1 + +void set_system_ram(); +void print_VRAM_stats(); +unsigned long get_system_ram(); +unsigned long get_free_ram(); +void print_ram_stats(); +void get_ram_stats(float *sys_ram, float *free_ram, float *used_ram, float *pvr_ram); + +#endif diff --git a/Source/monster.cpp b/Source/monster.cpp index cb7fb62530f..0409a31407a 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -54,6 +54,9 @@ #ifdef _DEBUG #include "debug.h" #endif +#ifdef __DREAMCAST__ +#include "memory_stats.h" +#endif namespace devilution { @@ -115,6 +118,21 @@ size_t GetNumAnimsWithGraphics(const MonsterData &monsterData) return result; } +#ifdef __DREAMCAST__ +void ReorientMonsterToSouth(Monster &monster) +{ + // knights and mages only have the south facing death animation in the dreamcast port + // the death animations are similar because they show the monster rotate into the ground + // so I removed the other directions for less RAM usage + // 1018 kB down to 140 kB for the knights and + // 311 kB down to 40 kB for the mages + bool hasSouthDeathAnimationOnly = monster.type().type == MT_NBLACK || monster.type().type == MT_RTBLACK || monster.type().type == MT_BTBLACK || monster.type().type == MT_RBLACK || monster.type().type == MT_COUNSLR || monster.type().type == MT_MAGISTR || monster.type().type == MT_CABALIST || monster.type().type == MT_ADVOCATE; + if (hasSouthDeathAnimationOnly) { + monster.direction = Direction::South; + } +} +#endif + void InitMonsterTRN(CMonster &monst) { char path[64]; @@ -1441,6 +1459,9 @@ void ShrinkLeaderPacksize(const Monster &monster) void MonsterDeath(Monster &monster) { +#ifdef __DREAMCAST__ + ReorientMonsterToSouth(monster); +#endif monster.var1++; if (monster.type().type == MT_DIABLO) { if (monster.position.tile.x < ViewPosition.x) { @@ -3449,6 +3470,14 @@ void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) GetMissileSpriteData(MissileGraphicID::DiabloApocalypseBoom).LoadGFX(); } +#ifdef __DREAMCAST__ +void print_memory_usage() +{ + print_ram_stats(); + Log("\n\n\n"); +} +#endif + void InitAllMonsterGFX() { if (HeadlessMode) @@ -3467,6 +3496,10 @@ void InitAllMonsterGFX() CMonster &firstMonster = LevelMonsterTypes[monsterTypes[0]]; if (firstMonster.animData != nullptr) continue; +#ifdef __DREAMCAST__ + Log("Loading monster graphics: {:15s} x{:d}", firstMonster.data().spritePath(), monsterTypes.size()); + print_memory_usage(); +#endif MonsterSpritesData spritesData = LoadMonsterSpritesData(firstMonster.data()); const size_t spritesDataSize = spritesData.offsets[GetNumAnimsWithGraphics(firstMonster.data())]; for (size_t i = 1; i < monsterTypes.size(); ++i) { @@ -3475,6 +3508,9 @@ void InitAllMonsterGFX() InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy)); } LogVerbose("Loaded monster graphics: {:15s} {:>4d} KiB x{:d}", firstMonster.data().spritePath(), spritesDataSize / 1024, monsterTypes.size()); +#ifdef __DREAMCAST__ + print_memory_usage(); +#endif totalUniqueBytes += spritesDataSize; totalBytes += spritesDataSize * monsterTypes.size(); InitMonsterGFX(firstMonster, std::move(spritesData)); @@ -3795,6 +3831,10 @@ void MonsterDeath(Monster &monster, Direction md, bool sendmsg) if (monster.mode != MonsterMode::Petrified) { if (monster.type().type == MT_GOLEM) md = Direction::South; +#ifdef __DREAMCAST__ + md = Direction::South; + ReorientMonsterToSouth(monster); +#endif NewMonsterAnim(monster, MonsterGraphic::Death, md, gGameLogicStep < GameLogicStep::ProcessMonsters ? AnimationDistributionFlags::ProcessAnimationPending : AnimationDistributionFlags::None); monster.mode = MonsterMode::Death; } else if (monster.isUnique()) {