props, hair, normals, sky, mips, 4x MSAA; NPC torso palette + capture tooling
This commit is contained in:
parent
4a2231b14d
commit
d101dc8b78
4 changed files with 1919 additions and 358 deletions
|
|
@ -35,9 +35,14 @@ REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_min_meshes, 0, "Skate 3",
|
||||||
.range(0, 100000)
|
.range(0, 100000)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_frames, 4, "Skate 3",
|
REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_frames, 4, "Skate 3",
|
||||||
"Number of consecutive frames of RenderMesh records to collect before "
|
"Number of frames of RenderMesh records to collect before writing "
|
||||||
"writing the snapshot")
|
"the snapshot")
|
||||||
.range(1, 64)
|
.range(1, 600)
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_INT32(skate3_native_render_snapshot_stride, 1, "Skate 3",
|
||||||
|
"Record every Nth frame while collecting (long viewer recordings: "
|
||||||
|
"e.g. 12 = ~12 recorded frames/sec at 144fps)")
|
||||||
|
.range(1, 32)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_STRING(skate3_native_render_snapshot_dir, "native_render_snapshots", "Skate 3",
|
REXCVAR_DEFINE_STRING(skate3_native_render_snapshot_dir, "native_render_snapshots", "Skate 3",
|
||||||
"Directory for native-render guest memory snapshots and metadata")
|
"Directory for native-render guest memory snapshots and metadata")
|
||||||
|
|
@ -46,9 +51,12 @@ REXCVAR_DEFINE_STRING(skate3_native_render_snapshot_dir, "native_render_snapshot
|
||||||
namespace skate3::native_render {
|
namespace skate3::native_render {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// One per-mesh submission. kind 0 = RenderMesh (dynamic entities: r3 is the
|
// One per-mesh submission. kind 0 = RenderMesh (dynamic entities: a = the
|
||||||
// MeshContext itself, b = VertexProgramState). kind 1 = SceneRenderView draw
|
// MeshContext, b = VertexProgramState, c = dynitem index+1). kind 1 =
|
||||||
// list entry (world geometry: a = MeshContext, b = sort key, c = view).
|
// SceneRenderView sort-list entry (a = MeshContext, b = list offset from the
|
||||||
|
// view, c = view). kind 2 = world-path capture (skinned / model-space prop:
|
||||||
|
// a = MeshContext, b = submitting view, c = dynitem index+1). kind 3 =
|
||||||
|
// quad-list DrawVertices capture (a = synthetic key, c = dynitem index+1).
|
||||||
using RenderMeshRecord = skate3::native_scene::SubmitRecord;
|
using RenderMeshRecord = skate3::native_scene::SubmitRecord;
|
||||||
|
|
||||||
struct FrameRecords {
|
struct FrameRecords {
|
||||||
|
|
@ -60,7 +68,11 @@ std::mutex g_mutex;
|
||||||
std::vector<RenderMeshRecord> g_current_frame;
|
std::vector<RenderMeshRecord> g_current_frame;
|
||||||
std::vector<FrameRecords> g_collected_frames;
|
std::vector<FrameRecords> g_collected_frames;
|
||||||
uint64_t g_frame_index = 0;
|
uint64_t g_frame_index = 0;
|
||||||
|
uint64_t g_collect_counter = 0;
|
||||||
bool g_collecting = false;
|
bool g_collecting = false;
|
||||||
|
// F10 immediate mode: capture exactly one frame regardless of the snapshot
|
||||||
|
// frames/stride cvars.
|
||||||
|
bool g_immediate = false;
|
||||||
bool g_snapshot_written = false;
|
bool g_snapshot_written = false;
|
||||||
std::atomic<bool> g_announced{false};
|
std::atomic<bool> g_announced{false};
|
||||||
|
|
||||||
|
|
@ -156,8 +168,9 @@ bool WriteMetadata(const std::filesystem::path& path,
|
||||||
<< "\"phys_mirror_note\":\"guest addr A -> file offset A, plus 0x1000 for A >= "
|
<< "\"phys_mirror_note\":\"guest addr A -> file offset A, plus 0x1000 for A >= "
|
||||||
"0xE0000000\","
|
"0xE0000000\","
|
||||||
<< "\"record_fields\":[\"kind\",\"a\",\"b\",\"c\"],"
|
<< "\"record_fields\":[\"kind\",\"a\",\"b\",\"c\"],"
|
||||||
<< "\"kinds\":{\"0\":\"RenderMesh a=ctx b=vps\",\"1\":\"SceneDrawList a=ctx "
|
<< "\"kinds\":{\"0\":\"RenderMesh a=ctx b=vps c=dyn\",\"1\":\"SceneDrawList a=ctx "
|
||||||
"b=sort_key c=view\"}}\n";
|
"b=list_offset c=view\",\"2\":\"WorldPathCapture a=ctx b=view c=dyn\","
|
||||||
|
"\"3\":\"QuadListDraw a=key c=dyn\"}}\n";
|
||||||
for (const FrameRecords& frame : frames) {
|
for (const FrameRecords& frame : frames) {
|
||||||
out << "{\"type\":\"frame\",\"index\":" << frame.frame_index << ",\"records\":[";
|
out << "{\"type\":\"frame\",\"index\":" << frame.frame_index << ",\"records\":[";
|
||||||
for (size_t i = 0; i < frame.records.size(); ++i) {
|
for (size_t i = 0; i < frame.records.size(); ++i) {
|
||||||
|
|
@ -173,15 +186,24 @@ bool WriteMetadata(const std::filesystem::path& path,
|
||||||
return static_cast<bool>(out);
|
return static_cast<bool>(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnRenderMesh(uint8_t* base, uint32_t mesh_context, uint32_t vertex_program_state) {
|
void OnRenderMesh(uint8_t* base, uint32_t mesh_context, uint32_t vertex_program_state,
|
||||||
const uint32_t dyn = skate3::native_scene::CaptureDynamicState(base, mesh_context);
|
bool drew_inside) {
|
||||||
|
const uint32_t dyn = skate3::native_scene::CaptureDynamicState(
|
||||||
|
base, mesh_context, /*world_path=*/false, drew_inside);
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
g_current_frame.push_back({0, mesh_context, vertex_program_state, dyn});
|
g_current_frame.push_back({0, mesh_context, vertex_program_state, dyn});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SceneRenderView draw-list renderer sub_827FAF50(view, sort_vec, first, count):
|
// SceneRenderView draw-list renderer sub_827FAF50(view, sort_vec, first, count):
|
||||||
// sort_vec points at an eastl vector whose [0] is the entry array; entries are
|
// sort_vec points at an eastl vector whose [0] is the entry array; entries are
|
||||||
// 8 bytes {u32 sort_key, MeshContext*}.
|
// 8 bytes {u32 sort_key, MeshContext*}.
|
||||||
|
//
|
||||||
|
// Skinned entries and rigid MODEL-SPACE props (vending machines and other
|
||||||
|
// movables that never reach RenderMesh) are captured HERE, before the
|
||||||
|
// dispatcher draws the list. The captures are transform-pending; the
|
||||||
|
// post-draw fixup attaches the palette / world matrix at whichever draw
|
||||||
|
// eventually consumes the mesh's buffers.
|
||||||
void OnSceneDrawList(uint8_t* base, uint32_t view, uint32_t sort_vec, uint32_t first,
|
void OnSceneDrawList(uint8_t* base, uint32_t view, uint32_t sort_vec, uint32_t first,
|
||||||
uint32_t count) {
|
uint32_t count) {
|
||||||
if (count == 0 || count > 100000) {
|
if (count == 0 || count > 100000) {
|
||||||
|
|
@ -198,8 +220,17 @@ void OnSceneDrawList(uint8_t* base, uint32_t view, uint32_t sort_vec, uint32_t f
|
||||||
for (uint32_t i = 0; i < count; ++i) {
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
const uint32_t entry = entries + (first + i) * 8;
|
const uint32_t entry = entries + (first + i) * 8;
|
||||||
const uint32_t mesh_context = REX_LOAD_U32(entry + 4);
|
const uint32_t mesh_context = REX_LOAD_U32(entry + 4);
|
||||||
if (mesh_context != 0) {
|
if (mesh_context == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
g_current_frame.push_back({1, mesh_context, list_offset, view});
|
g_current_frame.push_back({1, mesh_context, list_offset, view});
|
||||||
|
const uint32_t dyn =
|
||||||
|
skate3::native_scene::CaptureDynamicState(base, mesh_context, /*world_path=*/true);
|
||||||
|
if (dyn != 0) {
|
||||||
|
// b = the submitting view: shadow-cascade views submit their own
|
||||||
|
// contexts for the same NPCs; rendering those creates ghost
|
||||||
|
// duplicates (torso-less: their deferred skin passes never run).
|
||||||
|
g_current_frame.push_back({2, mesh_context, view, dyn});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,12 +242,25 @@ void WriteSnapshotLocked(uint8_t* base) {
|
||||||
const std::string stem = SnapshotStem();
|
const std::string stem = SnapshotStem();
|
||||||
const bool meta_ok = WriteMetadata(dir / (stem + ".meta.jsonl"), g_collected_frames);
|
const bool meta_ok = WriteMetadata(dir / (stem + ".meta.jsonl"), g_collected_frames);
|
||||||
const bool snap_ok = WriteMemorySnapshot(base, dir / (stem + ".gsnap"));
|
const bool snap_ok = WriteMemorySnapshot(base, dir / (stem + ".gsnap"));
|
||||||
|
skate3::native_scene::WriteRecording(dir.string().c_str(), stem.c_str());
|
||||||
REXLOG_INFO("native-render snapshot: {} ({} frames of records, meta_ok={} snap_ok={})",
|
REXLOG_INFO("native-render snapshot: {} ({} frames of records, meta_ok={} snap_ok={})",
|
||||||
stem, g_collected_frames.size(), meta_ok, snap_ok);
|
stem, g_collected_frames.size(), meta_ok, snap_ok);
|
||||||
g_collected_frames.clear();
|
g_collected_frames.clear();
|
||||||
g_snapshot_written = true;
|
g_snapshot_written = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-indexed cloth patch draws (see native_scene::CaptureClothDraw). The
|
||||||
|
// synthetic "context" key is the dynamic buffer object, stable per garment.
|
||||||
|
void OnClothDraw(uint8_t* base, uint32_t r4, uint32_t r5, uint32_t r6, uint32_t r7) {
|
||||||
|
uint32_t key = 0;
|
||||||
|
const uint32_t dyn = skate3::native_scene::CaptureClothDraw(base, r4, r5, r6, r7, &key);
|
||||||
|
if (dyn == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
g_current_frame.push_back({3, key, 0, dyn});
|
||||||
|
}
|
||||||
|
|
||||||
void OnFrameEnd(uint8_t* base) {
|
void OnFrameEnd(uint8_t* base) {
|
||||||
std::lock_guard<std::mutex> lock(g_mutex);
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
++g_frame_index;
|
++g_frame_index;
|
||||||
|
|
@ -236,31 +280,75 @@ void OnFrameEnd(uint8_t* base) {
|
||||||
if (!g_collecting && min_meshes > 0 &&
|
if (!g_collecting && min_meshes > 0 &&
|
||||||
mesh_count >= static_cast<size_t>(min_meshes)) {
|
mesh_count >= static_cast<size_t>(min_meshes)) {
|
||||||
g_collecting = true;
|
g_collecting = true;
|
||||||
|
g_collect_counter = 0;
|
||||||
|
skate3::native_scene::StartRecording(
|
||||||
|
uint32_t(REXCVAR_GET(skate3_native_render_snapshot_stride)));
|
||||||
REXLOG_INFO("native-render snapshot: armed at frame {} ({} meshes)", g_frame_index,
|
REXLOG_INFO("native-render snapshot: armed at frame {} ({} meshes)", g_frame_index,
|
||||||
mesh_count);
|
mesh_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Manual trigger (works repeatedly): create <snapshot_dir>\trigger while
|
// Manual triggers (work repeatedly): press F9 (window recording per the
|
||||||
// the game runs.
|
// snapshot cvars), F10 (IMMEDIATE single-frame capture: full memory
|
||||||
|
// snapshot + this frame's records/draws, for catching a broken object the
|
||||||
|
// moment it is on screen), or create <snapshot_dir>\trigger.
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (!g_collecting) {
|
||||||
|
static bool f9_was_down = false;
|
||||||
|
const bool f9_down = (GetAsyncKeyState(VK_F9) & 0x8000) != 0;
|
||||||
|
if (f9_down && !f9_was_down) {
|
||||||
|
g_collecting = true;
|
||||||
|
g_collect_counter = 0;
|
||||||
|
skate3::native_scene::StartRecording(
|
||||||
|
uint32_t(REXCVAR_GET(skate3_native_render_snapshot_stride)));
|
||||||
|
REXLOG_INFO("native-render snapshot: F9, armed at frame {} ({} meshes)",
|
||||||
|
g_frame_index, mesh_count);
|
||||||
|
}
|
||||||
|
f9_was_down = f9_down;
|
||||||
|
static bool f10_was_down = false;
|
||||||
|
const bool f10_down = (GetAsyncKeyState(VK_F10) & 0x8000) != 0;
|
||||||
|
if (f10_down && !f10_was_down) {
|
||||||
|
g_collecting = true;
|
||||||
|
g_immediate = true;
|
||||||
|
g_collect_counter = 0;
|
||||||
|
skate3::native_scene::StartRecording(1);
|
||||||
|
REXLOG_INFO("native-render snapshot: F10, immediate single-frame capture at frame {}",
|
||||||
|
g_frame_index);
|
||||||
|
}
|
||||||
|
f10_was_down = f10_down;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (!g_collecting && g_frame_index % 32 == 0) {
|
if (!g_collecting && g_frame_index % 32 == 0) {
|
||||||
const std::filesystem::path trigger = SnapshotDir() / "trigger";
|
const std::filesystem::path trigger = SnapshotDir() / "trigger";
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
if (std::filesystem::exists(trigger, ec)) {
|
if (std::filesystem::exists(trigger, ec)) {
|
||||||
std::filesystem::remove(trigger, ec);
|
std::filesystem::remove(trigger, ec);
|
||||||
g_collecting = true;
|
g_collecting = true;
|
||||||
|
g_collect_counter = 0;
|
||||||
|
skate3::native_scene::StartRecording(
|
||||||
|
uint32_t(REXCVAR_GET(skate3_native_render_snapshot_stride)));
|
||||||
REXLOG_INFO("native-render snapshot: trigger file, armed at frame {} ({} meshes)",
|
REXLOG_INFO("native-render snapshot: trigger file, armed at frame {} ({} meshes)",
|
||||||
g_frame_index, mesh_count);
|
g_frame_index, mesh_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (g_collecting) {
|
if (g_collecting) {
|
||||||
|
// Immediate (F10) captures skip the arming frame: the scene-side
|
||||||
|
// recording was only armed after this frame's BuildFrameScene ran, so
|
||||||
|
// the first fully-recorded frame is the next one.
|
||||||
|
const auto stride = g_immediate
|
||||||
|
? uint64_t(2)
|
||||||
|
: static_cast<uint64_t>(REXCVAR_GET(skate3_native_render_snapshot_stride));
|
||||||
|
if (++g_collect_counter % stride == 0) {
|
||||||
g_collected_frames.push_back({g_frame_index, std::move(g_current_frame)});
|
g_collected_frames.push_back({g_frame_index, std::move(g_current_frame)});
|
||||||
const auto wanted =
|
const auto wanted = g_immediate
|
||||||
static_cast<size_t>(REXCVAR_GET(skate3_native_render_snapshot_frames));
|
? size_t(1)
|
||||||
|
: static_cast<size_t>(REXCVAR_GET(skate3_native_render_snapshot_frames));
|
||||||
if (g_collected_frames.size() >= wanted) {
|
if (g_collected_frames.size() >= wanted) {
|
||||||
g_collecting = false;
|
g_collecting = false;
|
||||||
|
g_immediate = false;
|
||||||
WriteSnapshotLocked(base);
|
WriteSnapshotLocked(base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g_current_frame.clear();
|
g_current_frame.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -285,11 +373,27 @@ void Install() {
|
||||||
// "RenderMesh" per-visible-mesh submission for dynamic entities (characters,
|
// "RenderMesh" per-visible-mesh submission for dynamic entities (characters,
|
||||||
// props). Actual convention (verified via recompiled code + snapshot):
|
// props). Actual convention (verified via recompiled code + snapshot):
|
||||||
// r3 = MeshContext*, r4 = renderengine::VertexProgramState*.
|
// r3 = MeshContext*, r4 = renderengine::VertexProgramState*.
|
||||||
|
//
|
||||||
|
// The dynamic state snapshot must be taken AFTER the original call: the
|
||||||
|
// mesh's VS constants (instance world matrix, bone palette) are only flushed
|
||||||
|
// through D3D::SetPending_AluConstants from inside DrawIndexedVertices
|
||||||
|
// (sub_82B7AD68), i.e. during the RenderMesh body. Capturing on entry reads
|
||||||
|
// the PREVIOUS draw's constants and renders every dynamic entity with the
|
||||||
|
// previous entity's transform/palette.
|
||||||
|
// Deferred (multi-pass) meshes draw nothing inside the call, detected via
|
||||||
|
// the draw sequence counter so their transforms are left for the post-draw
|
||||||
|
// fixup instead of being read from a stale constant bank.
|
||||||
extern "C" REX_FUNC(sub_82795AD8) {
|
extern "C" REX_FUNC(sub_82795AD8) {
|
||||||
if (skate3::native_render::Enabled()) {
|
const bool enabled = skate3::native_render::Enabled();
|
||||||
skate3::native_render::OnRenderMesh(base, ctx.r3.u32, ctx.r4.u32);
|
const uint32_t mesh_context = ctx.r3.u32;
|
||||||
}
|
const uint32_t vertex_program_state = ctx.r4.u32;
|
||||||
|
const uint64_t draws_before = skate3::native_scene::DrawSequence();
|
||||||
__imp__sub_82795AD8(ctx, base);
|
__imp__sub_82795AD8(ctx, base);
|
||||||
|
if (enabled) {
|
||||||
|
const bool drew_inside = skate3::native_scene::DrawSequence() != draws_before;
|
||||||
|
skate3::native_render::OnRenderMesh(base, mesh_context, vertex_program_state,
|
||||||
|
drew_inside);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SceneRenderView sorted draw-list renderer (world geometry):
|
// SceneRenderView sorted draw-list renderer (world geometry):
|
||||||
|
|
@ -304,6 +408,7 @@ extern "C" REX_FUNC(sub_827FAF50) {
|
||||||
__imp__sub_827FAF50(ctx, base);
|
__imp__sub_827FAF50(ctx, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Guest D3D Swap: frame boundary.
|
// Guest D3D Swap: frame boundary.
|
||||||
extern "C" REX_FUNC(sub_82B82E08) {
|
extern "C" REX_FUNC(sub_82B82E08) {
|
||||||
if (skate3::native_render::Enabled()) {
|
if (skate3::native_render::Enabled()) {
|
||||||
|
|
@ -323,11 +428,90 @@ extern "C" REX_FUNC(sub_82C9A618) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// D3D::SetPending_AluConstants(device, u64 dirty_group_mask, bank, ptr):
|
// D3D::SetPending_AluConstants(device, u64 dirty_group_mask, bank, ptr):
|
||||||
// bank 0x4000 = vertex constants. Bone palettes are staged through here
|
// bank 0x4000 = vertex constants. Called from inside the Draw* functions;
|
||||||
// right before the skinned RenderMesh calls that consume them.
|
// ptr is the device's positional constant shadow bank.
|
||||||
extern "C" REX_FUNC(sub_82B83FE0) {
|
extern "C" REX_FUNC(sub_82B83FE0) {
|
||||||
if (skate3::native_render::Enabled()) {
|
if (skate3::native_render::Enabled()) {
|
||||||
skate3::native_scene::OnVsConstantUpload(base, ctx.r4.u64, ctx.r5.u32, ctx.r6.u32);
|
skate3::native_scene::OnVsConstantUpload(base, ctx.r4.u64, ctx.r5.u32, ctx.r6.u32,
|
||||||
|
ctx.r3.u32);
|
||||||
}
|
}
|
||||||
__imp__sub_82B83FE0(ctx, base);
|
__imp__sub_82B83FE0(ctx, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// D3DDevice_SetIndices(device, ib) / D3DDevice_SetStreamSource(device,
|
||||||
|
// stream, vb, offset, stride): track the currently bound guest buffers so
|
||||||
|
// draws can be matched back to captured skinned items.
|
||||||
|
extern "C" REX_FUNC(sub_82B79190) {
|
||||||
|
if (skate3::native_render::Enabled()) {
|
||||||
|
skate3::native_scene::OnSetIndices(ctx.r4.u32);
|
||||||
|
}
|
||||||
|
__imp__sub_82B79190(ctx, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" REX_FUNC(sub_82B78FF0) {
|
||||||
|
if (skate3::native_render::Enabled()) {
|
||||||
|
skate3::native_scene::OnSetStreamSource(ctx.r4.u32, ctx.r5.u32, ctx.r6.u32,
|
||||||
|
ctx.r7.u32);
|
||||||
|
}
|
||||||
|
__imp__sub_82B78FF0(ctx, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// D3DDevice_DrawIndexedVertices: post-call, the draw's VS constants are now
|
||||||
|
// in the shadow bank; refresh any pending skinned item bound to these
|
||||||
|
// buffers (deferred multi-pass meshes only draw here).
|
||||||
|
extern "C" REX_FUNC(sub_82B7AD68) {
|
||||||
|
const bool enabled = skate3::native_render::Enabled();
|
||||||
|
const uint32_t r4 = ctx.r4.u32;
|
||||||
|
const uint32_t r5 = ctx.r5.u32;
|
||||||
|
const uint32_t r6 = ctx.r6.u32;
|
||||||
|
const uint32_t r7 = ctx.r7.u32;
|
||||||
|
__imp__sub_82B7AD68(ctx, base);
|
||||||
|
if (enabled) {
|
||||||
|
skate3::native_scene::OnDrawDone(base, 0, r4, r5, r6, r7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D3DDevice_DrawVertices, non-indexed draw path: cloth-simulated garments
|
||||||
|
// (captured live as world-space quad items) and character shadow proxies.
|
||||||
|
extern "C" REX_FUNC(sub_82B7A970) {
|
||||||
|
const bool enabled = skate3::native_render::Enabled();
|
||||||
|
const uint32_t r4 = ctx.r4.u32;
|
||||||
|
const uint32_t r5 = ctx.r5.u32;
|
||||||
|
const uint32_t r6 = ctx.r6.u32;
|
||||||
|
const uint32_t r7 = ctx.r7.u32;
|
||||||
|
__imp__sub_82B7A970(ctx, base);
|
||||||
|
if (enabled) {
|
||||||
|
skate3::native_scene::OnDrawDone(base, 1, r4, r5, r6, r7);
|
||||||
|
skate3::native_render::OnClothDraw(base, r4, r5, r6, r7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth SetPending_AluConstants caller (symbol mislabeled as
|
||||||
|
// D3DQuery_GetData: it stages draw constants, so it is a draw variant;
|
||||||
|
// never observed firing in gameplay). Hooked so the draw-sequence counter
|
||||||
|
// and recording stay complete if it ever does.
|
||||||
|
extern "C" REX_FUNC(sub_82B7A458) {
|
||||||
|
const bool enabled = skate3::native_render::Enabled();
|
||||||
|
const uint32_t r4 = ctx.r4.u32;
|
||||||
|
const uint32_t r5 = ctx.r5.u32;
|
||||||
|
const uint32_t r6 = ctx.r6.u32;
|
||||||
|
const uint32_t r7 = ctx.r7.u32;
|
||||||
|
__imp__sub_82B7A458(ctx, base);
|
||||||
|
if (enabled) {
|
||||||
|
skate3::native_scene::OnDrawDone(base, 3, r4, r5, r6, r7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D3DDevice_BeginVertices: inline (write-through-ring) vertex path; the
|
||||||
|
// CPU writes computed vertices directly. Post-call r3 = guest write pointer.
|
||||||
|
extern "C" REX_FUNC(sub_82B79FC0) {
|
||||||
|
const bool enabled = skate3::native_render::Enabled();
|
||||||
|
const uint32_t r4 = ctx.r4.u32;
|
||||||
|
const uint32_t r5 = ctx.r5.u32;
|
||||||
|
const uint32_t r6 = ctx.r6.u32;
|
||||||
|
const uint32_t r7 = ctx.r7.u32;
|
||||||
|
__imp__sub_82B79FC0(ctx, base);
|
||||||
|
if (enabled) {
|
||||||
|
skate3::native_scene::OnDrawDone(base, 2, r4, r5, r6, ctx.r3.u32 != 0 ? ctx.r3.u32 : r7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,8 @@ struct DrawEntry {
|
||||||
|
|
||||||
struct DrawItem {
|
struct DrawItem {
|
||||||
uint32_t mesh; // guest mesh object address (resource cache key)
|
uint32_t mesh; // guest mesh object address (resource cache key)
|
||||||
|
uint32_t vb_obj; // guest renderengine::VertexBuffer object (draw matching)
|
||||||
|
uint32_t ib_obj; // guest renderengine::IndexBuffer object (draw matching)
|
||||||
uint32_t vb_addr; // guest address of raw vertex data
|
uint32_t vb_addr; // guest address of raw vertex data
|
||||||
uint32_t ib_addr; // guest address of raw index data (u16 big-endian)
|
uint32_t ib_addr; // guest address of raw index data (u16 big-endian)
|
||||||
uint32_t vb_bytes;
|
uint32_t vb_bytes;
|
||||||
|
|
@ -32,13 +34,34 @@ struct DrawItem {
|
||||||
uint16_t uv2_offset;
|
uint16_t uv2_offset;
|
||||||
uint16_t bw_offset; // blend weights (u8x4), valid when skinned
|
uint16_t bw_offset; // blend weights (u8x4), valid when skinned
|
||||||
uint16_t bi_offset; // blend indices (u8x4), valid when skinned
|
uint16_t bi_offset; // blend indices (u8x4), valid when skinned
|
||||||
|
uint16_t normal_offset; // usage-3 vertex normal (0 fmt = none)
|
||||||
uint8_t stride;
|
uint8_t stride;
|
||||||
uint8_t pos_fmt; // xenos vertex format (26 s16x4, 32 half4, 57 float3)
|
uint8_t pos_fmt; // xenos vertex format (26 s16x4, 32 half4, 57 float3)
|
||||||
uint8_t uv_fmt; // xenos vertex format of the first texcoord (0 = none)
|
uint8_t uv_fmt; // xenos vertex format of the first texcoord (0 = none)
|
||||||
uint8_t uv2_fmt; // xenos vertex format of the second texcoord (0 = none)
|
uint8_t uv2_fmt; // xenos vertex format of the second texcoord (0 = none)
|
||||||
|
uint8_t normal_fmt; // 16 = k_10_11_11 packed (0 = none, face-normal fallback)
|
||||||
|
// sky.* materials draw fullbright: per-facet lighting turns the multi-km
|
||||||
|
// sky dome into visibly shaded rectangular panels.
|
||||||
|
bool unlit;
|
||||||
bool skinned;
|
bool skinned;
|
||||||
// Bone palette snapshot (row-vector 4x4 matrices) taken on the game thread
|
// Grayscale-tinted material (CAS hair): the shader multiplies the diffuse
|
||||||
// - the guest bone array lives in a transient per-frame arena.
|
// by a per-character color staged in the PIXEL constant bank. Detected via
|
||||||
|
// the AttribulatorMaterialName channel; tint captured with the palette.
|
||||||
|
bool hair;
|
||||||
|
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
|
||||||
|
// meshes draw AFTER the submit call, and world sort-list captures happen
|
||||||
|
// before any draw. Cleared by the post-draw (ib,vb) fixup; items still
|
||||||
|
// pending at frame end are dropped (wrong state renders garbage).
|
||||||
|
bool pending;
|
||||||
|
// Cloth patch (non-indexed quad list, CPU-simulated absolute world-space
|
||||||
|
// float3 verts): the renderer synthesizes quad->triangle indices instead
|
||||||
|
// of reading a guest index buffer.
|
||||||
|
bool cloth_quads;
|
||||||
|
// Bone palette snapshot taken on the game thread: raw staged rows, 3
|
||||||
|
// float4s per bone (column-vector affine [R | t], model -> world). The
|
||||||
|
// guest staging bank is reused draw to draw, hence the copy.
|
||||||
std::vector<float> bones;
|
std::vector<float> bones;
|
||||||
// Content fingerprint of the guest payloads. Streaming reuses arena
|
// Content fingerprint of the guest payloads. Streaming reuses arena
|
||||||
// addresses and pages fill in after the mesh is first submitted, so cached
|
// addresses and pages fill in after the mesh is first submitted, so cached
|
||||||
|
|
@ -71,16 +94,58 @@ bool Enabled();
|
||||||
// renderengine::Texture object.
|
// renderengine::Texture object.
|
||||||
void OnRegisterTexture(uint64_t guid, uint32_t texture);
|
void OnRegisterTexture(uint64_t guid, uint32_t texture);
|
||||||
|
|
||||||
// Called from the D3D::SetPending_AluConstants hook (guest render thread).
|
// Called from the D3D::SetPending_AluConstants hook (guest render thread,
|
||||||
// Large vertex-bank uploads are bone palettes staged for the next skinned
|
// invoked from inside the Draw* functions). Records the location of the
|
||||||
// RenderMesh; the staging bank is reused, so contents are snapshotted here.
|
// device's positional 256-register VS constant shadow bank.
|
||||||
void OnVsConstantUpload(uint8_t* base, uint64_t mask, uint32_t bank, uint32_t ptr);
|
void OnVsConstantUpload(uint8_t* base, uint64_t mask, uint32_t bank, uint32_t ptr,
|
||||||
|
uint32_t device);
|
||||||
|
|
||||||
// Called from the RenderMesh hook for dynamic entities. Snapshots the
|
// Guest buffer-binding trackers + post-draw state fixup: each
|
||||||
// context's instance matrix (ctx+0x10 points into a transient per-frame
|
// DrawIndexedVertices whose bound IB/VB match a pending item fills that
|
||||||
// arena; it must be read now, not at frame end) and the pending bone
|
// item's bone palette / world matrix from the constant bank (FIFO one-shot
|
||||||
// palette. Returns a dynamic-state index (+1), 0 on failure.
|
// per buffer key; clones share mesh assets and draw in submit order).
|
||||||
uint32_t CaptureDynamicState(uint8_t* base, uint32_t ctx);
|
void OnSetIndices(uint32_t ib_obj);
|
||||||
|
void OnSetStreamSource(uint32_t stream, uint32_t vb_obj, uint32_t offset, uint32_t stride);
|
||||||
|
// func: 0 = DrawIndexedVertices (also runs the pending fixup),
|
||||||
|
// 1 = DrawVertices, 2 = BeginVertices (inline/cloth vertex path).
|
||||||
|
void OnDrawDone(uint8_t* base, uint32_t func, uint32_t r4, uint32_t r5, uint32_t r6,
|
||||||
|
uint32_t r7);
|
||||||
|
|
||||||
|
// Offline-analysis recording, armed alongside the guest-memory snapshot:
|
||||||
|
// captures every draw's bound buffers + full VS constant bank, plus the
|
||||||
|
// per-frame captured items and final scene, every `stride` frames. Written
|
||||||
|
// as <stem>.scene.jsonl and <stem>.draws.bin next to the .gsnap.
|
||||||
|
void StartRecording(uint32_t stride);
|
||||||
|
void WriteRecording(const char* dir, const char* stem);
|
||||||
|
|
||||||
|
// Called post-call from the DrawVertices hook for cloth patch draws
|
||||||
|
// (r6 == 0x80000000 signature): reads the bound dynamic buffer's vertex
|
||||||
|
// fetch block and builds a world-space quad-list item. Returns a
|
||||||
|
// dynamic-state index (+1), 0 when the draw is not a cloth patch;
|
||||||
|
// *out_key receives the garment's stable identity (the buffer object).
|
||||||
|
uint32_t CaptureClothDraw(uint8_t* base, uint32_t r4, uint32_t r5, uint32_t r6,
|
||||||
|
uint32_t r7, uint32_t* out_key);
|
||||||
|
|
||||||
|
// Total guest draws completed (any draw function). The RenderMesh hook
|
||||||
|
// samples this around the original call to detect deferred meshes: if no
|
||||||
|
// draw ran inside, the constant bank still belongs to an earlier mesh and
|
||||||
|
// the item's transform/palette must come from the post-draw fixup.
|
||||||
|
uint64_t DrawSequence();
|
||||||
|
|
||||||
|
// Called from the RenderMesh hook (dynamic entities) and the world per-mesh
|
||||||
|
// draw hook (sub_827FAEA8), AFTER the original call returns (the mesh's
|
||||||
|
// constants are only flushed to the shadow bank by its own draws). Builds
|
||||||
|
// the complete DrawItem: geometry from the transient per-frame arena plus
|
||||||
|
// world matrix / bone palette read live from the shadow bank. drew_inside
|
||||||
|
// says whether any guest draw ran during the original call; when false the
|
||||||
|
// bank is stale and the transform is left pending for the post-draw fixup.
|
||||||
|
// With world_path, bails out early for meshes that need no per-draw
|
||||||
|
// transform (absolute fmt-57 geometry, handled at frame end) and captures
|
||||||
|
// only skinned meshes and rigid model-space props (vending machines and
|
||||||
|
// other movables reach the frame ONLY through the sort lists). Returns a
|
||||||
|
// dynamic-state index (+1), 0 on failure/skip.
|
||||||
|
uint32_t CaptureDynamicState(uint8_t* base, uint32_t ctx, bool world_path = false,
|
||||||
|
bool drew_inside = false);
|
||||||
|
|
||||||
// Called on the guest render thread at frame end with the frame's records.
|
// Called on the guest render thread at frame end with the frame's records.
|
||||||
void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count);
|
void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count);
|
||||||
|
|
|
||||||
2
third_party/rexglue-sdk
vendored
2
third_party/rexglue-sdk
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit c4b0dfe2c9162340d348d1e86d7b0e1aed353101
|
Subproject commit c09b8c892d6038236def81e245dce6aef53aab19
|
||||||
Loading…
Add table
Reference in a new issue