Files
totk-pc-recomp/runtime/vk_exec.h
T
Claude 9069c9208f Keep uniform updates inside a pass as scratch overlays and copy them at the pass end
nvnCommandBufferUpdateUniformBuffer between draws was replayed as a buffer copy at that point, which needs a barrier
against the draws that read the old bytes, and a barrier ends the open pass. The terrain and the sky islands' meshes
update before most of their draws: about 200 updates inside passes in a field submission, each a pass end, a full
barrier and a pass begin.

An update inside a pass now goes to the scratch buffer only. Later bindings of a range it covers bind the scratch
bytes (ub_bind, from ubo_info and bind_range); a binding it covers only in part, or at an offset the alignment
refuses, gets a merged copy of the snapshot or guest memory with the submission's updates on top. The copies into the
real buffers are recorded behind one barrier of their own at the next full barrier (vk_exec_ub_flush from
barrier_to). A new update of a range replaces the older overlay and pending copy of it, so the lists stay as short as
the blocks. Copies, fills and storage writes of the same bytes retire the overlays they touch, and a storage binding
over a pending copy brings the copies forward with a pass restart. bind_sig hashes the overlay each of the draw's
uniform and storage bindings reads, so a draw of an updated block binds the newest bytes and the others still skip
their descriptor build. TOTK_UB_OVERLAY=0 copies at once as before; ab alternates; TOTK_TRACE=perf prints the
counts.

1.4.3, RTX 4070 Ti, 2560x1440 DLAA, standing still on Great Sky Island, TOTK_AB=10 (tools/ab_summary.py):
17.03 ms a frame with the copies at once, 14.39 ms with the overlays (-15.5%, 58.7 to 69.5 fps); replay time on the
CPU 8.81 to 8.30 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
2026-11-04 23:54:02 +00:00

209 lines
15 KiB
C

// The op-stream replay of the Vulkan back end. vk_exec.c walks the command
// handles of a submission, keeps render targets, passes, clears and fixed-function state; draws, binds and dispatches
// (vk_exec_draw.c) and copies, counters, events and conditional rendering (vk_exec_xfer.c) plug in through the two
// hooks at the bottom.
// Runs on the submitting thread under vk_sync_submit's lock: no locking inside.
// The op loop follows MacNativeGames nvn_mtl.m (MIT, Copyright (c) 2026 jhaizhou) and the pass handling LAS Recomp
// (MIT, Copyright (c) 2026 Peiikai); vk_exec.c names the sources.
#pragma once
#include <stdint.h>
#include "nvn_api.h"
#include "vk.h"
// Dynamic state the next draw must record (VkExec.dirty). vk_exec_record sets them all at the start of each command
// buffer; the state ops set theirs; vk_exec_draw.c clears what it recorded.
enum {
VK_EXEC_DIRTY_VIEWPORT = 1 << 0, // viewports or depth range
VK_EXEC_DIRTY_SCISSOR = 1 << 1,
VK_EXEC_DIRTY_DEPTH_BIAS = 1 << 2, // polygon offset
VK_EXEC_DIRTY_BLEND_CONST = 1 << 3,
VK_EXEC_DIRTY_STENCIL = 1 << 4, // compare and write masks, references
VK_EXEC_DIRTY_ALL = (1 << 5) - 1,
};
// One render target of OP_SET_RT, resolved to its vk_texture.c image for the current command buffer.
typedef struct {
const tex_t *tex; // texture pin of ex->rt (nvn_pins_sweep keeps it); NULL: no target in this slot
VkImage image; // VK_NULL_HANDLE: no target or not renderable (logged once)
VkFormat format; // view format: the view's when it reinterprets a same-size format
VkImageViewType view_type; // 2D, or 2D_ARRAY for more than one layer
VkImageAspectFlags aspect;
uint32_t level, base_layer, layers; // subresource of the view; of a 3D image: depth planes
uint8_t is3d; // a 3D image (colour only), viewed through 2D views of its slices
uint8_t blocks; // a BC image viewed as one texel per block (VkTexImage.block_views)
uint32_t width, height, samples; // extent of that level; with blocks, in blocks
int32_t nvn_format; // NVNformat of the view (VkPipeState.color_fmt / depth_fmt)
const VkTexImage *img; // the image (fb_usage, fb_learned)
uint8_t scale; // VkTexImage.scale
} VkExecTarget;
// The replay state. One instance lives for the run: NVN state is queue state and carries over from one submission to
// the next, as in MacNativeGames' and LAS Recomp's replays. The "per command buffer" fields are reset by vk_exec_record.
typedef struct VkExec {
// ---- per command buffer
VkCommandBuffer cb; // being recorded
uint64_t fence; // timeline value the submission signals (vk_completion_add)
int recorded; // Vulkan commands recorded so far; every handler adds the ones it records
int in_pass; // inside vkCmdBeginRendering
uint32_t dirty; // VK_EXEC_DIRTY_*
// Pass marks (vk.h vk_pass_mark_begin): the open pass's record (NULL: none or marks off), vk_exec_frame.draws when
// it began, and the passes and dispatches this submission marked so far.
VkPassMark *mark;
uint64_t mark_draws0;
uint32_t mark_passes, mark_dispatches;
// ---- render targets (OP_SET_RT) and the current pass
p_set_rt rt; // last OP_SET_RT payload (a change ends the open pass); rt.n 0, no depth: none
int rt_resolved; // color[] and depth hold this command buffer's images for rt
VkExecTarget color[8], depth; // resolved targets
uint32_t pass_w, pass_h, pass_layers; // render area and layer count of the open (or next) pass
uint32_t pass_scale; // its targets' VkTexImage.scale: game viewports and scissors
// are multiplied by it
int pass_noatt; // the pass has no attachment: its size follows the draws (vk_exec_noatt_dims)
VkFormat pass_color[8], pass_depth, pass_stencil; // attachment formats of the pass (VkPipelineRenderingCreateInfo);
// VK_FORMAT_UNDEFINED: none
uint32_t pass_ncolor, pass_samples; // colorAttachmentCount and rasterization samples of the pass
// ---- fixed-function state (vk_exec.c keeps it; vk_exec_draw.c records it before draws)
float vp[16][6]; // OP_VIEWPORTS: x, y, w, h, near, far
int has_vp; // a viewport was set; else the render area
p_scissor scissor; // OP_SCISSOR (scissor 0)
int has_scissor; // a scissor was set; else unbounded
float depth_range[2]; // OP_DEPTH_RANGE: overrides the viewports' near and far
uint32_t st_vmask[2], st_wmask[2], st_ref[2]; // OP_STENCIL per face (0 front, 1 back): compare mask, write mask, ref
float point_size; // OP_BIND_STATE kind 6: NVN point size for the support buffer; 0 = never set = 1
p_poly_offset poly_offset; // OP_POLY_OFFSET: depth bias factor, units, clamp
p_prim_restart prim_restart; // OP_PRIM_RESTART: applied to indexed strip and fan draws (op_draw_idx)
float blend_color[4]; // OP_BLEND_COLOR
int64_t copy_stride; // OP_COPY_STRIDE: row stride of buffer <-> texture copies (vk_exec_xfer.c)
const dpool_t *tex_pool, *samp_pool; // OP_SET_TEX_POOL, OP_SET_SAMP_POOL
// Pipeline inputs. vk_exec.c writes depth_clamp (OP_DEPTH_CLAMP), raster_discard (OP_RASTER_DISCARD) and, at pass
// begin, color_fmt, depth_fmt and samples; vk_exec_draw.c owns every other field (shaders, prim, y_flip, state
// objects, vertex input).
VkPipeState pipe;
// ---- vk_exec_draw.c slots (draws, binds, dispatch): left empty by vk_exec.c
const prog_t *prog; // OP_BIND_PROGRAM
int32_t prog_stages;
NVNbufferRange vbuf[16]; // OP_BIND_VBUF
p_bind_buf ubo[6][18], ssbo[6][16]; // OP_BIND_UBO / OP_BIND_SSBO per stage and index
uint64_t tex[6][128], samp[6][128], img[6][32]; // OP_BIND_TEX / OP_BIND_SAMP / OP_BIND_IMG handles per stage
// ---- vk_exec_xfer.c slots (render enable, conditional rendering): left empty by vk_exec.c
int render_disabled; // OP_RENDER_ENABLE false or a failed OP_RENDER_COND: clears and draws skip
p_render_cond render_cond; // last OP_RENDER_COND
int has_render_cond;
// ---- internal to vk_exec.c
void *views; // image views created for this command buffer (freed at its fence)
int nviews, cap_views;
int barrier_at; // recorded right after the last full barrier (-1: none yet)
// Feedback loops (vk.h VkCtx.feedback, vk_exec_draw.c feedback_prepare). Learned targets are attached in
// ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; they stay in it across consecutive passes and go back to GENERAL at the next
// full barrier that is not a pass begin.
VkImage fbl_img[9]; // images in the feedback layout now
VkImageAspectFlags fbl_aspect[9];
int nfbl;
uint32_t fbl_mask; // attachments of the open pass in that layout: bit i colour i, bit 8 depth
uint32_t fb_dirty; // attachments of the open pass written by a draw or clear since the pass began
// or the last in-pass barrier
const void *fb_fs; // feedback_scan's memo: fragment shader, texture bind serial, pass serial
uint32_t fb_serial, fb_pass, fb_sampled;
uint32_t tex_serial, pass_serial; // bumped by every texture or image bind and pass begin
} VkExec;
// Begins dynamic rendering with the current targets unless a pass is open (a full memory barrier precedes it). Call
// before a draw or an attachment clear.
void vk_exec_pass_begin(VkExec *ex);
// Ends the open pass (no-op without one) and marks its targets GPU-written. Call before copies and dispatches.
void vk_exec_pass_end(VkExec *ex);
// Full memory barrier (all commands, all memory); ends the open pass first. Copies and dispatches order themselves with
// it. Skipped when nothing was recorded since the last one.
void vk_exec_barrier(VkExec *ex);
// UpdateUniformBuffer overlays (vk_exec_draw.c): the copies into the real buffers that updates inside a pass left for
// the next barrier (barrier_to records them first), and a GPU write of another kind into [addr, addr+n) of this
// submission (a copy, a fill, a storage write), which retires the overlays it touches.
void vk_exec_ub_flush(VkExec *ex);
void vk_exec_ub_foreign(VkExec *ex, uint64_t addr, uint64_t n);
void vk_exec_ub_perf(void);
// vk_texture.c (TOTK_TEX_ALIAS): commands that must run at the replay's current position. vk_exec_inline_begin returns
// the command buffer being recorded after a full barrier (an open pass is ended; *reopen says so), or VK_NULL_HANDLE
// when this thread is not recording a replay. vk_exec_inline_end begins the pass again when it was open.
// vk_exec_inline_buffer: a device buffer for transfers, destroyed when the submission completes.
VkCommandBuffer vk_exec_inline_begin(int *reopen);
void vk_exec_inline_end(int reopen);
// vk_exec_draw.c: forget what the command buffer being recorded has bound (pipeline, sets, dynamic state, vertex
// buffers): the replay continues in a new one (vk_sync_split).
void vk_exec_draw_new_cb(void);
// vk_exec_draw.c: the vertex and fragment shader of the last draw recorded into this command buffer (NULL: none yet).
void vk_exec_draw_shaders(const GfxShader **vs, const GfxShader **fs);
VkBuffer vk_exec_inline_buffer(VkDeviceSize size);
// vk_exec.c: the stand-in a 3D BC render target is drawn through goes with its image (vk_exec_image_gone).
void vk_exec_bc_gone(VkImage image);
// vk_hdr.c: with TOTK_HDR=1, notes a draw into a presented window texture (vk_hdr_track: set by the first present).
extern int vk_hdr_track;
void vk_hdr_draw(VkExec *ex, const VkPipeInfo *info);
// Scissor 0 clipped to the render area of the open pass; extent 0: nothing to draw.
VkRect2D vk_exec_scissor(const VkExec *ex);
void vk_exec_noatt_dims(const VkExec *ex, uint32_t *w, uint32_t *h); // pass size when no attachment is set
// 1: every target of the current pass (any 1: at least one, and not only the depth target while a colour target is
// rendered for the first time) was rendered in the previous presented frame (per-frame targets); 0 otherwise, or when
// the pass has none.
int vk_exec_targets_recent(VkExec *ex, int any);
// The current pass's targets with their formats, extents and the frames they were last rendered in (diagnostics).
void vk_exec_targets_describe(VkExec *ex, char *buf, size_t cap);
// TOTK_TRACE=gfx: replay counters of the current frame, logged and reset at each present (vk_exec_frame_log).
typedef struct {
uint64_t passes, draws_seen, draws, clears, copies, dispatches, submits;
uint64_t fb_draws, fb_barriers, fb_restarts, fb_layouts; // feedback loops: draws sampling a written target of
// their pass, in-pass barriers, pass restarts, layout
// transitions
} VkExecFrame;
extern VkExecFrame vk_exec_frame;
void vk_exec_frame_written(const tex_t *t); // vk_texture_gpu_written: t rendered or copied into
uint64_t vk_exec_recording_fence(void); // fence of the submission this thread records, else 0
void vk_exec_frame_log(const tex_t *presented, uint64_t frame);
// TOTK_DUMP=<frame> (vk_dump.c): between presents frame-1 and frame, every pass's targets and every dispatch's storage
// images are copied out and written as PNGs to TOTK_DUMP_DIR (default "dump"), with passes.txt (targets, pipelines,
// textures, samplers) and images.txt (per image value statistics). The hooks run only when vk_dump_armed is set.
extern int vk_dump_armed;
void vk_dump_init(void); // reads the environment; vk_exec_record's first call
void vk_dump_pass_end(VkExec *ex); // after vkCmdEndRendering
void vk_dump_draw(VkExec *ex, const VkPipeState *s, VkPipeline p, const VkPipeInfo *info); // after a draw's binds
void vk_dump_dispatch(VkExec *ex, const GfxShader *cs, VkPipeline p, const VkPipeInfo *info, const p_dispatch *d);
void vk_dump_copy(VkExec *ex, uint32_t op, const void *payload); // before a copy or texture clear is replayed
void vk_dump_present(uint64_t frame); // vk_be_present: waits for the dump frame's copies and closes the logs
// c0, the NVN driver constant bank of one stage (vk_exec_draw.c; TOTK_FIX_C0=0 binds zeros instead): bank is
// VK_C0_BYTES, stage an NVN stage (0 vertex, 1 fragment, 5 compute), ssbo and ubo the 16 bound slots of that stage.
#define VK_C0_BYTES 0x800u
void vk_c0_fill(uint8_t bank[VK_C0_BYTES], int stage, const p_bind_buf ssbo[16], const p_bind_buf ubo[16],
const uint32_t dispatch[3], uint32_t base_instance);
// Handler hooks. vk_exec.c defines both WEAK: they stop with rt_fatal naming the op.
// vk_exec_draw.c: OP_DRAW, OP_DRAW_IDX, OP_DISPATCH, OP_BIND_* (program, state, vattr, vstream, vbuf, ubo, ssbo, tex,
// samp, img), OP_UPDATE_UB, OP_DRAW_IDX_IND, OP_DISPATCH_IND. payload: nvn_op_payload of the record.
void vk_exec_op_draw(VkExec *ex, uint32_t op, const void *payload);
// The aspect a sampled view of img reads (vk_exec_draw.c get_view): stencil for a view in STENCIL depth-stencil mode.
VkImageAspectFlags vk_exec_view_aspect(const VkTexImage *img, const view_t *v, int hv);
VkImageView vk_exec_target_view(const VkTexImage *img, VkFormat format, VkImageViewType type, uint32_t aspect,
uint32_t level, uint32_t base, uint32_t layers, int blocks); // kept for the image's life
// vk_exec_xfer.c: OP_COPY_B2B, OP_COPY_B2T, OP_COPY_T2B, OP_COPY_T2T, OP_CLEAR_TEX, OP_SIGNAL_EVENT, OP_REPORT_COUNTER,
// OP_RESET_COUNTER, OP_RENDER_ENABLE, OP_RENDER_COND, OP_CLEAR_BUF.
void vk_exec_op_xfer(VkExec *ex, uint32_t op, const void *payload);
// vk_dlaa.c: once DLSS has scaled the scene this frame, passes larger than the scene's light target vk_dlaa_swap_img
// sample DLSS's output vk_dlaa_swap_view in its place, so the game's scaling draw scales DLSS's picture.
extern const VkTexImage *vk_dlaa_swap_img;
extern VkImageView vk_dlaa_swap_view;
// vk_contact.c: a pass about to begin (its targets resolved, no pass open), and a dispatch or copy about to be recorded
void vk_contact_pass_begin(VkExec *ex);
void vk_contact_flush(VkExec *ex);
// vk_exec_xfer.c, samples-passed counting: a draw about to be recorded in the open pass (begins an occlusion query
// when counting and none is active), and the open pass about to end (ends the active query).
void vk_exec_oq_draw(VkExec *ex);
void vk_exec_oq_pass_end(VkExec *ex);