shading parity program: shader source mirror, A/B capture, offline ucode emulation
This commit is contained in:
parent
70812600f7
commit
2dedfe1d6f
7 changed files with 755 additions and 91 deletions
|
|
@ -699,6 +699,11 @@ void Skate3BaseApp::OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) {
|
|||
rex::ui::RegisterBind("bind_skate3_menu_alt", "F1", "Skate 3 settings alternate", [this] {
|
||||
ToggleSimpleSettings();
|
||||
});
|
||||
// Remembered handle: the F11 paired A/B parity capture (native + emulated
|
||||
// screenshots + gsnap, sequenced from the guest frame loop in
|
||||
// skate3_native_render.cpp) needs the window without an app pointer.
|
||||
skate3::screenshot::RememberWindow(window() ? window()->GetNativeWindowHandle()
|
||||
: nullptr);
|
||||
rex::ui::RegisterBind("bind_skate3_screenshot", "F6",
|
||||
"Save screenshot to screenshots/", [this] {
|
||||
skate3::screenshot::CaptureWindow(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "skate3_native_render.h"
|
||||
|
||||
#include "skate3_native_scene.h"
|
||||
#include "skate3_screenshot.h"
|
||||
|
||||
#include "generated/skate3_init.h"
|
||||
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
|
@ -316,6 +318,74 @@ void OnFrameEnd(uint8_t* base) {
|
|||
}
|
||||
f10_was_down = f10_down;
|
||||
}
|
||||
// F11: paired A/B parity capture; one keypress produces
|
||||
// shot_<ts>_native.png + shot_<ts>_emulated.png (same viewpoint, ~half a
|
||||
// second apart while the renderer toggles) + an immediate F10-style
|
||||
// capture taken back in native mode. Sequenced across guest frames so
|
||||
// each renderer has settled before its screenshot (the emulated pipeline
|
||||
// needs to recompose after suppression lifts).
|
||||
{
|
||||
enum class AbState { kIdle, kNativeSettle, kEmulatedSettle, kBackToNative };
|
||||
static AbState ab_state = AbState::kIdle;
|
||||
static uint64_t ab_resume_frame = 0;
|
||||
static char ab_tag[24] = {};
|
||||
static bool f11_was_down = false;
|
||||
const bool f11_down = (GetAsyncKeyState(VK_F11) & 0x8000) != 0;
|
||||
constexpr uint64_t kSettleFrames = 60;
|
||||
switch (ab_state) {
|
||||
case AbState::kIdle:
|
||||
if (f11_down && !f11_was_down && !g_collecting) {
|
||||
const std::time_t t = std::time(nullptr);
|
||||
std::tm tm{};
|
||||
localtime_s(&tm, &t);
|
||||
std::snprintf(ab_tag, sizeof(ab_tag), "%02d%02d%02d", tm.tm_hour, tm.tm_min,
|
||||
tm.tm_sec);
|
||||
// Ensure the sequence starts in NATIVE mode (the gsnap capture at
|
||||
// the end must record native-path scene data).
|
||||
if (!skate3::native_scene::Enabled()) {
|
||||
skate3::native_scene::ToggleSceneEnabled();
|
||||
}
|
||||
ab_state = AbState::kNativeSettle;
|
||||
ab_resume_frame = g_frame_index + kSettleFrames;
|
||||
REXLOG_INFO("native-render A/B capture: F11, tag {}", ab_tag);
|
||||
}
|
||||
break;
|
||||
case AbState::kNativeSettle:
|
||||
if (g_frame_index >= ab_resume_frame) {
|
||||
char tag[40];
|
||||
std::snprintf(tag, sizeof(tag), "%s_native", ab_tag);
|
||||
skate3::screenshot::CaptureWindow(skate3::screenshot::RememberedWindow(), tag);
|
||||
skate3::native_scene::ToggleSceneEnabled(); // -> emulated
|
||||
ab_state = AbState::kEmulatedSettle;
|
||||
ab_resume_frame = g_frame_index + kSettleFrames;
|
||||
}
|
||||
break;
|
||||
case AbState::kEmulatedSettle:
|
||||
if (g_frame_index >= ab_resume_frame) {
|
||||
char tag[40];
|
||||
std::snprintf(tag, sizeof(tag), "%s_emulated", ab_tag);
|
||||
skate3::screenshot::CaptureWindow(skate3::screenshot::RememberedWindow(), tag);
|
||||
skate3::native_scene::ToggleSceneEnabled(); // -> native
|
||||
ab_state = AbState::kBackToNative;
|
||||
ab_resume_frame = g_frame_index + kSettleFrames;
|
||||
}
|
||||
break;
|
||||
case AbState::kBackToNative:
|
||||
if (g_frame_index >= ab_resume_frame && !g_collecting) {
|
||||
g_collecting = true;
|
||||
g_immediate = true;
|
||||
g_collect_counter = 0;
|
||||
skate3::native_scene::StartRecording(1);
|
||||
REXLOG_INFO(
|
||||
"native-render A/B capture: screenshots tagged {} done, immediate "
|
||||
"capture armed",
|
||||
ab_tag);
|
||||
ab_state = AbState::kIdle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
f11_was_down = f11_down;
|
||||
}
|
||||
#endif
|
||||
if (!g_collecting && g_frame_index % 32 == 0) {
|
||||
const std::filesystem::path trigger = SnapshotDir() / "trigger";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,13 @@ struct DrawItem {
|
|||
uint32_t ib_count;
|
||||
uint32_t diffuse_tex; // guest renderengine::Texture address (0 = none)
|
||||
uint32_t lightmap_tex; // guest renderengine::Texture address (0 = none)
|
||||
// "macrooverlay" material channel (environment shaders): a large-scale
|
||||
// grime/crack overlay multiplied over the diffuse at uv *
|
||||
// macroOverlayUVScale with macroOverlayOpacity: the ground/wall
|
||||
// weathering that reads as "extra grit".
|
||||
uint32_t macro_tex; // guest renderengine::Texture address (0 = none)
|
||||
float macro_scale; // macroOverlayUVScale channel constant
|
||||
float macro_opacity; // macroOverlayOpacity channel constant
|
||||
uint16_t pos_offset;
|
||||
uint16_t uv_offset;
|
||||
uint16_t uv2_offset;
|
||||
|
|
@ -48,6 +55,28 @@ struct DrawItem {
|
|||
// by a per-character color staged in the PIXEL constant bank. Detected via
|
||||
// the AttribulatorMaterialName channel; tint captured with the palette.
|
||||
bool hair;
|
||||
// AttribulatorMaterialName starts "environment.decal": wall/ground
|
||||
// sections whose pixel shader composites a `decal` art texture (graffiti,
|
||||
// painted branding, grime) over the base diffuse INSIDE the shader,
|
||||
// lerp(base, decal.rgb, decal.a), and draws OPAQUE (disassembled from
|
||||
// decalenvironment_defaultPS; these meshes ARE the surface there, so
|
||||
// rendering them alpha-blended punches holes in walls). Without the
|
||||
// composite the paint is missing (the no-graffiti bug).
|
||||
bool decal;
|
||||
uint32_t decal_art; // `decal` channel texture (0 = none)
|
||||
// AttribulatorMaterialName starts "environment.transparent": mist/cloud
|
||||
// sheets, glass, fences, vines. Alpha-BLENDED in a sub-pass after all
|
||||
// opaque items (back-to-front, depth test on, z-write off); the opaque
|
||||
// pass's alpha-test turned the soft mist gradients into solid hard-edged
|
||||
// white cloud blobs.
|
||||
bool transparent;
|
||||
// character.cloth_ropa (Ropa cloth-simulated garments, e.g. player tees):
|
||||
// the VS variant branches on a flag row kept in front of the bone palette
|
||||
// - sim active means the dynamic VB already holds deformed root-local
|
||||
// positions and the draw is RIGID (one affine at c188/c191), not skinned.
|
||||
// Detected via the AttribulatorMaterialName channel; consumed by
|
||||
// CaptureSkinnedState.
|
||||
bool ropa;
|
||||
float tint[4]; // rgb + enable flag in w
|
||||
// The item's per-draw state (bone palette for skinned, world matrix for
|
||||
// rigid model-space props) was not available at capture time; deferred
|
||||
|
|
@ -77,6 +106,13 @@ struct FrameScene {
|
|||
uint64_t generation = 0;
|
||||
float view_proj[16] = {};
|
||||
float cam_pos[3] = {};
|
||||
// Global distance-fog parameter rows, captured once per frame from the
|
||||
// first main-pass draw's VS constant bank (main-pass layout: c4 = camera,
|
||||
// the validation key; c5 = fog ramp scale/bias/exponent, c6 = linear-space
|
||||
// fog color rgb + transmittance scale in w; identical across every
|
||||
// main-pass draw of a frame). Consumed by environment.transparent items.
|
||||
float fog_ramp[4] = {0.0f, 0.0f, 1.0f, 0.0f};
|
||||
float fog_color[4] = {};
|
||||
std::vector<DrawItem> items;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
|
@ -59,15 +60,21 @@ bool PngEncoderClsid(CLSID* clsid) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotPath() {
|
||||
std::filesystem::path ScreenshotPath(const char* tag) {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const std::time_t t = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm tm{};
|
||||
localtime_s(&tm, &t);
|
||||
char name[64];
|
||||
std::snprintf(name, sizeof(name), "shot_%04d%02d%02d_%02d%02d%02d.png",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
|
||||
tm.tm_sec);
|
||||
char name[96];
|
||||
if (tag != nullptr && tag[0] != '\0') {
|
||||
std::snprintf(name, sizeof(name), "shot_%04d%02d%02d_%02d%02d%02d_%s.png",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
|
||||
tm.tm_sec, tag);
|
||||
} else {
|
||||
std::snprintf(name, sizeof(name), "shot_%04d%02d%02d_%02d%02d%02d.png",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
|
||||
tm.tm_sec);
|
||||
}
|
||||
return std::filesystem::absolute(std::filesystem::path("screenshots") / name);
|
||||
}
|
||||
|
||||
|
|
@ -101,9 +108,17 @@ void EncodeAndSave(HBITMAP bitmap, int crop_x, int crop_y, int crop_w, int crop_
|
|||
REXLOG_INFO("screenshot: saved {} ({}x{})", path.string(), crop_w, crop_h);
|
||||
}
|
||||
|
||||
std::atomic<void*> g_remembered_window{nullptr};
|
||||
|
||||
} // namespace
|
||||
|
||||
void CaptureWindow(void* window_handle) {
|
||||
void RememberWindow(void* hwnd) {
|
||||
g_remembered_window.store(hwnd, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void* RememberedWindow() { return g_remembered_window.load(std::memory_order_relaxed); }
|
||||
|
||||
void CaptureWindow(void* window_handle, const char* tag) {
|
||||
HWND hwnd = static_cast<HWND>(window_handle);
|
||||
if (hwnd == nullptr) {
|
||||
REXLOG_WARN("screenshot: no window handle");
|
||||
|
|
@ -141,7 +156,7 @@ void CaptureWindow(void* window_handle) {
|
|||
}
|
||||
|
||||
std::thread(EncodeAndSave, bitmap, int(client_origin.x - wr.left),
|
||||
int(client_origin.y - wr.top), crop_w, crop_h, ScreenshotPath())
|
||||
int(client_origin.y - wr.top), crop_w, crop_h, ScreenshotPath(tag))
|
||||
.detach();
|
||||
}
|
||||
|
||||
|
|
@ -150,9 +165,11 @@ void CaptureWindow(void* window_handle) {
|
|||
#else // !_WIN32
|
||||
|
||||
namespace skate3::screenshot {
|
||||
void CaptureWindow(void* /*window_handle*/) {
|
||||
void CaptureWindow(void* /*window_handle*/, const char* /*tag*/) {
|
||||
REXLOG_WARN("screenshot: only implemented on Windows");
|
||||
}
|
||||
void RememberWindow(void* /*hwnd*/) {}
|
||||
void* RememberedWindow() { return nullptr; }
|
||||
} // namespace skate3::screenshot
|
||||
|
||||
#endif // _WIN32
|
||||
|
|
|
|||
|
|
@ -6,9 +6,17 @@ namespace skate3::screenshot {
|
|||
// screenshots/ under the working directory (the install dir), logging the
|
||||
// absolute path. Uses PrintWindow PW_RENDERFULLCONTENT, the flip-model
|
||||
// swapchain reads black through a plain GDI BitBlt, and crops to the client
|
||||
// area. The capture runs on the calling (UI) thread; the PNG encode runs on
|
||||
// a detached worker so the frame hitch stays minimal. Windows only; a no-op
|
||||
// with a warning elsewhere. hwnd is the platform-native window handle.
|
||||
void CaptureWindow(void* hwnd);
|
||||
// area. The capture runs on the calling thread (PrintWindow SendMessages to
|
||||
// the UI thread, so any thread works); the PNG encode runs on a detached
|
||||
// worker so the frame hitch stays minimal. Windows only; a no-op with a
|
||||
// warning elsewhere. hwnd is the platform-native window handle. tag, when
|
||||
// given, is appended to the filename (shot_<ts>_<tag>.png); the paired
|
||||
// native/emulated A/B captures share one timestamp.
|
||||
void CaptureWindow(void* hwnd, const char* tag = nullptr);
|
||||
|
||||
// The game window handle, stashed at dialog setup so frame-loop code (the
|
||||
// F11 A/B parity capture) can screenshot without an app pointer.
|
||||
void RememberWindow(void* hwnd);
|
||||
void* RememberedWindow();
|
||||
|
||||
} // namespace skate3::screenshot
|
||||
|
|
|
|||
2
third_party/rexglue-sdk
vendored
2
third_party/rexglue-sdk
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit c09b8c892d6038236def81e245dce6aef53aab19
|
||||
Subproject commit 1161489933388807195b6ac65d471b8e45640eac
|
||||
Loading…
Add table
Reference in a new issue