mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 18:01:03 +00:00
replay_workers = N (0, the default, keeps the sequential replay) records a submission of enough draws in chunks that start between passes, on N threads and in twice as many chunks, and submits them in order. In the town at 4K on an RTX 5070, 4 threads take the replay from 8.2 to 4.5 ms a frame and the frame rate from 71 to 79.
A sequential scan on the submitting thread applies only what is queue state (fixed-function and bind ops, render targets, render enable and conditions, samples-passed counting) and keeps a copy of it where each chunk starts, with how far its lists got: inline uniform updates, RENDER_COND results, contact shadow marches and the ops that drop uniform copies. A chunk takes those from the lists instead of from the chunks before it, starts with every image in GENERAL and ends with a full barrier, so its recording depends only on the copy and the lists.
Each thread records with a context of its own: the replay's memos, descriptor state, occlusion query segments and per-command-buffer state moved from file statics into VkExec and VkExecDraw, and the tables the contexts share (stand-ins, depth twins, views, mirrors, timestamps, completion callbacks, crumbs) take locks or atomics. The scan makes render targets live in replay order and notes the op (vk_texture_revive), since a chunk can sample a storage before the chunk that renders it got there or after the scan went past a later target there: a chunk's texture slots resolve as of its own op, and the chunks after a texture with no entry yet wait for the chunk that makes it. Uniform copies are dropped at the op that writes them (vk_usnap_drop_at).
Contact shadows plan their march in replay order (vk_contact_plan) and the chunk that holds its op records it; the HDR source keeps the copy that comes last in replay order. DLSS and fxaa = 0 act on the frame draw by draw and keep the replay sequential. TOTK_REPLAY_STATE_CHECK=1 compares the state after the last chunk with the scan's.
A draw or dispatch whose shaders write storage buffers drops the uniform copies they cover from its own op on, as the other GPU writes do (scan_writes). The scan still translates no shader: it reads the shaders the recording contexts translated from a table shared with them (vk_exec_draw_writes), and an op whose shader no context translated yet holds the chunks after it until it is recorded. Before, the chunk that recorded such a write dropped the copies for every position at that moment, so a later chunk recorded first bound the copy taken at the submit call: on an RX 7800 XT on RADV the grass near the camera came out dark or unshaded in some runs with 4 threads.
TOTK_TRACE=perf counts the chunks held per submission by reason and logs when each chunk of the last submission began. The settings menu has the thread count under Advanced, Scheduling, with the count this run uses under it.
The recorder thread begins, ends and submits the submission's open part (vk_rec.c rec_end_cb), so a chunk's mirror prologue stays off it: vk_sync_part waits until the recorder is idle, as the part pool allows one thread at a time, then records, ends and submits the prologue itself. Through the recorder the prologue could reach the queue before it was ended, and the GPU lost the device at the title screen.
(cherry picked from commit b94e2e9642)
177 lines
15 KiB
C
177 lines
15 KiB
C
// The recorder thread (vk_rec.c): the Vulkan calls the replay makes into its command buffers are made by a thread of
|
|
// their own. vk_sync.c begins each part of a submission (and the mirror prologue) with rec_begin, ends it with
|
|
// rec_end_cb and submits it with rec_submit. In between, every vkCmd* on that command buffer copies its arguments
|
|
// into a packet of a ring, in call order, and the recorder makes the real call in that order; calls into any other
|
|
// command buffer (uploads, the window, probes) are made at once. A call the ring has no packet for (a structure with a
|
|
// pNext chain) waits for the recorder to empty the ring and is then made at once on the calling thread. With
|
|
// TOTK_REC_END=1 (default) the recorder also begins, ends and submits the parts it records, so a part's command buffer
|
|
// never changes threads. rec_submit queues the submission and returns at once (TOTK_REC_ASYNC=1, default; 0: once the
|
|
// recorder submitted it); every other user of the queue waits in vk_queue_lock until the recorder submitted what was
|
|
// queued before (rec_queue_wait), so queue order stays the order of the calls. The submitting thread allocates from
|
|
// the part pool only while the recorder is idle (vk_sync.c claim_slot).
|
|
//
|
|
// Every vkCmd* the runtime calls through vk.h goes through the rc_* wrappers below (the macros at the end); the
|
|
// extension commands called through function pointers have rec_* wrappers of their own. TOTK_VK_RECORDER=0 makes every
|
|
// call at once (the same calls in the same order), =ab switches it with the A/B blocks (rt_ab), per submission part.
|
|
//
|
|
// The design follows Savage Wind Recomp's and LAS Recomp's recorder threads (LAS Recomp runtime/nvn/vulkan/record.c,
|
|
// MIT, Copyright (c) 2026 Peiikai): the driver's work behind the commands depends on nothing the replay decides next.
|
|
#pragma once
|
|
|
|
// The submitting thread, sub_lock held: vkBeginCommandBuffer (one submission) on cb and the breadcrumb crumb (0: none,
|
|
// vk_crumbs.c) with number n, made by the recorder when it records the part, else at once; fence: the part's value.
|
|
void rec_begin(VkCommandBuffer cb, uint64_t fence, int crumb, uint64_t n);
|
|
void rec_end(void); // waits for the recorder; no-op when rec_begin did not start it
|
|
// The end of part cb: the breadcrumb crumb, then vkEndCommandBuffer, both queued for the recorder when it records cb
|
|
// (1: rec_submit hands the part over); otherwise rec_end and the breadcrumb, and the caller ends cb (0).
|
|
int rec_end_cb(VkCommandBuffer cb, int crumb, uint64_t n);
|
|
// vkQueueSubmit2 of si (at most two command buffers, one signal, no wait, no fence) under vk_queue_lock: by the
|
|
// recorder after a part it ended, else at once. Queued for the recorder, it returns the first failure of an earlier
|
|
// submission the recorder made (VK_SUCCESS if none), since this one's result is not known yet.
|
|
VkResult rec_submit(const VkSubmitInfo2 *si);
|
|
void rec_drain(void); // waits until the recorder made every call queued so far
|
|
// vk_queue_lock, before it takes the lock (not on the recorder, not when the thread holds the lock already): waits
|
|
// until the recorder submitted every submission queued so far.
|
|
void rec_queue_wait(void);
|
|
int rec_owns(VkCommandBuffer cb); // the recorder makes cb's calls: a direct call into cb follows rec_drain
|
|
void rec_stats_log(void); // TOTK_TRACE=perf: packets, bytes, drains and waits since the last call
|
|
// TOTK_REC_CHECK=1 (debug, recorder on): vk_memory.c reports each object it defers (destroy 0, stamped tl) or destroys
|
|
// (destroy 1) with the value the GPU reached (gpu); a packet of the part being recorded that still names it is logged
|
|
// as a hazard when the object can go before the part ran. rec_check_log: the counts by kind and packet, at exit.
|
|
enum { REC_IMAGE, REC_VIEW, REC_BUFFER, REC_KINDS };
|
|
void rec_check_handle(int kind, uint64_t handle, int destroy, uint64_t tl, uint64_t gpu);
|
|
void rec_check_log(void);
|
|
|
|
// Extension commands called through device function pointers.
|
|
void rec_push_tpl(PFN_vkCmdPushDescriptorSetWithTemplateKHR fn, VkCommandBuffer cb, VkDescriptorUpdateTemplate tpl,
|
|
VkPipelineLayout layout, uint32_t set, const void *data, uint32_t bytes); // data: bytes read by tpl
|
|
void rec_push(PFN_vkCmdPushDescriptorSetKHR fn, VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipelineLayout layout,
|
|
uint32_t set, uint32_t n, const VkWriteDescriptorSet *w);
|
|
void rec_vertex_input(PFN_vkCmdSetVertexInputEXT fn, VkCommandBuffer cb, uint32_t nb,
|
|
const VkVertexInputBindingDescription2EXT *b, uint32_t na,
|
|
const VkVertexInputAttributeDescription2EXT *a);
|
|
void rec_bool(PFN_vkCmdSetDepthClampEnableEXT fn, VkCommandBuffer cb, VkBool32 v); // also alpha to coverage
|
|
void rec_polygon_mode(PFN_vkCmdSetPolygonModeEXT fn, VkCommandBuffer cb, VkPolygonMode m);
|
|
void rec_blend_enable(PFN_vkCmdSetColorBlendEnableEXT fn, VkCommandBuffer cb, uint32_t first, uint32_t n, const VkBool32 *v);
|
|
void rec_blend_equation(PFN_vkCmdSetColorBlendEquationEXT fn, VkCommandBuffer cb, uint32_t first, uint32_t n,
|
|
const VkColorBlendEquationEXT *v);
|
|
void rec_write_mask(PFN_vkCmdSetColorWriteMaskEXT fn, VkCommandBuffer cb, uint32_t first, uint32_t n,
|
|
const VkColorComponentFlags *v);
|
|
// A pass mark (vk_crumbs.c vk_pass_mark_cmd: a buffer marker and a checkpoint), in order with the commands around it.
|
|
void rec_pass_mark(VkCommandBuffer cb, uint32_t id, int end);
|
|
|
|
#define REC_CMDS(X) \
|
|
X(BeginQuery, (VkCommandBuffer cb, VkQueryPool p, uint32_t q, VkQueryControlFlags f), (cb, p, q, f)) \
|
|
X(BeginRendering, (VkCommandBuffer cb, const VkRenderingInfo *ri), (cb, ri)) \
|
|
X(BindDescriptorSets, (VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipelineLayout l, uint32_t first, uint32_t n, \
|
|
const VkDescriptorSet *s, uint32_t nd, const uint32_t *d), (cb, bp, l, first, n, s, nd, d)) \
|
|
X(BindIndexBuffer, (VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, VkIndexType t), (cb, b, o, t)) \
|
|
X(BindPipeline, (VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipeline p), (cb, bp, p)) \
|
|
X(BindVertexBuffers2, (VkCommandBuffer cb, uint32_t first, uint32_t n, const VkBuffer *b, const VkDeviceSize *o, \
|
|
const VkDeviceSize *sz, const VkDeviceSize *st), (cb, first, n, b, o, sz, st)) \
|
|
X(BlitImage, (VkCommandBuffer cb, VkImage s, VkImageLayout sl, VkImage d, VkImageLayout dl, uint32_t n, \
|
|
const VkImageBlit *r, VkFilter f), (cb, s, sl, d, dl, n, r, f)) \
|
|
X(ClearAttachments, (VkCommandBuffer cb, uint32_t na, const VkClearAttachment *a, uint32_t nr, const VkClearRect *r), \
|
|
(cb, na, a, nr, r)) \
|
|
X(ClearColorImage, (VkCommandBuffer cb, VkImage i, VkImageLayout l, const VkClearColorValue *c, uint32_t n, \
|
|
const VkImageSubresourceRange *r), (cb, i, l, c, n, r)) \
|
|
X(ClearDepthStencilImage, (VkCommandBuffer cb, VkImage i, VkImageLayout l, const VkClearDepthStencilValue *c, \
|
|
uint32_t n, const VkImageSubresourceRange *r), (cb, i, l, c, n, r)) \
|
|
X(CopyBuffer, (VkCommandBuffer cb, VkBuffer s, VkBuffer d, uint32_t n, const VkBufferCopy *r), (cb, s, d, n, r)) \
|
|
X(CopyBufferToImage, (VkCommandBuffer cb, VkBuffer s, VkImage d, VkImageLayout dl, uint32_t n, \
|
|
const VkBufferImageCopy *r), (cb, s, d, dl, n, r)) \
|
|
X(CopyImage, (VkCommandBuffer cb, VkImage s, VkImageLayout sl, VkImage d, VkImageLayout dl, uint32_t n, \
|
|
const VkImageCopy *r), (cb, s, sl, d, dl, n, r)) \
|
|
X(CopyImageToBuffer, (VkCommandBuffer cb, VkImage s, VkImageLayout sl, VkBuffer d, uint32_t n, \
|
|
const VkBufferImageCopy *r), (cb, s, sl, d, n, r)) \
|
|
X(Dispatch, (VkCommandBuffer cb, uint32_t x, uint32_t y, uint32_t z), (cb, x, y, z)) \
|
|
X(DispatchIndirect, (VkCommandBuffer cb, VkBuffer b, VkDeviceSize o), (cb, b, o)) \
|
|
X(Draw, (VkCommandBuffer cb, uint32_t n, uint32_t i, uint32_t f, uint32_t fi), (cb, n, i, f, fi)) \
|
|
X(DrawIndexed, (VkCommandBuffer cb, uint32_t n, uint32_t i, uint32_t f, int32_t v, uint32_t fi), (cb, n, i, f, v, fi)) \
|
|
X(DrawIndexedIndirect, (VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, uint32_t n, uint32_t s), (cb, b, o, n, s)) \
|
|
X(EndQuery, (VkCommandBuffer cb, VkQueryPool p, uint32_t q), (cb, p, q)) \
|
|
X(EndRendering, (VkCommandBuffer cb), (cb)) \
|
|
X(FillBuffer, (VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, VkDeviceSize n, uint32_t v), (cb, b, o, n, v)) \
|
|
X(PipelineBarrier2, (VkCommandBuffer cb, const VkDependencyInfo *di), (cb, di)) \
|
|
X(ExecuteCommands, (VkCommandBuffer cb, uint32_t n, const VkCommandBuffer *c), (cb, n, c)) \
|
|
X(PushConstants, (VkCommandBuffer cb, VkPipelineLayout l, VkShaderStageFlags st, uint32_t o, uint32_t n, \
|
|
const void *v), (cb, l, st, o, n, v)) \
|
|
X(ResetQueryPool, (VkCommandBuffer cb, VkQueryPool p, uint32_t f, uint32_t n), (cb, p, f, n)) \
|
|
X(ResolveImage, (VkCommandBuffer cb, VkImage s, VkImageLayout sl, VkImage d, VkImageLayout dl, uint32_t n, \
|
|
const VkImageResolve *r), (cb, s, sl, d, dl, n, r)) \
|
|
X(SetBlendConstants, (VkCommandBuffer cb, const float c[4]), (cb, c)) \
|
|
X(SetCullMode, (VkCommandBuffer cb, VkCullModeFlags m), (cb, m)) \
|
|
X(SetDepthBias, (VkCommandBuffer cb, float c, float cl, float s), (cb, c, cl, s)) \
|
|
X(SetDepthBiasEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetDepthCompareOp, (VkCommandBuffer cb, VkCompareOp v), (cb, v)) \
|
|
X(SetDepthTestEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetDepthWriteEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetFrontFace, (VkCommandBuffer cb, VkFrontFace v), (cb, v)) \
|
|
X(SetPrimitiveRestartEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetPrimitiveTopology, (VkCommandBuffer cb, VkPrimitiveTopology v), (cb, v)) \
|
|
X(SetRasterizerDiscardEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetScissorWithCount, (VkCommandBuffer cb, uint32_t n, const VkRect2D *r), (cb, n, r)) \
|
|
X(SetStencilCompareMask, (VkCommandBuffer cb, VkStencilFaceFlags f, uint32_t v), (cb, f, v)) \
|
|
X(SetStencilOp, (VkCommandBuffer cb, VkStencilFaceFlags f, VkStencilOp a, VkStencilOp b, VkStencilOp c, \
|
|
VkCompareOp d), (cb, f, a, b, c, d)) \
|
|
X(SetStencilReference, (VkCommandBuffer cb, VkStencilFaceFlags f, uint32_t v), (cb, f, v)) \
|
|
X(SetStencilTestEnable, (VkCommandBuffer cb, VkBool32 v), (cb, v)) \
|
|
X(SetStencilWriteMask, (VkCommandBuffer cb, VkStencilFaceFlags f, uint32_t v), (cb, f, v)) \
|
|
X(SetViewportWithCount, (VkCommandBuffer cb, uint32_t n, const VkViewport *v), (cb, n, v)) \
|
|
X(WriteTimestamp, (VkCommandBuffer cb, VkPipelineStageFlagBits s, VkQueryPool p, uint32_t q), (cb, s, p, q)) \
|
|
X(WriteTimestamp2, (VkCommandBuffer cb, VkPipelineStageFlags2 s, VkQueryPool p, uint32_t q), (cb, s, p, q))
|
|
|
|
#define REC_DECL(name, params, args) void rc_vkCmd##name params;
|
|
REC_CMDS(REC_DECL)
|
|
#undef REC_DECL
|
|
|
|
#ifndef VK_REC_IMPL
|
|
#define vkCmdBeginQuery rc_vkCmdBeginQuery
|
|
#define vkCmdBeginRendering rc_vkCmdBeginRendering
|
|
#define vkCmdBindDescriptorSets rc_vkCmdBindDescriptorSets
|
|
#define vkCmdBindIndexBuffer rc_vkCmdBindIndexBuffer
|
|
#define vkCmdBindPipeline rc_vkCmdBindPipeline
|
|
#define vkCmdBindVertexBuffers2 rc_vkCmdBindVertexBuffers2
|
|
#define vkCmdBlitImage rc_vkCmdBlitImage
|
|
#define vkCmdExecuteCommands rc_vkCmdExecuteCommands
|
|
#define vkCmdClearAttachments rc_vkCmdClearAttachments
|
|
#define vkCmdClearColorImage rc_vkCmdClearColorImage
|
|
#define vkCmdClearDepthStencilImage rc_vkCmdClearDepthStencilImage
|
|
#define vkCmdCopyBuffer rc_vkCmdCopyBuffer
|
|
#define vkCmdCopyBufferToImage rc_vkCmdCopyBufferToImage
|
|
#define vkCmdCopyImage rc_vkCmdCopyImage
|
|
#define vkCmdCopyImageToBuffer rc_vkCmdCopyImageToBuffer
|
|
#define vkCmdDispatch rc_vkCmdDispatch
|
|
#define vkCmdDispatchIndirect rc_vkCmdDispatchIndirect
|
|
#define vkCmdDraw rc_vkCmdDraw
|
|
#define vkCmdDrawIndexed rc_vkCmdDrawIndexed
|
|
#define vkCmdDrawIndexedIndirect rc_vkCmdDrawIndexedIndirect
|
|
#define vkCmdEndQuery rc_vkCmdEndQuery
|
|
#define vkCmdEndRendering rc_vkCmdEndRendering
|
|
#define vkCmdFillBuffer rc_vkCmdFillBuffer
|
|
#define vkCmdPipelineBarrier2 rc_vkCmdPipelineBarrier2
|
|
#define vkCmdPushConstants rc_vkCmdPushConstants
|
|
#define vkCmdResetQueryPool rc_vkCmdResetQueryPool
|
|
#define vkCmdResolveImage rc_vkCmdResolveImage
|
|
#define vkCmdSetBlendConstants rc_vkCmdSetBlendConstants
|
|
#define vkCmdSetCullMode rc_vkCmdSetCullMode
|
|
#define vkCmdSetDepthBias rc_vkCmdSetDepthBias
|
|
#define vkCmdSetDepthBiasEnable rc_vkCmdSetDepthBiasEnable
|
|
#define vkCmdSetDepthCompareOp rc_vkCmdSetDepthCompareOp
|
|
#define vkCmdSetDepthTestEnable rc_vkCmdSetDepthTestEnable
|
|
#define vkCmdSetDepthWriteEnable rc_vkCmdSetDepthWriteEnable
|
|
#define vkCmdSetFrontFace rc_vkCmdSetFrontFace
|
|
#define vkCmdSetPrimitiveRestartEnable rc_vkCmdSetPrimitiveRestartEnable
|
|
#define vkCmdSetPrimitiveTopology rc_vkCmdSetPrimitiveTopology
|
|
#define vkCmdSetRasterizerDiscardEnable rc_vkCmdSetRasterizerDiscardEnable
|
|
#define vkCmdSetScissorWithCount rc_vkCmdSetScissorWithCount
|
|
#define vkCmdSetStencilCompareMask rc_vkCmdSetStencilCompareMask
|
|
#define vkCmdSetStencilOp rc_vkCmdSetStencilOp
|
|
#define vkCmdSetStencilReference rc_vkCmdSetStencilReference
|
|
#define vkCmdSetStencilTestEnable rc_vkCmdSetStencilTestEnable
|
|
#define vkCmdSetStencilWriteMask rc_vkCmdSetStencilWriteMask
|
|
#define vkCmdSetViewportWithCount rc_vkCmdSetViewportWithCount
|
|
#define vkCmdWriteTimestamp rc_vkCmdWriteTimestamp
|
|
#define vkCmdWriteTimestamp2 rc_vkCmdWriteTimestamp2
|
|
#endif
|