feat(debug): theme x tab screenshot sweep + Debug options dialog
Add a debug tool that cycles every skin across every build-enabled tab and saves a PNG of each to a timestamped folder under the config dir (screenshots/sweep_<ts>/) — for gathering visual context on theme-specific UI work. - App state machine (app_network.cpp): startScreenshotSweep() builds the skin+enabled-page lists, saves the current skin/page, and steps through them; updateScreenshotSweep() (top of App::render) pins the page + settles ~4 frames after each skin/page change (skin switches reload TOML + reset the acrylic capture, so they need to settle); wantsScreenshotThisFrame()/screenshotSweepPath() /onScreenshotCaptured() coordinate with main.cpp. Restores the original skin/page when done; nothing persisted. - Framebuffer capture (main.cpp): read the finished frame and encode PNG via the bundled miniz (tdefl_write_image_to_png_file_in_memory_ex). GL reads FBO 0 (RGBA, bottom-up -> flip); DX11 copies the backbuffer to a staging texture + maps it (BGRA, top-down -> channel-swap). Alpha forced opaque. - Settings UI: move the Verbose logging checkbox off the wallet row into a new "Debug options" button that opens a dialog housing the verbose toggle + a "Run screenshot sweep" button. New i18n keys. Both platforms build; no-crash smoke verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
src/main.cpp
71
src/main.cpp
@@ -71,11 +71,70 @@ extern "C" HRESULT __stdcall SHGetPropertyStoreForWindow(HWND hwnd, REFIID riid,
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <miniz.h>
|
||||
#include "util/logger.h"
|
||||
|
||||
// Read the just-rendered framebuffer and write it to a PNG (used by the debug screenshot sweep).
|
||||
// GL: read FBO 0 (RGBA, bottom-up -> miniz flip). DX11: copy the backbuffer to a staging texture,
|
||||
// map it (BGRA, top-down -> channel-swap, no flip). Alpha forced opaque (the window is transparent).
|
||||
namespace {
|
||||
inline void writePng(const std::string& path, const std::vector<uint8_t>& rgba, int w, int h, bool flip) {
|
||||
size_t len = 0;
|
||||
void* png = tdefl_write_image_to_png_file_in_memory_ex(rgba.data(), w, h, 4, &len, 6, flip ? MZ_TRUE : MZ_FALSE);
|
||||
if (!png) return;
|
||||
std::error_code ec; std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
if (out) out.write(reinterpret_cast<const char*>(png), static_cast<std::streamsize>(len));
|
||||
mz_free(png);
|
||||
}
|
||||
#ifdef DRAGONX_USE_DX11
|
||||
inline void captureFrameToPng(dragonx::platform::DX11Context& dx, const std::string& path) {
|
||||
IDXGISwapChain1* sc = dx.swapChain(); ID3D11Device* dev = dx.device(); ID3D11DeviceContext* ctx = dx.deviceContext();
|
||||
if (!sc || !dev || !ctx) return;
|
||||
ID3D11Texture2D* back = nullptr;
|
||||
if (FAILED(sc->GetBuffer(0, IID_PPV_ARGS(&back))) || !back) return;
|
||||
D3D11_TEXTURE2D_DESC bd; back->GetDesc(&bd);
|
||||
D3D11_TEXTURE2D_DESC sd = bd; sd.Usage = D3D11_USAGE_STAGING; sd.BindFlags = 0;
|
||||
sd.CPUAccessFlags = D3D11_CPU_ACCESS_READ; sd.MiscFlags = 0;
|
||||
ID3D11Texture2D* staging = nullptr;
|
||||
if (SUCCEEDED(dev->CreateTexture2D(&sd, nullptr, &staging)) && staging) {
|
||||
ctx->CopyResource(staging, back);
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
if (SUCCEEDED(ctx->Map(staging, 0, D3D11_MAP_READ, 0, &m))) {
|
||||
int w = (int)bd.Width, h = (int)bd.Height;
|
||||
std::vector<uint8_t> px((size_t)w * h * 4);
|
||||
for (int y = 0; y < h; ++y) {
|
||||
const uint8_t* s = (const uint8_t*)m.pData + (size_t)y * m.RowPitch;
|
||||
uint8_t* d = px.data() + (size_t)y * w * 4;
|
||||
for (int x = 0; x < w; ++x) { d[x*4+0]=s[x*4+2]; d[x*4+1]=s[x*4+1]; d[x*4+2]=s[x*4+0]; d[x*4+3]=255; }
|
||||
}
|
||||
ctx->Unmap(staging, 0);
|
||||
writePng(path, px, w, h, /*flip*/false); // DX11 textures are top-down
|
||||
}
|
||||
staging->Release();
|
||||
}
|
||||
back->Release();
|
||||
}
|
||||
#else
|
||||
inline void captureFrameToPng(const std::string& path, int w, int h) {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
std::vector<uint8_t> px((size_t)w * h * 4);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glReadBuffer(GL_BACK);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, px.data());
|
||||
for (size_t i = 3; i < px.size(); i += 4) px[i] = 255; // opaque
|
||||
writePng(path, px, w, h, /*flip*/true); // GL default framebuffer is bottom-up
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows crash handler — writes a minidump and log entry before exit
|
||||
static LONG WINAPI CrashHandler(EXCEPTION_POINTERS* ep)
|
||||
@@ -1632,6 +1691,12 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
// Debug screenshot sweep — grab the finished frame before present.
|
||||
if (app.wantsScreenshotThisFrame()) {
|
||||
captureFrameToPng(dx, app.screenshotSweepPath());
|
||||
app.onScreenshotCaptured();
|
||||
}
|
||||
|
||||
// Update and Render additional Platform Windows (multi-viewport)
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
@@ -1655,6 +1720,12 @@ int main(int argc, char* argv[])
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
// Debug screenshot sweep — grab the finished frame before swap.
|
||||
if (app.wantsScreenshotThisFrame()) {
|
||||
captureFrameToPng(app.screenshotSweepPath(), (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
app.onScreenshotCaptured();
|
||||
}
|
||||
|
||||
// Update and Render additional Platform Windows (multi-viewport)
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user