diff --git a/include/dolphin/gx/GXAurora.h b/include/dolphin/gx/GXAurora.h index 6c44287..e3fb746 100644 --- a/include/dolphin/gx/GXAurora.h +++ b/include/dolphin/gx/GXAurora.h @@ -59,6 +59,8 @@ extern "C" { #define GX_LOAD_AURORA_DESTROY_TLUT 0x0033 +#define GX_LOAD_AURORA_DESTROY_COPY_TEX 0x0034 + /* * Debug marker stuff diff --git a/include/dolphin/gx/GXExtra.h b/include/dolphin/gx/GXExtra.h index 3dabff0..27713aa 100644 --- a/include/dolphin/gx/GXExtra.h +++ b/include/dolphin/gx/GXExtra.h @@ -18,6 +18,7 @@ typedef struct { void GXDestroyTexObj(GXTexObj* obj); void GXDestroyTlutObj(GXTlutObj* obj); +void GXDestroyCopyTex(void* dest); void GXColor4f32(float r, float g, float b, float a); diff --git a/lib/dolphin/gx/GXExtra.cpp b/lib/dolphin/gx/GXExtra.cpp index 0d555c7..8cb2b59 100644 --- a/lib/dolphin/gx/GXExtra.cpp +++ b/lib/dolphin/gx/GXExtra.cpp @@ -20,4 +20,11 @@ void GXDestroyTlutObj(GXTlutObj* obj_) { } obj->tlutObjId = 0; } + +void GXDestroyCopyTex(void* dest) { + if (dest != nullptr) { + GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_COPY_TEX); + GX_WRITE_U64(reinterpret_cast(dest)); + } +} } diff --git a/lib/gx/command_processor.cpp b/lib/gx/command_processor.cpp index 314e548..ed8541a 100644 --- a/lib/gx/command_processor.cpp +++ b/lib/gx/command_processor.cpp @@ -1776,6 +1776,10 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) { CHECK(pos + 4 <= size, "GX_LOAD_AURORA_DESTROY_TLUT read overrun"); evict_tlut_object(read_u32(data + pos, bigEndian)); pos += 4; + } else if (subCmd == GX_LOAD_AURORA_DESTROY_COPY_TEX) { + CHECK(pos + 8 <= size, "GX_LOAD_AURORA_DESTROY_COPY_TEX read overrun"); + evict_copy_texture(reinterpret_cast(read_u64(data + pos, bigEndian))); + pos += 8; } else if (subCmd == GX_LOAD_AURORA_DEBUG_GROUP_PUSH) { auto label = read_string(data, pos, size, bigEndian); gfx::push_debug_group(std::move(label)); diff --git a/lib/gx/gx.cpp b/lib/gx/gx.cpp index 7cfc257..ad6fd08 100644 --- a/lib/gx/gx.cpp +++ b/lib/gx/gx.cpp @@ -379,6 +379,17 @@ void clear_copy_texture_cache() noexcept { } } +void evict_copy_texture(const void* dest) noexcept { + g_gxState.copyTextures.erase(dest); + for (auto it = g_gxState.copyTextureCache.begin(); it != g_gxState.copyTextureCache.end();) { + if (it->first.dest == dest) { + g_gxState.copyTextureCache.erase(it++); + } else { + ++it; + } + } +} + void resolve_sampled_textures(const ShaderInfo& info) noexcept { ZoneScoped; diff --git a/lib/gx/gx.hpp b/lib/gx/gx.hpp index 795cfb8..3b27077 100644 --- a/lib/gx/gx.hpp +++ b/lib/gx/gx.hpp @@ -388,6 +388,7 @@ struct ShaderInfo; void initialize() noexcept; void shutdown() noexcept; void clear_copy_texture_cache() noexcept; +void evict_copy_texture(const void* dest) noexcept; void evict_texture_object(u32 texObjId) noexcept; void evict_tlut_object(u32 tlutObjId) noexcept; Vec2 logical_fb_size() noexcept; diff --git a/tests/gx_fifo_test.cpp b/tests/gx_fifo_test.cpp index 416eca4..d661649 100644 --- a/tests/gx_fifo_test.cpp +++ b/tests/gx_fifo_test.cpp @@ -1461,6 +1461,45 @@ TEST_F(GXFifoTest, DestroyTlutObj_MarksLoadedSlotNoCacheUntilReloaded) { EXPECT_FALSE(slot.no_cache()); } +TEST_F(GXFifoTest, DestroyCopyTex_EmitsAuroraDestroyCommand) { + alignas(32) u8 image[32]{}; + + GXDestroyCopyTex(image); + auto bytes = capture_fifo(); + + EXPECT_TRUE(has_aurora_cmd(bytes, GX_LOAD_AURORA_DESTROY_COPY_TEX)); + + reset_gx_state(); + decode_fifo(bytes); +} + +TEST_F(GXFifoTest, DestroyCopyTex_RemovesActiveCopyTextureAndCacheEntriesForPointer) { + alignas(32) u8 imageA[32]{}; + alignas(32) u8 imageB[32]{}; + + const aurora::gx::GXState::CopyTextureRef ref{.revision = 1}; + gxState().copyTextures[imageA] = ref; + gxState().copyTextures[imageB] = ref; + gxState().copyTextureCache.emplace( + aurora::gx::GXState::CopyTextureKey{.dest = imageA, .width = 32, .height = 32, .format = GX_TF_I4}, ref); + gxState().copyTextureCache.emplace( + aurora::gx::GXState::CopyTextureKey{.dest = imageA, .width = 64, .height = 64, .format = GX_TF_I8}, ref); + gxState().copyTextureCache.emplace( + aurora::gx::GXState::CopyTextureKey{.dest = imageB, .width = 32, .height = 32, .format = GX_TF_I4}, ref); + + GXDestroyCopyTex(imageA); + auto bytes = capture_fifo(); + + decode_fifo(bytes); + + EXPECT_FALSE(gxState().copyTextures.contains(imageA)); + EXPECT_TRUE(gxState().copyTextures.contains(imageB)); + for (const auto& [key, _] : gxState().copyTextureCache) { + EXPECT_NE(key.dest, imageA); + } + EXPECT_EQ(gxState().copyTextureCache.size(), 1u); +} + // ============================================================================ // BP genMode (requires __GXSetDirtyState() flush) // ============================================================================ diff --git a/tests/gx_test_stubs.cpp b/tests/gx_test_stubs.cpp index 8240c12..118d294 100644 --- a/tests/gx_test_stubs.cpp +++ b/tests/gx_test_stubs.cpp @@ -80,6 +80,16 @@ void evict_tlut_object(u32 tlutObjId) noexcept { } } } +void evict_copy_texture(const void* dest) noexcept { + g_gxState.copyTextures.erase(dest); + for (auto it = g_gxState.copyTextureCache.begin(); it != g_gxState.copyTextureCache.end();) { + if (it->first.dest == dest) { + g_gxState.copyTextureCache.erase(it++); + } else { + ++it; + } + } +} void shutdown() noexcept {} Vec2 logical_fb_size() noexcept { return {640, 480}; } gfx::Viewport map_logical_viewport(const gfx::Viewport& logicalViewport) noexcept { return logicalViewport; }