// Internal header of the NVN front end, shared by nvn_api_core.c and the other // slices (textures, samplers, programs, windows, state objects, command buffers). Adapted from MacNativeGames games/totk/third_party/d2r_gfx/gfx/nvn/hle_nvn.c and nvn_impl.h @ b75be8cf // (MIT, Copyright (c) 2026 jhaizhou; origin d2r-mobile). #pragma once #include #include "gfx.h" #include "host.h" #include "nvn_obj.h" // NVN_API(nvnX) { body }: the strong nvnp_nvnX(Cpu *c); counts the call in gfx_counters, TOTK_TRACE=gfx logs the first. #define NVN_API(n) \ static void nvn_impl_##n(Cpu *c); \ void nvnp_##n(Cpu *c) { \ static uint8_t seen; \ nvn_called(&seen, #n); \ nvn_impl_##n(c); \ } \ static void nvn_impl_##n(Cpu *c) void nvn_called(uint8_t *seen, const char *name); // the counting prologue of NVN_API #define NVN_LOG(...) do { if (rt_trace & RT_TRACE_GFX) rt_log("nvn: " __VA_ARGS__); } while (0) // TOTK_TRACE=gfx // A wait for the GPU, the display or another thread gives the virtual core up until the end of the enclosing scope. #define NVN_BLOCKING() __attribute__((cleanup(kern_core_back))) int nvn_away_ = kern_core_away() static inline int nvn_arg_bool(const Cpu *c, int i) { return (int)(arg_x(c, i) & 0xff); } // NVNboolean: low 8 bits only #define NVN_POOL_MAGIC MAGIC('P', 'O', 'O', 'L') // pool_t.magic of an initialized pool #define NVN_VPOOL_PAGE 0x10000ull // NVN virtual mapping granularity (64 KiB) static inline int nvn_gpu_only(uint64_t a) { return a - RT_GPU_VA_BASE < RT_GPU_VA_SIZE; } // address in the GPU window // A CPU-invisible physical or virtual pool lives in the GPU window: mem is its GPU address (never mapped on the host), // game_mem the game's storage (never touched), span the reserved window bytes, host NULL. Every other pool: mem is the // game's storage, the GPU address equals the guest address. typedef struct { NVNdevice *dev; pool_t *pool; int64_t off; uint64_t size; } buf_builder_t; FITS(buf_builder_t, NVNbufferBuilder); typedef struct { uint32_t magic; pool_t *pool; int64_t off; uint64_t size; } buf_t; FITS(buf_t, NVNbuffer); typedef struct { // NVNmappingRequest as main passes it (0x28 bytes) const NVNmemoryPool *physical_pool; // NULL unmaps int64_t physical_offset, virtual_offset; uint64_t size; int32_t storage_class; uint32_t padding; } nvn_mapping_t; _Static_assert(sizeof(nvn_mapping_t) == 0x28, "NVNmappingRequest stride"); const pool_t *nvn_pool_find(uint64_t addr, uint64_t len); // GPU-visible pool holding [addr, addr+len], NULL if none uint64_t nvn_new_serial(void); // next tex_t / prog_t serial, from 1 // TOTK_SYNC_AHEAD (nvn_api_core.c): nvnQueuePresentTexture notes the newest fence at each present; the newest // previous-frame value the game was told is done although it may not be (0: none). The back end's shadow copies // wait for it or are queued for the GPU (vk_sync.c prologue), so the GPU never reads CPU writes the game made under // that assumption too early. void nvn_sync_ahead_present(void); uint64_t nvn_sync_ahead_guard(void); int vk_mem_shadow_on(void); // vk_memory.c: 1 with shadow storage on (valid once the Vulkan back end is up) void vk_mem_shadow_fence(void); // vk_memory.c: returns once every shadow sync that read the old guard is done void gfx_replay_wait_value(uint64_t v); // vk_backend.c: the replay thread submitted value v uint64_t nvn_fence_last(void); // fence value of the newest submit (0 before the first) int nvn_depth_minus_w(void); // 1 after DeviceSetDepthMode(NEAR_IS_MINUS_W), else 0 void nvn_guest_written(uint64_t addr, uint64_t size); // the host or the game wrote pool storage: back end re-reads // TOTK_TRACE=perf: MemoryPoolFlushMappedRange [0] and BufferFlushMappedRange [1] calls [0] and bytes [1] by the pool's // CPU class (0 CPU_CACHED, 1 CPU_UNCACHED, 2 other) since the previous call. void nvn_flush_stats(uint64_t out[2][3][2]); void nvn_submit_prepare(const uint64_t *handles, int n); // weak no-op; the command-buffer slice reads deferred binds // Asynchronous replay: as nvn_submit_prepare, writing the handles to replay to out (a clone where a still queued // submission uses the same recording with other deferred binds) void nvn_submit_prepare_async(const uint64_t *handles, uint64_t *out, int n); int gfx_replay_async(void); // 1: the back end replays submissions on its own thread uint64_t gfx_replayed_submits(void); // newest submit number (nvn_submit_count) replayed #define NVN_HANDLE_TEX(h) ((int)((h) & 0xfffff)) // combined texture handle = texture id | sampler id << 20 #define NVN_HANDLE_SAMP(h) ((int)(((h) >> 20) & 0xfff)) extern GfxStats gfx_counters; // behind gfx_stats(); add with __atomic_fetch_add. frames: // the back end's present; nvn_calls/nvn_distinct: NVN_API // ---- nvn_api_obj.c: textures, samplers, programs, windows #define NVN_TEX_MAGIC MAGIC('T', 'E', 'X', 0) // tex_t.magic of an initialized texture #define NVN_SAMP_MAGIC MAGIC('S', 'M', 'P', 0) // samp_t.magic #define NVN_PROG_MAGIC MAGIC('P', 'R', 'O', 'G') // prog_t.magic #define NVN_WIN_MAGIC MAGIC('W', 'I', 'N', 'D') // win_t.magic // The live texture whose storage starts at mem, copied into *out; 0 if none. The game re-initializes transient textures // at the same pool memory without registering them again, so a registered serial that was finalized since resolves here. int nvn_tex_live_at(const void *mem, tex_t *out); // Byte offset of a view's first level and layer inside the texture storage (nvnTextureGetViewOffset); v may be NULL. uint64_t nvn_tex_view_offset(const tex_t *t, const view_t *v); // ---- nvn_api_cmd.c, nvn_ext.c: command buffers and their replay contract // A command handle (what QueueSubmitCommands receives) points at a host copy of one recording: a flat sequence of // 8-byte-aligned records {op_hdr, payload}, OP_BEGIN first and OP_END last. Called recordings are inlined, so a copy // holds no OP_CALL or OP_JUMP. Texture pointers in payloads (p_set_rt, p_copy_b2t, p_copy_t2t, p_clear_tex) are // immutable host copies ("pins"), one per tex_t.serial, that live while a recording copy names them and a while after // (nvn_pins_sweep). Deferred binds (p_bind_handles.src != 0) hold the // handles read at submit (nvn_submit_prepare). Payloads of the ops nvn_obj.h does not define: typedef struct { volatile uint32_t *val; uint32_t mode, value; } p_signal_event; // OP_SIGNAL_EVENT; mode: NVNeventSignalMode typedef struct { uint64_t type, addr; } p_report_counter; // OP_REPORT_COUNTER: NVNcounterType, NVNcounterData address typedef struct { uint64_t type; } p_reset_counter; // OP_RESET_COUNTER typedef struct { uint64_t src, dst, size; } p_copy_b2b; // OP_COPY_B2B: GPU addresses typedef struct { uint64_t dst, size; uint8_t data[]; } p_update_ub; // OP_UPDATE_UB: size bytes written at dst typedef struct { int32_t bits, pad; } p_barrier; // OP_BARRIER: NVNbarrierBits typedef struct { uint64_t target; } p_jump; // OP_JUMP / OP_CALL: next record / called recording // OP_DRAW_IDX_IND: DrawElementsIndirect; ind = GPU address of {count, instances, first index, base vertex, base // instance} (VkDrawIndexedIndirectCommand's layout), read by the GPU when the draw runs typedef struct { int32_t prim, itype; uint64_t ibuf, ind; } p_draw_idx_ind; typedef struct { uint64_t ind, caller; } p_dispatch_ind; // OP_DISPATCH_IND: ind = GPU address of the 3 group counts // OP_CLEAR_BUF: size bytes at addr filled with the 32-bit value in GPU order (ClearBuffer; ReportValue is a 4-byte one) typedef struct { uint64_t addr, size; uint32_t value, pad; } p_clear_buf; _Static_assert(sizeof(p_signal_event) == 16 && sizeof(p_report_counter) == 16 && sizeof(p_copy_b2b) == 24 && sizeof(p_update_ub) == 16, "op payload layouts of hle_nvn.c"); static inline const op_hdr *nvn_op_first(uint64_t handle) { return (const op_hdr *)(uintptr_t)handle; } // OP_BEGIN static inline const op_hdr *nvn_op_next(const op_hdr *h) { return (const op_hdr *)(const void *)((const uint8_t *)h + h->size); } static inline const void *nvn_op_payload(const op_hdr *h) { return h + 1; } int nvn_handle_live(uint64_t handle); // 1 while the handle's copy exists (its control memory was // not added again) uint64_t nvn_handle_bytes(uint64_t handle); // bytes of the copy, OP_BEGIN to OP_END; 0 if not live uint64_t nvn_submit_count(void); // QueueSubmitCommands calls so far // Submits a texture pin outlives its last tex_pin and the release of the last recording copy naming it: about 20 s at // one submit a frame (MacNativeGames frees after two 600-submit windows). #define NVN_PIN_AGE 1200 // Frees the texture pins no recording copy has named for NVN_PIN_AGE submits (nvn_api_cmd.c, "texture pins"), except keep[0..nkeep): // the replay's current render targets, which carry over to the next submission. Called between submissions by the // thread that replays them; now = nvn_submit_count(). void nvn_pins_sweep(uint64_t now, const tex_t *const *keep, int nkeep); void nvn_pins_stats(uint64_t *live, uint64_t *freed); // pins that exist, pins freed so far const char *nvn_op_name(uint32_t op); // "DRAW_IDX", ...; "?" past OP_COUNT_ // MemoryPoolInvalidateMappedRange: the CPU is about to read pool storage the GPU wrote. Weak no-op in nvn_ext.c; a back // end that renders into host-side copies defines it and writes them back. void nvn_pool_invalidate(const pool_t *p, uint64_t off, uint64_t size); // MemoryPoolFlushMappedRange: the CPU wrote [mem + off, mem + off + size). Weak no-op in nvn_ext.c; a back end that keeps // device-side copies of pool memory (vk_memory.c's VRAM mirror) defines it and copies the range again. void nvn_pool_flushed(const pool_t *p, uint64_t off, uint64_t size);