diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index bc2fc30bc..b6b8d9b40 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -100,7 +100,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_link_libraries(core PUBLIC Threads::Threads common zlib vulkan-loader)
+target_link_libraries(core PUBLIC Threads::Threads common zlib vulkan-loader xxhash)
target_link_libraries(core PRIVATE glad stb)
if(WIN32)
diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index e0121765a..c76a5106c 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -158,6 +158,9 @@
{7ff9fdb9-d504-47db-a16a-b08071999620}
+
+ {09553c96-9f39-49bf-8ae6-7acbd07c410c}
+
{ee054e08-3799-4a59-a422-18259c105ffd}
@@ -306,10 +309,10 @@
Level4
Disabled
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -332,10 +335,10 @@
Level4
Disabled
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -358,10 +361,10 @@
Level4
Disabled
- WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -387,10 +390,10 @@
Level4
Disabled
- WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -417,8 +420,8 @@
MaxSpeed
true
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -444,8 +447,8 @@
MaxSpeed
true
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -472,8 +475,8 @@
MaxSpeed
true
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -499,8 +502,8 @@
MaxSpeed
true
- WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -583,4 +586,4 @@
true
core
-
\ No newline at end of file
+
diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index 086865c83..cb8210305 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -1,8 +1,10 @@
#include "gpu.h"
#include "common/file_system.h"
+#include "common/hash_combine.h"
#include "common/heap_array.h"
#include "common/log.h"
#include "common/state_wrapper.h"
+#include "common/string.h"
#include "dma.h"
#include "host_display.h"
#include "host_interface.h"
@@ -10,10 +12,12 @@
#include "stb_image_write.h"
#include "system.h"
#include "timers.h"
+#include
#include
#ifdef WITH_IMGUI
#include "imgui.h"
#endif
+#include
Log_SetChannel(GPU);
std::unique_ptr g_gpu;
@@ -1184,6 +1188,8 @@ void GPU::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data)
}
}
}
+
+ DumpDirectVRAMWrite(data, x, y, width, height);
}
void GPU::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height)
@@ -1319,6 +1325,212 @@ void GPU::SetTextureWindow(u32 value)
m_draw_mode.texture_window_changed = true;
}
+GPU::VRAMHashType GPU::GetVRAMHash(u32 x, u32 y, u32 width, u32 height) const
+{
+ static_assert(sizeof(VRAMHashType) == sizeof(XXH64_hash_t));
+
+ const u32 max_y = y + height;
+ DebugAssert(max_y <= VRAM_HEIGHT);
+
+ XXH64_state_t hash_state;
+ XXH64_reset(&hash_state, UINT64_C(0x4AF073341EF24663));
+
+ for (u32 current_y = y; current_y < max_y; current_y++)
+ XXH64_update(&hash_state, &m_vram_ptr[current_y * VRAM_WIDTH + x], sizeof(u16) * width);
+
+ return XXH64_digest(&hash_state);
+}
+
+std::size_t GPU::TextureHashHasher::operator()(const TextureHash& th) const
+{
+ std::size_t h = 0;
+ hash_combine(h, th.texture_hash, th.palette_hash);
+ return h;
+}
+
+GPU::TextureHash GPU::GetCurrentTextureHash() const
+{
+ switch (m_draw_mode.GetTextureMode())
+ {
+ case TextureMode::Palette4Bit:
+ {
+ return TextureHash{TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT,
+ GetVRAMHash(m_draw_mode.texture_page_x, m_draw_mode.texture_page_y, TEXTURE_PAGE_WIDTH / 4,
+ TEXTURE_PAGE_HEIGHT),
+ GetVRAMHash(m_draw_mode.texture_palette_x, m_draw_mode.texture_palette_y, 16, 1)};
+ }
+
+ case TextureMode::Palette8Bit:
+ {
+ return TextureHash{TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT,
+ GetVRAMHash(m_draw_mode.texture_page_x, m_draw_mode.texture_page_y, TEXTURE_PAGE_WIDTH / 2,
+ TEXTURE_PAGE_HEIGHT),
+ GetVRAMHash(m_draw_mode.texture_palette_x, m_draw_mode.texture_palette_y, 256, 1)};
+ }
+
+ case TextureMode::Direct16Bit:
+ case TextureMode::Reserved_Direct16Bit:
+ {
+ return TextureHash{
+ TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT,
+ GetVRAMHash(m_draw_mode.texture_page_x, m_draw_mode.texture_page_y, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT),
+ static_cast(0)};
+ }
+
+ default:
+ {
+ return TextureHash{};
+ }
+ }
+}
+
+std::unordered_set GPU::dumped_textures;
+
+void GPU::DumpCurrentTexture() const
+{
+ const TextureHash hash = GetCurrentTextureHash();
+ if (dumped_textures.find(hash) != dumped_textures.end())
+ return;
+
+ dumped_textures.insert(hash);
+
+ const TextureMode mode = m_draw_mode.GetTextureMode();
+ const u32 clut_size = (mode == TextureMode::Palette4Bit ? 16 : (mode == TextureMode::Palette8Bit ? 256 : 0));
+ std::vector clut;
+ clut.reserve(clut_size);
+ const u16* clut_ptr = &m_vram_ptr[m_draw_mode.texture_palette_y * VRAM_WIDTH + m_draw_mode.texture_palette_x];
+ for (u32 i = 0; i < clut_size; i++)
+ clut.push_back(RGBA5551ToRGBA8888ForExport(*clut_ptr++));
+
+ std::vector texture_data;
+ texture_data.reserve(TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT);
+ switch (mode)
+ {
+ case TextureMode::Palette4Bit:
+ {
+ for (u32 row = 0; row < TEXTURE_PAGE_HEIGHT; row++)
+ {
+ const u16* row_ptr = &m_vram_ptr[(m_draw_mode.texture_page_y + row) * VRAM_WIDTH + m_draw_mode.texture_page_x];
+ for (u32 col = 0; col < TEXTURE_PAGE_WIDTH / 4; col++)
+ {
+ u16 packed_pixels = *(row_ptr++);
+ texture_data.push_back(clut[packed_pixels & 0x0F]);
+ packed_pixels >>= 4;
+ texture_data.push_back(clut[packed_pixels & 0x0F]);
+ packed_pixels >>= 4;
+ texture_data.push_back(clut[packed_pixels & 0x0F]);
+ packed_pixels >>= 4;
+ texture_data.push_back(clut[packed_pixels & 0x0F]);
+ }
+ }
+ }
+ break;
+
+ case TextureMode::Palette8Bit:
+ {
+ for (u32 row = 0; row < TEXTURE_PAGE_HEIGHT; row++)
+ {
+ const u16* row_ptr = &m_vram_ptr[(m_draw_mode.texture_page_y + row) * VRAM_WIDTH + m_draw_mode.texture_page_x];
+ for (u32 col = 0; col < TEXTURE_PAGE_WIDTH / 2; col++)
+ {
+ u16 packed_pixels = *(row_ptr++);
+ texture_data.push_back(clut[packed_pixels & 0xFF]);
+ packed_pixels >>= 8;
+ texture_data.push_back(clut[packed_pixels & 0xFF]);
+ }
+ }
+ }
+ break;
+
+ case TextureMode::Direct16Bit:
+ case TextureMode::Reserved_Direct16Bit:
+ {
+ for (u32 row = 0; row < TEXTURE_PAGE_HEIGHT; row++)
+ {
+ const u16* row_ptr = &m_vram_ptr[(m_draw_mode.texture_page_y + row) * VRAM_WIDTH + m_draw_mode.texture_page_x];
+ for (u32 col = 0; col < TEXTURE_PAGE_WIDTH; col++)
+ texture_data.push_back(RGBA5551ToRGBA8888ForExport(*(row_ptr++)));
+ }
+ }
+ break;
+
+ default:
+ texture_data.resize(TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT);
+ break;
+ }
+
+ std::string path;
+ if (!System::GetRunningCode().empty())
+ path = g_host_interface->GetUserDirectoryRelativePath("dump/textures/%s", System::GetRunningCode().c_str());
+ else
+ path = g_host_interface->GetUserDirectoryRelativePath("dump/textures");
+
+ if (!FileSystem::DirectoryExists(path.c_str()) && !FileSystem::CreateDirectory(path.c_str(), true))
+ {
+ Log_WarningPrintf("Failed to create texture dump directory '%s'", path.c_str());
+ return;
+ }
+
+ if (hash.palette_hash != 0)
+ path += TinyString::FromFormat("/%" PRIx64 "_%" PRIx64 ".png", hash.texture_hash, hash.palette_hash);
+ else
+ path += TinyString::FromFormat("/%" PRIx64 ".png", hash.texture_hash);
+
+ if (!stbi_write_png(path.c_str(), TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, 4, texture_data.data(),
+ TEXTURE_PAGE_WIDTH * sizeof(u32)))
+ {
+ Log_WarningPrintf("Failed to dump texture to '%s'", path.c_str());
+ return;
+ }
+}
+
+void GPU::DumpDirectVRAMWrite(const void* pixels, u32 x, u32 y, u32 width, u32 height)
+{
+ const u32 WIDTH_THRESHOLD = 128;
+ const u32 HEIGHT_THRESHOLD = 128;
+ if (width < WIDTH_THRESHOLD || height < HEIGHT_THRESHOLD)
+ return;
+
+ TextureHash hash{width, height, GetVRAMHash(x, y, width, height), 0};
+ if (dumped_textures.find(hash) != dumped_textures.end())
+ return;
+
+ dumped_textures.insert(hash);
+
+ std::vector texture_data;
+ texture_data.reserve(width * height);
+
+ for (u32 row = 0; row < height; row++)
+ {
+ const u16* row_ptr = &m_vram_ptr[(y + row) * VRAM_WIDTH + x];
+ for (u32 col = 0; col < width; col++)
+ texture_data.push_back(RGBA5551ToRGBA8888ForExport(*(row_ptr++)));
+ }
+
+ std::string path;
+ if (!System::GetRunningCode().empty())
+ path = g_host_interface->GetUserDirectoryRelativePath("dump/textures/vram_%s", System::GetRunningCode().c_str());
+ else
+ path = g_host_interface->GetUserDirectoryRelativePath("dump/textures");
+
+ if (!FileSystem::DirectoryExists(path.c_str()) && !FileSystem::CreateDirectory(path.c_str(), true))
+ {
+ Log_WarningPrintf("Failed to create texture dump directory '%s'", path.c_str());
+ return;
+ }
+
+ if (hash.palette_hash != 0)
+ path += TinyString::FromFormat("/%" PRIx64 "_%" PRIx64 ".png", hash.texture_hash, hash.palette_hash);
+ else
+ path += TinyString::FromFormat("/%" PRIx64 ".png", hash.texture_hash);
+
+ if (!stbi_write_png(path.c_str(), width, height, 4, texture_data.data(), width * sizeof(u32)))
+ {
+ Log_WarningPrintf("Failed to dump texture to '%s'", path.c_str());
+ return;
+ }
+}
+
bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha)
{
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
diff --git a/src/core/gpu.h b/src/core/gpu.h
index 54078114b..5c61b6bea 100644
--- a/src/core/gpu.h
+++ b/src/core/gpu.h
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
class StateWrapper;
@@ -109,6 +110,31 @@ public:
PAL_TOTAL_LINES = 314,
};
+ using VRAMHashType = u64;
+
+ struct TextureHash
+ {
+ u32 width, height;
+ VRAMHashType texture_hash;
+ VRAMHashType palette_hash;
+
+ ALWAYS_INLINE bool operator==(const TextureHash& h) const
+ {
+ return (texture_hash == h.texture_hash && palette_hash == h.palette_hash);
+ }
+ ALWAYS_INLINE bool operator!=(const TextureHash& h) const
+ {
+ return (texture_hash != h.texture_hash || palette_hash != h.palette_hash);
+ }
+ };
+
+ struct TextureHashHasher
+ {
+ std::size_t operator()(const TextureHash& th) const;
+ };
+
+ static std::unordered_set dumped_textures;
+
// 4x4 dither matrix.
static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-4, +0, -3, +1}, // row 0
{+2, -2, +3, -1}, // row 1
@@ -235,6 +261,22 @@ protected:
return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24);
}
+ static constexpr u32 RGBA5551ToRGBA8888ForExport(u16 color)
+ {
+ u8 r = Truncate8(color & 31);
+ u8 g = Truncate8((color >> 5) & 31);
+ u8 b = Truncate8((color >> 10) & 31);
+ u8 a = Truncate8((color >> 15) & 1);
+
+ // 00012345 -> 1234545
+ b = (b << 3) | (b & 0b111);
+ g = (g << 3) | (g & 0b111);
+ r = (r << 3) | (r & 0b111);
+ a = a ? 127 : 255;
+
+ return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24);
+ }
+
static constexpr u16 RGBA8888ToRGBA5551(u32 color)
{
const u16 r = Truncate16((color >> 3) & 0x1Fu);
@@ -405,6 +447,17 @@ protected:
/// Sets/decodes texture window bits.
void SetTextureWindow(u32 value);
+ /// Returns the hash for a VRAM region.
+ VRAMHashType GetVRAMHash(u32 x, u32 y, u32 width, u32 height) const;
+
+ /// Returns the hash for the currently-bound texture.
+ TextureHash GetCurrentTextureHash() const;
+
+ /// Dumps any currently-bound texture if the files do not exist.
+ void DumpCurrentTexture() const;
+
+ void DumpDirectVRAMWrite(const void* pixels, u32 x, u32 y, u32 width, u32 height);
+
u32 ReadGPUREAD();
void FinishVRAMWrite();
diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp
index 1f379af3f..6f161c2d3 100644
--- a/src/core/gpu_commands.cpp
+++ b/src/core/gpu_commands.cpp
@@ -348,6 +348,8 @@ bool GPU::HandleRenderPolygonCommand()
SetDrawMode((texpage_attribute & DrawMode::Reg::POLYGON_TEXPAGE_MASK) |
(m_draw_mode.mode_reg.bits & ~DrawMode::Reg::POLYGON_TEXPAGE_MASK));
SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
+ if (m_draw_mode.IsUsingPalette())
+ DumpCurrentTexture();
}
m_stats.num_vertices += num_vertices;
@@ -372,7 +374,11 @@ bool GPU::HandleRenderRectangleCommand()
SynchronizeCRTC();
if (rc.texture_enable)
+ {
SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
+ if (m_draw_mode.IsUsingPalette())
+ DumpCurrentTexture();
+ }
const TickCount setup_ticks = 16;
AddCommandTicks(setup_ticks);