From dceeaaa9302b7e9ce85fa773fc187bc593f3c93c Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Thu, 24 Jul 2025 01:58:27 -0500 Subject: [PATCH] Squashed 'deps/nostalgia/' changes from 671b8eda..fae1e73e fae1e73e [nostalgia/gfx] Make getSubSheet check root subsheet name 51f2905c [nostalgia/gfx/studio] Make TileSheetEditor use export-tilesheet functions for export, fix exporting Root subsheet 0c866d1b [studio,nostalgia/gfx] Add system for adding sub-commands in Modules, add export-tilesheet command fdf39d1a [ox/std] Add Result::transformError a523a75e [ox/std] Add missing include to StringParam cdaa64ed [ox/clargs] Fix arg parsing for first '-' 37b5fcc0 [teagba] Put parentheses around all registers f5f2c3be [studio/applib] Make Studio remove files unable to be opened from open file list in config f6ef2b5a [turbine,nostalgia] Make memory regions cast to bounded ox::Arrays bf958a4a [teagba] Make memory regions cast to bounded ox::Arrays 6a70e478 [nostalgia/gfx] Cleanup git-subtree-dir: deps/nostalgia git-subtree-split: fae1e73e54a420d4b93af67e03144d5442825a11 --- deps/ox/src/ox/clargs/clargs.cpp | 12 +- deps/ox/src/ox/std/error.hpp | 7 + deps/ox/src/ox/std/stringparam.hpp | 1 + deps/teagba/include/teagba/addresses.hpp | 92 +++++----- .../gfx/include/nostalgia/gfx/tilesheet.hpp | 8 + src/nostalgia/modules/gfx/src/gfx-gba.cpp | 18 +- src/nostalgia/modules/gfx/src/gfx-opengl.cpp | 40 ++--- .../modules/gfx/src/studio/CMakeLists.txt | 1 + .../modules/gfx/src/studio/studiomodule.cpp | 18 +- .../gfx/src/studio/subcommands/CMakeLists.txt | 10 ++ .../export-tilesheet/export-tilesheet.cpp | 158 ++++++++++++++++++ .../export-tilesheet/export-tilesheet.hpp | 28 ++++ .../tilesheeteditor/tilesheeteditor-imgui.cpp | 69 +------- src/nostalgia/modules/gfx/src/tilesheet.cpp | 41 +++++ .../modules/sound/src/studio/studiomodule.cpp | 6 +- .../applib/include/studioapp/studioapp.hpp | 3 + src/olympic/studio/applib/src/app.cpp | 46 ++++- src/olympic/studio/applib/src/studioui.cpp | 30 ++-- .../studio/modlib/include/studio/module.hpp | 25 ++- .../studio/modlib/include/studio/project.hpp | 12 +- src/olympic/studio/modlib/src/module.cpp | 4 + src/olympic/studio/modlib/src/project.cpp | 2 +- src/olympic/turbine/src/gba/turbine.cpp | 2 +- 23 files changed, 455 insertions(+), 178 deletions(-) create mode 100644 src/nostalgia/modules/gfx/src/studio/subcommands/CMakeLists.txt create mode 100644 src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.cpp create mode 100644 src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.hpp diff --git a/deps/ox/src/ox/clargs/clargs.cpp b/deps/ox/src/ox/clargs/clargs.cpp index ddeebe2..854a88e 100644 --- a/deps/ox/src/ox/clargs/clargs.cpp +++ b/deps/ox/src/ox/clargs/clargs.cpp @@ -15,7 +15,7 @@ ClArgs::ClArgs(int argc, const char **args) noexcept: ClArgs({args, static_cast< ClArgs::ClArgs(ox::SpanView args) noexcept { for (auto i = 0u; i < args.size(); ++i) { - auto arg = StringView(args[i]); + auto arg = StringView{args[i]}; if (arg[0] == '-') { while (arg[0] == '-' && arg.len()) { arg = substr(arg, 1); @@ -23,8 +23,8 @@ ClArgs::ClArgs(ox::SpanView args) noexcept { m_bools[arg] = true; // parse additional arguments if (i < args.size() && args[i + 1]) { - auto val = String(args[i + 1]); - if (val.len() && val[i] != '-') { + auto const val = StringView{args[i + 1]}; + if (val.len() && val[0] != '-') { if (val == "false") { m_bools[arg] = false; } @@ -40,17 +40,17 @@ ClArgs::ClArgs(ox::SpanView args) noexcept { } bool ClArgs::getBool(ox::StringViewCR arg, bool defaultValue) const noexcept { - auto [value, err] = m_ints.at(arg); + auto const [value, err] = m_ints.at(arg); return !err ? *value : defaultValue; } String ClArgs::getString(ox::StringViewCR arg, ox::StringView defaultValue) const noexcept { - auto [value, err] = m_strings.at(arg); + auto const [value, err] = m_strings.at(arg); return !err ? ox::String(*value) : ox::String(defaultValue); } int ClArgs::getInt(ox::StringViewCR arg, int defaultValue) const noexcept { - auto [value, err] = m_ints.at(arg); + auto const [value, err] = m_ints.at(arg); return !err ? *value : defaultValue; } diff --git a/deps/ox/src/ox/std/error.hpp b/deps/ox/src/ox/std/error.hpp index 6030a52..8b21166 100644 --- a/deps/ox/src/ox/std/error.hpp +++ b/deps/ox/src/ox/std/error.hpp @@ -307,6 +307,13 @@ struct [[nodiscard]] Result { return std::move(value); } + constexpr Result transformError(ErrorCode const ec, CString const msg) && { + if (error) { + error = Error{ec, msg}; + } + return *this; + } + }; namespace detail { diff --git a/deps/ox/src/ox/std/stringparam.hpp b/deps/ox/src/ox/std/stringparam.hpp index 32e9e5b..a64b71f 100644 --- a/deps/ox/src/ox/std/stringparam.hpp +++ b/deps/ox/src/ox/std/stringparam.hpp @@ -8,6 +8,7 @@ #pragma once +#include "cstringview.hpp" #include "string.hpp" namespace ox { diff --git a/deps/teagba/include/teagba/addresses.hpp b/deps/teagba/include/teagba/addresses.hpp index 7a19f86..23380b1 100644 --- a/deps/teagba/include/teagba/addresses.hpp +++ b/deps/teagba/include/teagba/addresses.hpp @@ -5,103 +5,101 @@ #pragma once #include -#include +#include ///////////////////////////////////////////////////////////////// // Interrupt Handler -using interrupt_handler = void (*)(); -#define REG_ISR *reinterpret_cast(0x0300'7FFC) -#define REG_IE *reinterpret_cast(0x0400'0200) -#define REG_IF *reinterpret_cast(0x0400'0202) -#define REG_IME *reinterpret_cast(0x0400'0208) +using InterruptHandler = void(*)(); +#define REG_ISR (*reinterpret_cast(0x0300'7FFC)) +#define REG_IE (*reinterpret_cast(0x0400'0200)) +#define REG_IF (*reinterpret_cast(0x0400'0202)) +#define REG_IME (*reinterpret_cast(0x0400'0208)) ///////////////////////////////////////////////////////////////// // Display Registers -#define REG_DISPCTL *reinterpret_cast(0x0400'0000) -#define REG_DISPSTAT *reinterpret_cast(0x0400'0004) -#define REG_VCOUNT *reinterpret_cast(0x0400'0006) +#define REG_DISPCTL (*reinterpret_cast(0x0400'0000)) +#define REG_DISPSTAT (*reinterpret_cast(0x0400'0004)) +#define REG_VCOUNT (*reinterpret_cast(0x0400'0006)) ///////////////////////////////////////////////////////////////// // Timers -#define REG_TIMER0 *reinterpret_cast(0x0400'0100) -#define REG_TIMER0CTL *reinterpret_cast(0x0400'0102) +#define REG_TIMER0 (*reinterpret_cast(0x0400'0100)) +#define REG_TIMER0CTL (*reinterpret_cast(0x0400'0102)) -#define REG_TIMER1 *reinterpret_cast(0x0400'0104) -#define REG_TIMER1CTL *reinterpret_cast(0x0400'0106) +#define REG_TIMER1 (*reinterpret_cast(0x0400'0104)) +#define REG_TIMER1CTL (*reinterpret_cast(0x0400'0106)) -#define REG_TIMER2 *reinterpret_cast(0x0400'0108) -#define REG_TIMER2CTL *reinterpret_cast(0x0400'010a) +#define REG_TIMER2 (*reinterpret_cast(0x0400'0108)) +#define REG_TIMER2CTL (*reinterpret_cast(0x0400'010a)) -#define REG_TIMER3 *reinterpret_cast(0x0400'010c) -#define REG_TIMER3CTL *reinterpret_cast(0x0400'010e) +#define REG_TIMER3 (*reinterpret_cast(0x0400'010c)) +#define REG_TIMER3CTL (*reinterpret_cast(0x0400'010e)) ///////////////////////////////////////////////////////////////// // background registers // background control registers using BgCtl = uint16_t; -#define REG_BG0CTL *reinterpret_cast(0x0400'0008) -#define REG_BG1CTL *reinterpret_cast(0x0400'000a) -#define REG_BG2CTL *reinterpret_cast(0x0400'000c) -#define REG_BG3CTL *reinterpret_cast(0x0400'000e) +#define REG_BG0CTL (*reinterpret_cast(0x0400'0008)) +#define REG_BG1CTL (*reinterpret_cast(0x0400'000a)) +#define REG_BG2CTL (*reinterpret_cast(0x0400'000c)) +#define REG_BG3CTL (*reinterpret_cast(0x0400'000e)) [[nodiscard]] -inline volatile BgCtl ®BgCtl(uintptr_t bgIdx) noexcept { +inline volatile BgCtl ®BgCtl(uintptr_t const bgIdx) noexcept { return *reinterpret_cast(0x0400'0008 + 2 * bgIdx); } // background horizontal scrolling registers -#define REG_BG0HOFS *reinterpret_cast(0x0400'0010) -#define REG_BG1HOFS *reinterpret_cast(0x0400'0014) -#define REG_BG2HOFS *reinterpret_cast(0x0400'0018) -#define REG_BG3HOFS *reinterpret_cast(0x0400'001c) +#define REG_BG0HOFS (*reinterpret_cast(0x0400'0010)) +#define REG_BG1HOFS (*reinterpret_cast(0x0400'0014)) +#define REG_BG2HOFS (*reinterpret_cast(0x0400'0018)) +#define REG_BG3HOFS (*reinterpret_cast(0x0400'001c)) [[nodiscard]] -inline volatile uint32_t ®BgHofs(auto bgIdx) noexcept { +volatile uint32_t ®BgHofs(auto const bgIdx) noexcept { return *reinterpret_cast(0x0400'0010 + 4 * bgIdx); } // background vertical scrolling registers -#define REG_BG0VOFS *reinterpret_cast(0x0400'0012) -#define REG_BG1VOFS *reinterpret_cast(0x0400'0016) -#define REG_BG2VOFS *reinterpret_cast(0x0400'001a) -#define REG_BG3VOFS *reinterpret_cast(0x0400'001e) +#define REG_BG0VOFS (*reinterpret_cast(0x0400'0012)) +#define REG_BG1VOFS (*reinterpret_cast(0x0400'0016)) +#define REG_BG2VOFS (*reinterpret_cast(0x0400'001a)) +#define REG_BG3VOFS (*reinterpret_cast(0x0400'001e)) [[nodiscard]] -inline volatile uint32_t ®BgVofs(auto bgIdx) noexcept { +volatile uint32_t ®BgVofs(auto const bgIdx) noexcept { return *reinterpret_cast(0x0400'0012 + 4 * bgIdx); } ///////////////////////////////////////////////////////////////// // User Input -#define REG_GAMEPAD *reinterpret_cast(0x0400'0130) +#define REG_GAMEPAD (*reinterpret_cast(0x0400'0130)) ///////////////////////////////////////////////////////////////// // Memory Addresses -#define MEM_EWRAM_BEGIN reinterpret_cast(0x0200'0000) -#define MEM_EWRAM_END reinterpret_cast(0x0203'FFFF) +#define MEM_EWRAM (*reinterpret_cast*>(0x0200'0000)) -#define MEM_IWRAM_BEGIN reinterpret_cast(0x0300'0000) -#define MEM_IWRAM_END reinterpret_cast(0x0300'7FFF) +#define MEM_IWRAM (*reinterpret_cast*>(0x0300'0000)) -#define REG_BLNDCTL *reinterpret_cast(0x0400'0050) +#define REG_BLNDCTL (*reinterpret_cast(0x0400'0050)) -#define MEM_BG_PALETTE reinterpret_cast(0x0500'0000) -#define MEM_SPRITE_PALETTE reinterpret_cast(0x0500'0200) +using Palette = ox::Array; +#define MEM_BG_PALETTE (*reinterpret_cast<::Palette*>(0x0500'0000)) +#define MEM_SPRITE_PALETTE (*reinterpret_cast<::Palette*>(0x0500'0200)) using BgMapTile = ox::Array; -#define MEM_BG_TILES reinterpret_cast(0x0600'0000) -#define MEM_BG_MAP reinterpret_cast(0x0600'e000) +#define MEM_BG_TILES (*reinterpret_cast*>(0x0600'0000)) +#define MEM_BG_MAP (*reinterpret_cast*>(0x0600'e000)) -#define MEM_SPRITE_TILES reinterpret_cast(0x0601'0000) -#define MEM_OAM reinterpret_cast(0x0700'0000) +#define MEM_SPRITE_TILES (*reinterpret_cast*>(0x0601'0000)) +#define MEM_OAM (*reinterpret_cast*>(0x0700'0000)) -#define MEM_ROM reinterpret_cast(0x0800'0000) +#define MEM_ROM (*reinterpret_cast*>(0x0700'0000)) -#define MEM_SRAM reinterpret_cast(0x0e00'0000) -#define MEM_SRAM_SIZE 65535 +#define MEM_SRAM (*reinterpret_cast*>(0x0e00'0000)) diff --git a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp index c8b5feb..983e976 100644 --- a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp +++ b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp @@ -428,6 +428,14 @@ TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubShe ox::Result getSubSheetIdx(TileSheet const &ts, SubSheetId pId) noexcept; +ox::Result getSubSheet( + ox::SpanView const &idx, + TileSheet &ts) noexcept; + +ox::Result getSubSheet( + ox::SpanView const &idx, + TileSheet const &ts) noexcept; + [[nodiscard]] TileSheet::SubSheet &getSubSheet( ox::SpanView const&idx, diff --git a/src/nostalgia/modules/gfx/src/gfx-gba.cpp b/src/nostalgia/modules/gfx/src/gfx-gba.cpp index faea11b..ad67b35 100644 --- a/src/nostalgia/modules/gfx/src/gfx-gba.cpp +++ b/src/nostalgia/modules/gfx/src/gfx-gba.cpp @@ -35,11 +35,6 @@ class Context { Context(Context const &&other) noexcept = delete; virtual ~Context() noexcept = default; - [[nodiscard]] - ox::MemFS const &rom() const noexcept { - return static_cast(*turbine::rom(turbineCtx)); - } - }; void safeDelete(Context *ctx) noexcept { @@ -81,7 +76,7 @@ ox::Error loadBgPalette( if (palette.pages.empty()) { return {}; } - auto const paletteMem = MEM_BG_PALETTE + palBank * 16; + auto const paletteMem = ox::Span{MEM_BG_PALETTE} + palBank * 16; for (auto i = 0u; i < colorCnt(palette, page); ++i) { paletteMem[i] = color(palette, page, i); } @@ -95,9 +90,8 @@ ox::Error loadSpritePalette( if (palette.pages.empty()) { return {}; } - auto const paletteMem = MEM_SPRITE_PALETTE; for (auto i = 0u; i < colorCnt(palette, page); ++i) { - paletteMem[i] = color(palette, page, i); + MEM_SPRITE_PALETTE[i] = color(palette, page, i); } return {}; } @@ -240,7 +234,7 @@ ox::Error loadSpriteTileSheet( Context &ctx, TileSheetSet const &set) noexcept { auto const bpp = static_cast(set.bpp); - OX_RETURN_ERROR(loadTileSheetSet(ctx, {MEM_SPRITE_TILES, 32 * ox::units::KB}, set)); + OX_RETURN_ERROR(loadTileSheetSet(ctx, MEM_SPRITE_TILES, set)); setSpritesBpp(bpp); return {}; } @@ -349,9 +343,9 @@ void panic(char const *file, int line, char const *panicMsg, ox::Error const &er using namespace nostalgia::gfx; // reset heap to make sure we have enough memory to allocate context data OX_ALLOW_UNSAFE_BUFFERS_BEGIN - auto const heapBegin = reinterpret_cast(MEM_EWRAM_BEGIN); - auto const heapSz = (MEM_EWRAM_END - MEM_EWRAM_BEGIN) / 2; - auto const heapEnd = reinterpret_cast(MEM_EWRAM_BEGIN + heapSz); + auto const heapBegin = reinterpret_cast(MEM_EWRAM.data()); + auto const heapSz = MEM_EWRAM.size() / 2; + auto const heapEnd = reinterpret_cast(MEM_EWRAM.data() + heapSz); ox::heapmgr::initHeap(heapBegin, heapEnd); OX_ALLOW_UNSAFE_BUFFERS_END auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap(); diff --git a/src/nostalgia/modules/gfx/src/gfx-opengl.cpp b/src/nostalgia/modules/gfx/src/gfx-opengl.cpp index a3a17f3..778b451 100644 --- a/src/nostalgia/modules/gfx/src/gfx-opengl.cpp +++ b/src/nostalgia/modules/gfx/src/gfx-opengl.cpp @@ -453,7 +453,7 @@ static void drawBackgrounds( } } -static void drawSprites(Context &ctx, ox::Size const&renderSz) noexcept { +static void drawSprites(Context &ctx, ox::Size const &renderSz) noexcept { glUseProgram(ctx.spriteShader); auto &sb = ctx.spriteBlocks; auto const uniformXScale = glGetUniformLocation(ctx.bgShader, "vXScale"); @@ -481,7 +481,7 @@ static void loadPalette( ox::Array &palette, size_t const palOffset, GLuint const shaderPgrm, - CompactPalette const&pal, + CompactPalette const &pal, size_t const page = 0) noexcept { static constexpr std::size_t ColorCnt = 256; for (auto i = palOffset; auto const c : pal.pages[page]) { @@ -500,7 +500,7 @@ static void loadPalette( static void setSprite( Context &ctx, uint_t const idx, - Sprite const&s) noexcept { + Sprite const &s) noexcept { // Tonc Table 8.4 struct Sz { uint_t x{}, y{}; }; static constexpr ox::Array dimensions{ @@ -574,7 +574,7 @@ static void setSprite( } -ox::Result> init(turbine::Context &tctx, InitParams const¶ms) noexcept { +ox::Result> init(turbine::Context &tctx, InitParams const ¶ms) noexcept { auto ctx = ox::make_unique(tctx, params); auto const bgVshad = ox::sfmt(renderer::bgvshadTmpl, gl::GlslVersion); auto const bgFshad = ox::sfmt(renderer::bgfshadTmpl, gl::GlslVersion); @@ -604,7 +604,7 @@ struct TileSheetData { }; static ox::Result normalizeTileSheet - (CompactTileSheet const&ts) noexcept { + (CompactTileSheet const &ts) noexcept { const uint_t bytesPerTile = ts.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2; auto const tiles = ts.pixels.size() / bytesPerTile; constexpr int width = 8; @@ -632,7 +632,7 @@ static ox::Result normalizeTileSheet ox::Error loadBgPalette( Context &ctx, size_t const palBank, - CompactPalette const&palette, + CompactPalette const &palette, size_t const page) noexcept { renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, palette, page); return {}; @@ -640,7 +640,7 @@ ox::Error loadBgPalette( ox::Error loadSpritePalette( Context &ctx, - CompactPalette const&palette, + CompactPalette const &palette, size_t const page) noexcept { ox::Array pal; renderer::loadPalette(pal, 0, ctx.spriteShader, palette, page); @@ -649,14 +649,14 @@ ox::Error loadSpritePalette( static ox::Result buildSetTsd( Context const &ctx, - TileSheetSet const&set) noexcept { + TileSheetSet const &set) noexcept { auto &kctx = keelCtx(ctx.turbineCtx); TileSheetData setTsd; setTsd.width = TileWidth; - for (auto const&entry : set.entries) { + for (auto const &entry : set.entries) { OX_REQUIRE(tilesheet, readObj(kctx, entry.tilesheet)); OX_REQUIRE(tsd, normalizeTileSheet(*tilesheet)); - for (auto const&s : entry.sections) { + for (auto const &s : entry.sections) { auto const size = s.tiles * PixelsPerTile; for (auto i = 0; i < size; ++i) { auto const srcIdx = static_cast(i) + static_cast(s.begin * PixelsPerTile); @@ -669,7 +669,7 @@ static ox::Result buildSetTsd( } static void copyPixels( - CompactTileSheet const&ts, + CompactTileSheet const &ts, ox::Span const dst, size_t const srcPxIdx, size_t const pxlCnt) noexcept { @@ -704,7 +704,7 @@ void clearCbbs(Context &ctx) noexcept { ox::Error loadBgTileSheet( Context &ctx, unsigned const cbb, - CompactTileSheet const&ts, + CompactTileSheet const &ts, size_t const dstTileIdx, size_t const srcTileIdx, size_t const tileCnt) noexcept { @@ -728,8 +728,8 @@ ox::Error loadBgTileSheet( ox::Error loadBgTileSheet( Context &ctx, uint_t const cbb, - CompactTileSheet const&ts, - ox::Optional const&paletteBank) noexcept { + CompactTileSheet const &ts, + ox::Optional const &paletteBank) noexcept { auto const bytesPerTile = static_cast(PixelsPerTile / (1 + (ts.bpp == 4))); auto const tiles = ts.pixels.size() / bytesPerTile; OX_RETURN_ERROR(loadBgTileSheet(ctx, cbb, ts, 0, 0, tiles)); @@ -742,7 +742,7 @@ ox::Error loadBgTileSheet( ox::Error loadBgTileSheet( Context &ctx, unsigned const cbb, - TileSheetSet const&set) noexcept { + TileSheetSet const &set) noexcept { OX_REQUIRE(setTsd, buildSetTsd(ctx, set)); ctx.cbbs[cbb].tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data()); return {}; @@ -750,7 +750,7 @@ ox::Error loadBgTileSheet( ox::Error loadSpriteTileSheet( Context &ctx, - CompactTileSheet const&ts, + CompactTileSheet const &ts, bool const loadDefaultPalette) noexcept { OX_REQUIRE(tsd, normalizeTileSheet(ts)); oxTracef("nostalgia.gfx.gl", "loadSpriteTexture: { w: {}, h: {} }", tsd.width, tsd.height); @@ -763,7 +763,7 @@ ox::Error loadSpriteTileSheet( ox::Error loadSpriteTileSheet( Context &ctx, - TileSheetSet const&set) noexcept { + TileSheetSet const &set) noexcept { OX_REQUIRE(setTsd, buildSetTsd(ctx, set)); ctx.spriteBlocks.tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data()); return {}; @@ -774,7 +774,7 @@ void setBgTile( uint_t const bgIdx, int const column, int const row, - BgTile const&tile) noexcept { + BgTile const &tile) noexcept { oxTracef( "nostalgia.gfx.setBgTile", "bgIdx: {}, column: {}, row: {}, tile: {}, palBank: {}", @@ -853,7 +853,7 @@ void showSprite(Context &ctx, uint_t const idx) noexcept { renderer::setSprite(ctx, idx, s); } -void setSprite(Context &ctx, uint_t const idx, Sprite const&sprite) noexcept { +void setSprite(Context &ctx, uint_t const idx, Sprite const &sprite) noexcept { auto &s = ctx.spriteStates[idx]; s = sprite; renderer::setSprite(ctx, idx, s); @@ -869,7 +869,7 @@ ox::Size drawSize(int const scale) noexcept { return {240 * scale, 160 * scale}; } -void draw(Context &ctx, ox::Size const&renderSz) noexcept { +void draw(Context &ctx, ox::Size const &renderSz) noexcept { glViewport(0, 0, renderSz.width, renderSz.height); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); diff --git a/src/nostalgia/modules/gfx/src/studio/CMakeLists.txt b/src/nostalgia/modules/gfx/src/studio/CMakeLists.txt index 75c008c..c6201fa 100644 --- a/src/nostalgia/modules/gfx/src/studio/CMakeLists.txt +++ b/src/nostalgia/modules/gfx/src/studio/CMakeLists.txt @@ -26,3 +26,4 @@ install( add_subdirectory(paletteeditor) add_subdirectory(tilesheeteditor) +add_subdirectory(subcommands) diff --git a/src/nostalgia/modules/gfx/src/studio/studiomodule.cpp b/src/nostalgia/modules/gfx/src/studio/studiomodule.cpp index 58b03a2..d6e7681 100644 --- a/src/nostalgia/modules/gfx/src/studio/studiomodule.cpp +++ b/src/nostalgia/modules/gfx/src/studio/studiomodule.cpp @@ -8,10 +8,25 @@ #include "paletteeditor/paletteeditor-imgui.hpp" #include "tilesheeteditor/tilesheeteditor-imgui.hpp" +#include "subcommands/export-tilesheet/export-tilesheet.hpp" namespace nostalgia::gfx { -static class: public studio::Module { +static struct: studio::Module { + + ox::String id() const noexcept final { + return ox::String{"net.drinkingtea.nostalgia.gfx"}; + } + + ox::Vector commands() const final { + return { + { + "export-tilesheet", + cmdExportTilesheet, + } + }; + } + ox::Vector editors(studio::Context &ctx) const noexcept final { return { studio::editorMaker(ctx, {FileExt_ng, FileExt_nts}), @@ -28,6 +43,7 @@ static class: public studio::Module { }, ox::ClawFormat::Organic)); return out; } + } const mod; studio::Module const *studioModule() noexcept { diff --git a/src/nostalgia/modules/gfx/src/studio/subcommands/CMakeLists.txt b/src/nostalgia/modules/gfx/src/studio/subcommands/CMakeLists.txt new file mode 100644 index 0000000..64d9706 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/subcommands/CMakeLists.txt @@ -0,0 +1,10 @@ + +target_sources( + NostalgiaGfx-Studio PRIVATE + export-tilesheet/export-tilesheet.cpp +) + +target_link_libraries( + NostalgiaGfx-Studio PUBLIC + OxClArgs +) \ No newline at end of file diff --git a/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.cpp b/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.cpp new file mode 100644 index 0000000..03f510b --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include +#include + +#include + +#include +#include + +#include "export-tilesheet.hpp" + +#include "nostalgia/gfx/tilesheet.hpp" +#include "studio/context.hpp" + + +namespace nostalgia::gfx { + +static ox::Vector normalizePixelSizes( + ox::Vector const &inPixels) noexcept { + ox::Vector outPixels; + outPixels.reserve(inPixels.size()); + outPixels.resize(inPixels.size()); + for (size_t i{}; i < inPixels.size(); ++i) { + outPixels[i] = inPixels[i]; + } + return outPixels; +} + +static ox::Vector normalizePixelArrangement( + ox::Vector const &inPixels, + int const cols, + int const scale) { + auto const scalePt = ox::Point{scale, scale}; + auto const width = cols * TileWidth; + auto const height = static_cast(inPixels.size()) / width; + auto const dstWidth = width * scale; + ox::Vector outPixels(static_cast((width * scale) * (height * scale))); + for (size_t dstIdx{}; dstIdx < outPixels.size(); ++dstIdx) { + auto const dstPt = ox::Point{ + static_cast(dstIdx) % dstWidth, + static_cast(dstIdx) / dstWidth}; + auto const srcPt = dstPt / scalePt; + auto const srcIdx = ptToIdx(srcPt, cols); + outPixels[dstIdx] = inPixels[srcIdx]; + } + return outPixels; +} + +static ox::Error toPngFile( + ox::CStringViewCR path, + ox::Vector &&pixels, + Palette const &pal, + size_t const page, + unsigned const width, + unsigned const height) noexcept { + for (auto &c : pixels) { + c = color32(color(pal, page, c)) | static_cast(0XFF << 24); + } + constexpr auto fmt = LCT_RGBA; + return ox::Error(static_cast( + lodepng_encode_file( + path.c_str(), + reinterpret_cast(pixels.data()), + width, + height, + fmt, + 8))); +} + +ox::Error exportSubsheetToPng( + TileSheet::SubSheet const &s, + Palette const &pal, + size_t const palPage, + ox::CStringViewCR dstPath, + int const scale) noexcept { + // subsheet to png + auto const width = s.columns * TileWidth; + auto const height = s.rows * TileHeight; + auto const err = toPngFile( + dstPath, + normalizePixelArrangement( + normalizePixelSizes(s.pixels), + s.columns, + scale), + pal, + palPage, + static_cast(width * scale), + static_cast(height * scale)); + if (err) { + oxErrorf("TileSheet export failed: {}", toStr(err)); + return ox::Error{7, "TileSheet export failed"}; + } + return {}; +} + +ox::Error exportSubsheetToPng( + TileSheet const &src, + Palette const &pal, + ox::StringViewCR subsheetPath, + size_t const palPage, + ox::CStringViewCR dstPath, + int const scale) noexcept { + // subsheet to png + auto const [s, ssErr] = getSubSheet( + ox::split(subsheetPath, '.'), src); + if (ssErr) { + return ox::Error{6, "failed to find SubSheet"}; + } + return exportSubsheetToPng( + *s, + pal, + palPage, + dstPath, + scale); +} + +ox::Error cmdExportTilesheet(studio::Project &project, ox::SpanView const args) noexcept { + // parse args + ox::ClArgs const clargs{args}; + bool showUsage = true; + OX_DEFER [&showUsage] { + if (showUsage) { + oxErr("usage: export-tilesheet " + "-src-path " + "-dst-path " + "[-pal-path ] " + "[-subsheet-path ] " + "[-scale ]\n"); + } + }; + OX_REQUIRE(srcPath, clargs.getString("src-path").transformError(1, "no src path specified")); + OX_REQUIRE(dstPath, clargs.getString("dst-path").transformError(2, "no dst path specified")); + auto const palPath = clargs.getString("pal-path", ""); + auto const subsheetPath = clargs.getString("subsheet-path").or_value(ox::String{"Root"}); + auto const palPage = static_cast(clargs.getInt("pal-page", 0)); + auto const scale = clargs.getInt("scale", 1); + showUsage = false; + // load objects + auto &kctx = project.kctx(); + OX_REQUIRE(ts, keel::readObj(kctx, srcPath).transformError(4, "could not load TileSheet")); + OX_REQUIRE(pal, keel::readObj(kctx, palPath.len() ? palPath : ts->defaultPalette) + .transformError(5, "could not load Palette")); + // export to the destination file + return exportSubsheetToPng( + *ts, + *pal, + subsheetPath, + palPage, + dstPath, + scale); +} + +} diff --git a/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.hpp b/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.hpp new file mode 100644 index 0000000..ee0faf2 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/subcommands/export-tilesheet/export-tilesheet.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace nostalgia::gfx { + +ox::Error exportSubsheetToPng( + TileSheet::SubSheet const &s, + Palette const &pal, + size_t palPage, + ox::CStringViewCR dstPath, + int scale) noexcept; + +ox::Error exportSubsheetToPng( + TileSheet const &src, + Palette const &pal, + ox::StringViewCR subsheetPath, + size_t palPage, + ox::CStringViewCR dstPath, + int scale) noexcept; + +ox::Error cmdExportTilesheet(studio::Project& project, ox::SpanView args) noexcept; + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index 6640c1e..b894d98 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -11,6 +11,7 @@ #include +#include "../subcommands/export-tilesheet/export-tilesheet.hpp" #include "tilesheeteditor-imgui.hpp" namespace nostalgia::gfx { @@ -37,58 +38,6 @@ OX_MODEL_BEGIN(TileSheetEditorConfig) OX_MODEL_FIELD_RENAME(activeSubsheet, active_subsheet) OX_MODEL_END() -static ox::Vector normalizePixelSizes( - ox::Vector const &inPixels) noexcept { - ox::Vector outPixels; - outPixels.reserve(inPixels.size()); - outPixels.resize(inPixels.size()); - for (std::size_t i = 0; i < inPixels.size(); ++i) { - outPixels[i] = inPixels[i]; - } - return outPixels; -} - -static ox::Vector normalizePixelArrangement( - ox::Vector const &inPixels, - int const cols, - int const scale) { - auto const scalePt = ox::Point{scale, scale}; - auto const width = cols * TileWidth; - auto const height = static_cast(inPixels.size()) / width; - auto const dstWidth = width * scale; - ox::Vector outPixels(static_cast((width * scale) * (height * scale))); - for (std::size_t dstIdx = 0; dstIdx < outPixels.size(); ++dstIdx) { - auto const dstPt = ox::Point{ - static_cast(dstIdx) % dstWidth, - static_cast(dstIdx) / dstWidth}; - auto const srcPt = dstPt / scalePt; - auto const srcIdx = ptToIdx(srcPt, cols); - outPixels[dstIdx] = inPixels[srcIdx]; - } - return outPixels; -} - -static ox::Error toPngFile( - ox::CStringViewCR path, - ox::Vector &&pixels, - Palette const &pal, - size_t const page, - unsigned const width, - unsigned const height) noexcept { - for (auto &c : pixels) { - c = color32(color(pal, page, c)) | static_cast(0XFF << 24); - } - constexpr auto fmt = LCT_RGBA; - return ox::Error(static_cast( - lodepng_encode_file( - path.c_str(), - reinterpret_cast(pixels.data()), - width, - height, - fmt, - 8))); -} - TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringParam path): Editor{sctx, std::move(path)}, m_sctx{sctx}, @@ -421,21 +370,7 @@ ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexc // subsheet to png auto const &s = m_model.activeSubSheet(); auto const &pal = m_model.pal(); - auto const width = s.columns * TileWidth; - auto const height = s.rows * TileHeight; - auto pixels = normalizePixelSizes(s.pixels); - pixels = normalizePixelArrangement(pixels, s.columns, scale); - auto const err = toPngFile( - path, - std::move(pixels), - pal, - m_model.palettePage(), - static_cast(width * scale), - static_cast(height * scale)); - if (err) { - oxErrorf("TileSheet export failed: {}", toStr(err)); - } - return err; + return gfx::exportSubsheetToPng(s, pal, m_model.palettePage(), path, scale); } void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept { diff --git a/src/nostalgia/modules/gfx/src/tilesheet.cpp b/src/nostalgia/modules/gfx/src/tilesheet.cpp index 3790919..d59fec0 100644 --- a/src/nostalgia/modules/gfx/src/tilesheet.cpp +++ b/src/nostalgia/modules/gfx/src/tilesheet.cpp @@ -211,6 +211,47 @@ ox::Result getSubSheetIdx(TileSheet const &ts, SubSheetI return out; } + +template +static ox::Result getSubSheet( + ox::SpanView const &idx, + std::size_t const idxIt, + SubSheet &pSubSheet) noexcept { + if (idxIt == idx.size()) { + return &pSubSheet; + } + auto const ¤tIdx = idx[idxIt]; + auto const next = ox::find_if( + pSubSheet.subsheets.begin(), + pSubSheet.subsheets.end(), + [¤tIdx](TileSheet::SubSheet const &ss) { + return ss.name == currentIdx; + }); + if (next == pSubSheet.subsheets.end()) { + return ox::Error{1, "SubSheet not found"}; + } + return getSubSheet(idx, idxIt + 1, *next); +} + +ox::Result getSubSheet( + ox::SpanView const &idx, + TileSheet const &ts) noexcept { + if (!idx.empty() && idx[0] == ts.subsheet.name) { + return getSubSheet(idx, 1, ts.subsheet); + } + return ox::Error{1, "SubSheet not found"}; +} + +ox::Result getSubSheet( + ox::SpanView const &idx, + TileSheet &ts) noexcept { + if (!idx.empty() && idx[0] == ts.subsheet.name) { + return getSubSheet(idx, 1, ts.subsheet); + } + return ox::Error{1, "SubSheet not found"}; +} + + #if defined(__GNUC__) && __GNUC__ >= 13 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdangling-reference" diff --git a/src/nostalgia/modules/sound/src/studio/studiomodule.cpp b/src/nostalgia/modules/sound/src/studio/studiomodule.cpp index 0806dfe..93eafd1 100644 --- a/src/nostalgia/modules/sound/src/studio/studiomodule.cpp +++ b/src/nostalgia/modules/sound/src/studio/studiomodule.cpp @@ -10,6 +10,10 @@ namespace nostalgia::sound { static struct: studio::Module { + ox::String id() const noexcept final { + return ox::String{"net.drinkingtea.nostalgia.sound"}; + } + ox::Vector editors(studio::Context&) const noexcept final { return { }; @@ -22,7 +26,7 @@ static struct: studio::Module { } const mod; -const studio::Module *studioModule() noexcept { +studio::Module const *studioModule() noexcept { return &mod; } diff --git a/src/olympic/studio/applib/include/studioapp/studioapp.hpp b/src/olympic/studio/applib/include/studioapp/studioapp.hpp index 8defebe..ec9ec5c 100644 --- a/src/olympic/studio/applib/include/studioapp/studioapp.hpp +++ b/src/olympic/studio/applib/include/studioapp/studioapp.hpp @@ -10,6 +10,9 @@ namespace studio { +[[nodiscard]] +ox::Vector const &modules() noexcept; + void registerModule(Module const*) noexcept; } diff --git a/src/olympic/studio/applib/src/app.cpp b/src/olympic/studio/applib/src/app.cpp index d301086..6729bc1 100644 --- a/src/olympic/studio/applib/src/app.cpp +++ b/src/olympic/studio/applib/src/app.cpp @@ -2,15 +2,18 @@ * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. */ +#include #include #include #include #include -#include +#include #include #include +#include + #include "studioui.hpp" namespace studio { @@ -40,7 +43,7 @@ static void mouseButtonEventHandler(turbine::Context &ctx, int const btn, bool c [[nodiscard]] ox::Vector> WindowIcons() noexcept; -static ox::Error runApp( +static ox::Error runStudio( ox::StringViewCR appName, ox::StringViewCR projectDataDir, ox::UPtr &&fs) noexcept { @@ -61,17 +64,46 @@ static ox::Error runApp( static ox::Error run( ox::StringViewCR appName, ox::StringViewCR projectDataDir, - ox::SpanView) { + ox::SpanView const &args) { // seed UUID generator auto const time = std::time(nullptr); ox::UUID::seedGenerator({ static_cast(time), static_cast(time << 1) }); - // run app - auto const err = runApp(appName, projectDataDir, ox::UPtr{}); - oxAssert(err, "Something went wrong..."); - return err; + if (args.size() > 1 && ox::StringView{args[1]} == "cmd") { + if (args.size() < 5) { + return ox::Error{1, "insufficient arguments for sub-command"}; + } + auto constexpr numCmdArgs = 5; + ox::StringView const projectDir = args[2]; + ox::StringView const moduleId = args[3]; + ox::StringView const subCmd = args[4]; + for (auto const m : modules()) { + if (m->id() == moduleId) { + for (auto const &c : m->commands()) { + if (c.name == subCmd) { + auto kctx = keel::init( + ox::make_unique(projectDir), + c.name); + if (kctx.error) { + return ox::Error{2, "failed to load project directory"}; + } + Project project{*kctx.value, projectDir, projectDataDir}; + return c.func(project, args + numCmdArgs); + } + } + return ox::Error{1, "command not found"}; + } + } + return ox::Error{1, "module not found"}; + } else { + // run app + auto const err = runStudio(appName, projectDataDir, ox::UPtr{}); + oxAssert(err, "Something went wrong..."); + return err; + } + return {}; } } diff --git a/src/olympic/studio/applib/src/studioui.cpp b/src/olympic/studio/applib/src/studioui.cpp index 441c0f4..16ecb96 100644 --- a/src/olympic/studio/applib/src/studioui.cpp +++ b/src/olympic/studio/applib/src/studioui.cpp @@ -37,11 +37,15 @@ static bool shutdownHandler(turbine::Context &ctx) { namespace ig { extern bool s_mainWinHasFocus; } -static ox::Vector modules; +static ox::Vector g_modules; + +ox::Vector const &modules() noexcept { + return g_modules; +} void registerModule(Module const*mod) noexcept { if (mod) { - modules.emplace_back(mod); + g_modules.emplace_back(mod); } } @@ -443,7 +447,7 @@ void StudioUI::loadModule(Module const &mod) noexcept { } void StudioUI::loadModules() noexcept { - for (auto const mod : modules) { + for (auto const mod : g_modules) { loadModule(*mod); } } @@ -673,14 +677,18 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept { auto p = std::move(*pcIt); std::ignore = config.projects.erase(pcIt); auto &pc = *config.projects.emplace(0, std::move(p)); - for (auto const &f: pc.openFiles) { - auto const openFileErr = openFileActiveTab(f, pc.activeTabItemName == f); - if (openFileErr) { - oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr)); - continue; - } - m_activeEditor = m_editors.back().value->get(); - } + std::ignore = pc.openFiles.erase( + std::remove_if( + pc.openFiles.begin(), pc.openFiles.end(), + [&](ox::String const &f) { + auto const openFileErr = openFileActiveTab(f, pc.activeTabItemName == f); + if (openFileErr) { + oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr)); + return true; + } + m_activeEditor = m_editors.back().value->get(); + return false; + })); } else { config.projects.emplace(0, StudioConfig::ProjectConfig{ .projectPath = ox::String{m_project->projectPath()}, diff --git a/src/olympic/studio/modlib/include/studio/module.hpp b/src/olympic/studio/modlib/include/studio/module.hpp index dbefd61..5434d10 100644 --- a/src/olympic/studio/modlib/include/studio/module.hpp +++ b/src/olympic/studio/modlib/include/studio/module.hpp @@ -23,15 +23,34 @@ struct EditorMaker { Func make; }; +struct Command { + ox::String name; + std::function)> func; + Command( + ox::StringParam name, + std::function)> func) noexcept: + name(std::move(name)), + func(std::move(func)) {} +}; + class Module { public: virtual ~Module() noexcept = default; - virtual ox::Vector editors(studio::Context &ctx) const; + [[nodiscard]] + virtual ox::String id() const noexcept = 0; - virtual ox::Vector> itemMakers(studio::Context&) const; + [[nodiscard]] + virtual ox::Vector commands() const; - virtual ox::Vector> itemTemplates(studio::Context&) const; + [[nodiscard]] + virtual ox::Vector editors(Context &ctx) const; + + [[nodiscard]] + virtual ox::Vector> itemMakers(Context&) const; + + [[nodiscard]] + virtual ox::Vector> itemTemplates(Context&) const; }; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index dda1eab..f730e8b 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -59,7 +59,7 @@ class Project: public ox::SignalHandler { ox::HashMap> m_fileExtFileMap; public: - explicit Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir); + explicit Project(keel::Context &ctx, ox::StringParam path, ox::StringViewCR projectDataDir); ox::Error create() noexcept; @@ -101,6 +101,16 @@ class Project: public ox::SignalHandler { ox::Error deleteItem(ox::StringViewCR path) noexcept; + [[nodiscard]] + keel::Context &kctx() noexcept { + return m_kctx; + } + + [[nodiscard]] + keel::Context const &kctx() const noexcept { + return m_kctx; + } + [[nodiscard]] bool exists(ox::StringViewCR path) const noexcept; diff --git a/src/olympic/studio/modlib/src/module.cpp b/src/olympic/studio/modlib/src/module.cpp index 9f57342..2398d9a 100644 --- a/src/olympic/studio/modlib/src/module.cpp +++ b/src/olympic/studio/modlib/src/module.cpp @@ -6,6 +6,10 @@ namespace studio { +ox::Vector Module::commands() const { + return {}; +} + ox::Vector Module::editors(Context&) const { return {}; } diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 0ccb2f4..7312225 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -54,7 +54,7 @@ static constexpr bool isParentOf(ox::StringViewCR parent, ox::StringViewCR child return beginsWith(sfmt("{}/", child), parent); } -Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir): +Project::Project(keel::Context &ctx, ox::StringParam path, ox::StringViewCR projectDataDir): m_kctx(ctx), m_path(std::move(path)), m_projectDataDir(projectDataDir), diff --git a/src/olympic/turbine/src/gba/turbine.cpp b/src/olympic/turbine/src/gba/turbine.cpp index df24c0e..e8706d0 100644 --- a/src/olympic/turbine/src/gba/turbine.cpp +++ b/src/olympic/turbine/src/gba/turbine.cpp @@ -50,7 +50,7 @@ OX_ALLOW_UNSAFE_BUFFERS_BEGIN constexpr auto headerP1Len = ox::strlen(headerP2); constexpr auto headerP2Len = ox::strlen(headerP1); constexpr auto headerLen = headerP1Len + headerP2Len; - for (auto current = MEM_ROM; current < reinterpret_cast(0x0a000000); current += headerLen) { + for (auto current = MEM_ROM.data(); current < reinterpret_cast(0x0a000000); current += headerLen) { if (memcmp(current, headerP1, headerP1Len) == 0 && memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) { return reinterpret_cast(current + headerLen);