GSDump: Add embedded screenshot

This commit is contained in:
Connor McLaughlin 2022-03-05 15:05:42 +10:00 committed by refractionpcsx2
parent 4ed748fa30
commit 89c79aa6c3
4 changed files with 154 additions and 17 deletions

View File

@ -33,7 +33,9 @@ GSDumpBase::~GSDumpBase()
fclose(m_gs);
}
void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
void GSDumpBase::AddHeader(const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs)
{
// New header: CRC of FFFFFFFF, secondary header, full header follows.
const u32 fake_crc = 0xFFFFFFFFu;
@ -41,7 +43,8 @@ void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData&
// Compute full header size (with serial).
// This acts as the state size for loading older dumps.
const u32 header_size = sizeof(GSDumpHeader) + static_cast<u32>(serial.size());
const u32 screenshot_size = screenshot_width * screenshot_height * sizeof(screenshot_pixels[0]);
const u32 header_size = sizeof(GSDumpHeader) + static_cast<u32>(serial.size()) + screenshot_size;
AppendRawData(&header_size, 4);
// Write hader.
@ -51,9 +54,15 @@ void GSDumpBase::AddHeader(const std::string& serial, u32 crc, const freezeData&
header.crc = crc;
header.serial_offset = sizeof(header);
header.serial_size = static_cast<u32>(serial.size());
header.screenshot_width = screenshot_width;
header.screenshot_height = screenshot_height;
header.screenshot_offset = header.serial_offset + header.serial_size;
header.screenshot_size = screenshot_size;
AppendRawData(&header, sizeof(header));
if (!serial.empty())
AppendRawData(serial.data(), serial.size());
if (screenshot_pixels)
AppendRawData(screenshot_pixels, screenshot_size);
// Then the real state data.
AppendRawData(fd.data, fd.size);
@ -112,10 +121,12 @@ void GSDumpBase::Write(const void* data, size_t size)
// GSDump implementation
//////////////////////////////////////////////////////////////////////
GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs)
: GSDumpBase(fn + ".gs")
{
AddHeader(serial, crc, fd, regs);
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
}
void GSDumpUncompressed::AppendRawData(const void* data, size_t size)
@ -132,7 +143,9 @@ void GSDumpUncompressed::AppendRawData(u8 c)
// GSDumpXz implementation
//////////////////////////////////////////////////////////////////////
GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs)
: GSDumpBase(fn + ".gs.xz")
{
m_strm = LZMA_STREAM_INIT;
@ -143,7 +156,7 @@ GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, co
return;
}
AddHeader(serial, crc, fd, regs);
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
}
GSDumpXz::~GSDumpXz()

View File

@ -47,6 +47,10 @@ struct GSDumpHeader
u32 serial_offset;
u32 serial_size;
u32 crc;
u32 screenshot_width;
u32 screenshot_height;
u32 screenshot_offset;
u32 screenshot_size;
};
#pragma pack(pop)
@ -57,7 +61,9 @@ class GSDumpBase
FILE* m_gs;
protected:
void AddHeader(const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
void AddHeader(const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs);
void Write(const void* data, size_t size);
virtual void AppendRawData(const void* data, size_t size) = 0;
@ -78,7 +84,9 @@ class GSDumpUncompressed final : public GSDumpBase
void AppendRawData(u8 c) final;
public:
GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs);
virtual ~GSDumpUncompressed() = default;
};
@ -94,6 +102,8 @@ class GSDumpXz final : public GSDumpBase
void AppendRawData(u8 c);
public:
GSDumpXz(const std::string& fn, const std::string& serial, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
GSDumpXz(const std::string& fn, const std::string& serial, u32 crc,
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
const freezeData& fd, const GSPrivRegSet* regs);
virtual ~GSDumpXz();
};

View File

@ -466,10 +466,26 @@ void GSRenderer::VSync(u32 field, bool registers_written)
const std::string serial(VMManager::GetGameSerial());
#endif
// keep the screenshot relatively small so we don't bloat the dump
static constexpr u32 DUMP_SCREENSHOT_WIDTH = 640;
static constexpr u32 DUMP_SCREENSHOT_HEIGHT = 480;
std::vector<u32> screenshot_pixels;
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, &screenshot_pixels);
if (m_control_key)
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, serial, m_crc, fd, m_regs));
{
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, serial, m_crc,
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
fd, m_regs));
}
else
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, serial, m_crc, fd, m_regs));
{
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, serial, m_crc,
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
fd, m_regs));
}
delete[] fd.data;
}

View File

@ -22,6 +22,8 @@
#include "common/EmbeddedImage.h"
#include "common/FileSystem.h"
#include "common/StringUtil.h"
#include "gui/Resources/NoIcon.h"
#include "GS.h"
#include "GS/GSDump.h"
@ -47,6 +49,7 @@
#include <wx/wfstream.h>
#include <array>
#include <functional>
#include <optional>
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
static constexpr Output BitCast(Input input)
@ -168,6 +171,94 @@ static constexpr const char* GetNameTEXCPSM(u8 psm)
}
}
static std::unique_ptr<GSDumpFile> GetDumpFile(const std::string& filename)
{
std::FILE* fp = FileSystem::OpenCFile(filename.c_str(), "rb");
if (!fp)
return nullptr;
if (StringUtil::EndsWith(filename, ".xz"))
return std::make_unique<GSDumpLzma>(fp, nullptr);
else
return std::make_unique<GSDumpRaw>(fp, nullptr);
}
static bool GetPreviewImageFromDump(const std::string& filename, u32* width, u32* height, std::vector<u32>* pixels)
{
try
{
std::unique_ptr<GSDumpFile> dump = GetDumpFile(filename);
if (!dump)
return false;
u32 crc;
dump->Read(&crc, sizeof(crc));
if (crc != 0xFFFFFFFFu)
{
// not new header dump, so no preview
return false;
}
u32 header_size;
dump->Read(&header_size, sizeof(header_size));
if (header_size < sizeof(GSDumpHeader))
{
// doesn't have the screenshot fields
return false;
}
std::unique_ptr<u8[]> header_bits = std::make_unique<u8[]>(header_size);
dump->Read(header_bits.get(), header_size);
GSDumpHeader header;
std::memcpy(&header, header_bits.get(), sizeof(header));
if (header.screenshot_size == 0 ||
header.screenshot_size < (header.screenshot_width * header.screenshot_height * sizeof(u32)) ||
(static_cast<u64>(header.screenshot_offset) + header.screenshot_size) > header_size)
{
// doesn't have a screenshot
return false;
}
*width = header.screenshot_width;
*height = header.screenshot_height;
pixels->resize(header.screenshot_width * header.screenshot_height);
std::memcpy(pixels->data(), header_bits.get() + header.screenshot_offset, header.screenshot_size);
return true;
}
catch (...)
{
return false;
}
}
static std::optional<wxImage> GetPreviewImageFromDump(const std::string& filename)
{
std::vector<u32> pixels;
u32 width, height;
if (!GetPreviewImageFromDump(filename, &width, &height, &pixels))
return std::nullopt;
// strip alpha bytes because wx is dumb and stores on a separate plane
// apparently this isn't aligned? stupidity...
const u32 pitch = width * 3;
u8* wxpixels = static_cast<u8*>(std::malloc(pitch * height));
for (u32 y = 0; y < height; y++)
{
const u8* in = reinterpret_cast<const u8*>(pixels.data() + y * width);
u8* out = wxpixels + y * pitch;
for (u32 x = 0; x < width; x++)
{
*(out++) = in[0];
*(out++) = in[1];
*(out++) = in[2];
in += sizeof(u32);
}
}
return wxImage(wxSize(width, height), wxpixels);
}
namespace GSDump
{
bool isRunning = false;
@ -335,8 +426,17 @@ void Dialogs::GSDumpDialog::SelectedDump(wxListEvent& evt)
img.Rescale(400,250, wxIMAGE_QUALITY_HIGH);
m_preview_image->SetBitmap(wxBitmap(img));
}
else if (std::optional<wxImage> img = GetPreviewImageFromDump(StringUtil::wxStringToUTF8String(filename)); img.has_value())
{
// try embedded dump
img->Rescale(400, 250, wxIMAGE_QUALITY_HIGH);
m_preview_image->SetBitmap(img.value());
}
else
{
m_preview_image->SetBitmap(EmbeddedImage<res_NoIcon>().Get());
}
m_selected_dump = wxString(filename);
}
@ -370,18 +470,16 @@ void Dialogs::GSDumpDialog::RunDump(wxCommandEvent& event)
{
if (!m_run->IsEnabled())
return;
FILE* dumpfile = wxFopen(m_selected_dump, L"rb");
if (!dumpfile)
m_thread->m_dump_file = GetDumpFile(StringUtil::wxStringToUTF8String(m_selected_dump));
if (!m_thread->m_dump_file)
{
wxString s;
s.Printf(_("Failed to load the dump %s !"), m_selected_dump);
wxMessageBox(s, _("GS Debugger"), wxICON_ERROR);
return;
}
if (m_selected_dump.EndsWith(".xz"))
m_thread->m_dump_file = std::make_unique<GSDumpLzma>(dumpfile, nullptr);
else
m_thread->m_dump_file = std::make_unique<GSDumpRaw >(dumpfile, nullptr);
m_run->Disable();
m_settings->Disable();
m_debug_mode->Enable();