GPU: Add texture hashing and dumping support

This commit is contained in:
Connor McLaughlin 2020-02-11 12:00:38 +09:00
parent cf2599b6c7
commit aeba30c24a
5 changed files with 292 additions and 18 deletions

View File

@ -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)

View File

@ -158,6 +158,9 @@
<ProjectReference Include="..\..\dep\zlib\zlib.vcxproj">
<Project>{7ff9fdb9-d504-47db-a16a-b08071999620}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\xxhash\xxhash.vcxproj">
<Project>{09553c96-9f39-49bf-8ae6-7acbd07c410c}</Project>
</ProjectReference>
<ProjectReference Include="..\common\common.vcxproj">
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
</ProjectReference>
@ -306,10 +309,10 @@
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -332,10 +335,10 @@
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -358,10 +361,10 @@
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -387,10 +390,10 @@
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -417,8 +420,8 @@
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -444,8 +447,8 @@
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -472,8 +475,8 @@
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -499,8 +502,8 @@
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;XXH_STATIC_LINKING_ONLY;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>

View File

@ -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 <cinttypes>
#include <cmath>
#ifdef WITH_IMGUI
#include "imgui.h"
#endif
#include <xxhash.h>
Log_SetChannel(GPU);
std::unique_ptr<GPU> 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<VRAMHashType>(0)};
}
default:
{
return TextureHash{};
}
}
}
std::unordered_set<GPU::TextureHash, GPU::TextureHashHasher> 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<u32> 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<u32> 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<u32> 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");

View File

@ -10,6 +10,7 @@
#include <memory>
#include <tuple>
#include <vector>
#include <unordered_set>
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<TextureHash, TextureHashHasher> 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();

View File

@ -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);