Files
NX2navi/runtime/nvn_ext.c
chrg6 b5c9a9b38e Answer NVN getters asked of no object with 0, and map a buffer with no pool to memory of its own.
The game's scene-loading thread makes a few NVN calls with a null object while an area loads or Kakariko's time of day
changes: a memory pool's flags (and the other pool getters), a texture builder's storage size or alignment, and the
mapping of a buffer that has no pool, into which it then writes 0x3d0 bytes. Each read the object at address 0 plus a
field's offset, an access violation. The pool getters, nvnMemoryPoolInvalidateMappedRange and the two builder queries
now answer 0 for a null object (nvn_no_object), and nvnBufferMap of a buffer with no pool returns a block of scratch
memory (64 KiB, or the buffer's size when it records a larger one) instead of 0. The first such calls are logged with
their caller. These fit the crash reports in Castle Town and in Kakariko before Death Mountain (#605, #602), which have
not been confirmed to be this.

Checked: --probe-nvn gains three checks (pool getters of no pool, builder queries of no builder, a 0x3d0-byte write
through the mapping of a buffer with no pool) and they pass; its four other failures are the same on the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 19:38:17 -05:00

436 lines
24 KiB
C

// NVN entry points beyond the core implementation that only report stored object state, attach debugging metadata,
// convert timestamps or take hardware hints. None of them changes rendering
// results. The *Deferred binds and memory-callback switches live with the command buffer in nvn_api_cmd.c.
// Contains code Copyright (c) 2026 jhaizhou (MIT; see THIRD_PARTY_NOTICES.md).
#include <stdint.h>
#include <string.h>
#include "gfx.h"
#include "host.h"
#include "nvn_api.h"
#include "nvn_tex.h"
// The CPU is about to read what the GPU wrote into pool storage: the back end writes its host-side copies (the Vulkan
// back end's images) back first. NX2navi invalidates its 260x260 R16 grass maps and 1x1 pick targets this way. A
// CPU-invisible pool has no storage to read.
void nvn_pool_invalidate(const pool_t *p, uint64_t off, uint64_t size) {
if (!p || !p->mem || nvn_gpu_only((uintptr_t)p->mem) || off >= p->size || !size) return;
if (size > p->size - off) size = p->size - off;
gfx_backend()->guest_read((uintptr_t)p->mem + off, size);
}
__attribute__((weak)) void nvn_pool_flushed(const pool_t *p, uint64_t off, uint64_t size) { (void)p, (void)off, (void)size; }
// ---------- debug labels, markers, groups, checkpoints: meaningful only to NVN debugging tools
// void nvn<Object>SetDebugLabel(NVN<object> *o, const char *label)
NVN_API(nvnDeviceSetDebugLabel) { (void)c; }
NVN_API(nvnQueueSetDebugLabel) { (void)c; }
NVN_API(nvnWindowSetDebugLabel) { (void)c; }
NVN_API(nvnProgramSetDebugLabel) { (void)c; }
NVN_API(nvnMemoryPoolSetDebugLabel) { (void)c; }
NVN_API(nvnTexturePoolSetDebugLabel) { (void)c; }
NVN_API(nvnSamplerPoolSetDebugLabel) { (void)c; }
NVN_API(nvnBufferSetDebugLabel) { (void)c; }
NVN_API(nvnTextureSetDebugLabel) { (void)c; }
NVN_API(nvnSamplerSetDebugLabel) { (void)c; }
NVN_API(nvnCommandBufferSetDebugLabel) { (void)c; }
NVN_API(nvnSyncSetDebugLabel) { (void)c; }
// void nvnCommandBufferPushDebugGroup{Static,Dynamic,}(NVNcommandBuffer *cmd, uint32_t domain_id, const char *text),
// PopDebugGroup(cmd), PopDebugGroupId(cmd, uint32_t id), InsertDebugMarker{Static,Dynamic}(cmd, uint32_t id,
// const char *text), InsertDebugMarker(cmd, const char *text), InsertCheckpoint{Static,Dynamic}(cmd, uint32_t id,
// const char *text)
NVN_API(nvnCommandBufferPushDebugGroupStatic) { (void)c; }
NVN_API(nvnCommandBufferPushDebugGroupDynamic) { (void)c; }
NVN_API(nvnCommandBufferPushDebugGroup) { (void)c; }
NVN_API(nvnCommandBufferPopDebugGroup) { (void)c; }
NVN_API(nvnCommandBufferPopDebugGroupId) { (void)c; }
NVN_API(nvnCommandBufferInsertDebugMarkerStatic) { (void)c; }
NVN_API(nvnCommandBufferInsertDebugMarkerDynamic) { (void)c; }
NVN_API(nvnCommandBufferInsertDebugMarker) { (void)c; }
NVN_API(nvnCommandBufferInsertCheckpointStatic) { (void)c; }
NVN_API(nvnCommandBufferInsertCheckpointDynamic) { (void)c; }
// NVNboolean nvnDeviceIsExternalDebuggerAttached(const NVNdevice *d)
NVN_API(nvnDeviceIsExternalDebuggerAttached) { ret_x(c, 0); }
// void nvnDeviceSetAutomaticCheckpointsMask(NVNdevice *d, uint32_t mask) (NVN 55, no prototype in nvn.h)
NVN_API(nvnDeviceSetAutomaticCheckpointsMask) { (void)c; }
// ---------- timestamps: counters are written in Tegra 19.2 MHz ticks, the base of GetSystemTick (rt_ticks)
static uint64_t ticks_to_ns(uint64_t t) { return t / 12 * 625 + t % 12 * 625 / 12; }
// uint64_t nvnDeviceGetCurrentTimestampInNanoseconds(const NVNdevice *d)
NVN_API(nvnDeviceGetCurrentTimestampInNanoseconds) { ret_x(c, ticks_to_ns(rt_ticks())); }
// uint64_t nvnDeviceGetTimestampInNanoseconds(const NVNdevice *d, const NVNcounterData *counter)
NVN_API(nvnDeviceGetTimestampInNanoseconds) { ret_x(c, ticks_to_ns(((const NVNcounterData *)arg_p(c, 1))->timestamp)); }
// ---------- memory pools and buffers
// size_t nvnMemoryPoolGetSize(const NVNmemoryPool *p)
NVN_API(nvnMemoryPoolGetSize) {
if (nvn_no_object(c, "nvnMemoryPoolGetSize")) return;
ret_x(c, ((const pool_t *)arg_p(c, 0))->size);
}
// void nvnMemoryPoolInvalidateMappedRange(const NVNmemoryPool *p, ptrdiff_t offset, size_t size)
NVN_API(nvnMemoryPoolInvalidateMappedRange) {
int64_t off = (int64_t)arg_x(c, 1);
if (off >= 0) nvn_pool_invalidate(arg_p(c, 0), (uint64_t)off, arg_x(c, 2));
}
// void nvnBufferInvalidateMappedRange(const NVNbuffer *b, ptrdiff_t offset, size_t size): buffers are pool storage,
// which the GPU writes directly
NVN_API(nvnBufferInvalidateMappedRange) { (void)c; }
// ---------- texture builder and texture queries
static const tex_builder_t *tb_of(const Cpu *c) { return arg_p(c, 0); }
static const tex_t *tex_of(const Cpu *c) { return arg_p(c, 0); }
// void ...GetSwizzle(const T *o, NVNtextureSwizzle *r, *g, *b, *a)
static void get_swizzle(const Cpu *c, const nvn_texdesc *d) {
*(int32_t *)arg_p(c, 1) = d->swizzle_r;
*(int32_t *)arg_p(c, 2) = d->swizzle_g;
*(int32_t *)arg_p(c, 3) = d->swizzle_b;
*(int32_t *)arg_p(c, 4) = d->swizzle_a;
}
NVN_API(nvnTextureBuilderGetDevice) { ret_x(c, (uintptr_t)tb_of(c)->dev); }
NVN_API(nvnTextureBuilderGetTarget) { ret_x(c, (uint32_t)tb_of(c)->d.target); }
NVN_API(nvnTextureBuilderGetWidth) { ret_x(c, tb_of(c)->d.width); }
NVN_API(nvnTextureBuilderGetHeight) { ret_x(c, tb_of(c)->d.height); }
NVN_API(nvnTextureBuilderGetDepth) { ret_x(c, tb_of(c)->d.depth); }
NVN_API(nvnTextureBuilderGetLevels) { ret_x(c, tb_of(c)->d.levels); }
NVN_API(nvnTextureBuilderGetFormat) { ret_x(c, (uint32_t)tb_of(c)->d.format); }
NVN_API(nvnTextureBuilderGetSamples) { ret_x(c, (uint32_t)tb_of(c)->d.samples); }
NVN_API(nvnTextureBuilderGetSwizzle) { get_swizzle(c, &tb_of(c)->d); }
NVN_API(nvnTextureBuilderGetDepthStencilMode) { ret_x(c, NVN_TEXTURE_DEPTH_STENCIL_MODE_DEPTH); }
NVN_API(nvnTextureBuilderGetStride) { ret_x(c, tb_of(c)->d.stride); }
NVN_API(nvnTextureBuilderGetMemoryPool) { ret_x(c, (uintptr_t)tb_of(c)->pool); }
NVN_API(nvnTextureBuilderGetMemoryOffset) { ret_x(c, (uint64_t)tb_of(c)->off); }
NVN_API(nvnTextureGetTarget) { ret_x(c, (uint32_t)tex_of(c)->d.target); }
NVN_API(nvnTextureGetLevels) { ret_x(c, tex_of(c)->d.levels); }
NVN_API(nvnTextureGetFormat) { ret_x(c, (uint32_t)tex_of(c)->d.format); }
NVN_API(nvnTextureGetSamples) { ret_x(c, (uint32_t)tex_of(c)->d.samples); }
NVN_API(nvnTextureGetSwizzle) { get_swizzle(c, &tex_of(c)->d); }
NVN_API(nvnTextureGetDepthStencilMode) { ret_x(c, NVN_TEXTURE_DEPTH_STENCIL_MODE_DEPTH); }
NVN_API(nvnTextureGetStride) { ret_x(c, tex_of(c)->d.stride); }
NVN_API(nvnTextureGetTextureAddress) { ret_x(c, (uintptr_t)tex_of(c)->mem); }
NVN_API(nvnTextureGetMemoryPool) { ret_x(c, (uintptr_t)tex_of(c)->pool); }
NVN_API(nvnTextureGetMemoryOffset) { ret_x(c, (uint64_t)tex_of(c)->off); }
NVN_API(nvnTextureGetDebugID) { ret_x(c, tex_of(c)->serial); }
// ptrdiff_t nvnTextureBuilderGetViewOffset(const NVNtextureBuilder *b, const NVNtextureView *view) (NVN 55)
NVN_API(nvnTextureBuilderGetViewOffset) {
const tex_builder_t *o = tb_of(c);
const view_t *w = arg_p(c, 1);
if (!w) { ret_x(c, 0); return; }
nvn_texlayout L;
nvn_tex_layout(&o->d, &L);
uint64_t off = w->set_levels ? nvn_tex_level_offset(&o->d, w->base_level) : 0;
if (w->set_layers) off += (uint64_t)w->min_layer * L.layer_size;
ret_x(c, off);
}
// size_t nvnTextureBuilderGetZCullStorageSize / nvnTextureGetZCullStorageSize: the depth-cull cache needs no storage
NVN_API(nvnTextureBuilderGetZCullStorageSize) { ret_x(c, 0); }
NVN_API(nvnTextureGetZCullStorageSize) { ret_x(c, 0); }
// ---------- window queries
static const win_builder_t *wb_of(const Cpu *c) { return arg_p(c, 0); }
static const win_t *win_of(const Cpu *c) { return arg_p(c, 0); }
NVN_API(nvnWindowBuilderGetDevice) { ret_x(c, (uintptr_t)wb_of(c)->dev); }
NVN_API(nvnWindowBuilderGetNativeWindow) { ret_x(c, (uintptr_t)wb_of(c)->native); }
NVN_API(nvnWindowBuilderGetPresentInterval) { ret_x(c, (uint32_t)wb_of(c)->interval); }
NVN_API(nvnWindowBuilderGetNumTextures) { ret_x(c, (uint32_t)wb_of(c)->ntex); }
// NVNtexture *nvnWindowBuilderGetTexture(const NVNwindowBuilder *b, int i)
NVN_API(nvnWindowBuilderGetTexture) {
const win_builder_t *b = wb_of(c);
int i = (int)arg_x(c, 1);
ret_x(c, i >= 0 && i < b->ntex && i < 4 ? (uintptr_t)b->tex[i] : 0);
}
NVN_API(nvnWindowGetNativeWindow) { ret_x(c, (uintptr_t)win_of(c)->native); }
NVN_API(nvnWindowGetPresentInterval) { ret_x(c, (uint32_t)win_of(c)->interval); }
NVN_API(nvnWindowGetNumTextures) { ret_x(c, (uint32_t)win_of(c)->ntex); }
// void nvnWindowGetCrop(const NVNwindow *w, NVNrectangle *rect): the default crop is the whole image
NVN_API(nvnWindowGetCrop) {
const win_t *o = win_of(c);
NVNrectangle *r = arg_p(c, 1);
if (!o->crop[2] && !o->crop[3] && o->ntex) {
const tex_t *t = (const tex_t *)o->tex[0];
*r = (NVNrectangle){0, 0, (int)t->d.width, (int)t->d.height};
return;
}
*r = (NVNrectangle){o->crop[0], o->crop[1], o->crop[2], o->crop[3]};
}
// ---------- texture views
static view_t *view_of(const Cpu *c, int i) { return arg_p(c, i); }
// void nvnTextureViewSetSwizzle(NVNtextureView *v, NVNtextureSwizzle r, g, b, a)
NVN_API(nvnTextureViewSetSwizzle) {
view_t *v = view_of(c, 0);
for (int i = 0; i < 4; i++) {
unsigned s = (unsigned)arg_x(c, 1 + i);
if (s > 5) rt_fatal("nvn: invalid texture view swizzle %u", s);
v->swz[i] = (uint8_t)s;
}
v->set_swizzle = 1;
}
// void nvnTextureViewSetDepthStencilMode(NVNtextureView *v, NVNtextureDepthStencilMode mode): STENCIL makes the replay
// sample a depth-stencil texture through a stencil-aspect view (vk_exec_draw.c get_view)
NVN_API(nvnTextureViewSetDepthStencilMode) {
uint32_t m = (uint32_t)arg_x(c, 1);
if (m > NVN_TEXTURE_DEPTH_STENCIL_MODE_STENCIL) rt_fatal("nvn: invalid texture view depth-stencil mode %u", m);
view_t *v = view_of(c, 0);
v->set_dsmode = 1;
v->dsmode = (uint8_t)m;
}
// NVNboolean nvnTextureViewGetLevels(const NVNtextureView *v, int *base, int *n)
NVN_API(nvnTextureViewGetLevels) {
const view_t *v = view_of(c, 0);
*(int32_t *)arg_p(c, 1) = v->base_level;
*(int32_t *)arg_p(c, 2) = v->nlevels;
ret_x(c, v->set_levels);
}
// NVNboolean nvnTextureViewGetFormat(const NVNtextureView *v, NVNformat *format)
NVN_API(nvnTextureViewGetFormat) {
const view_t *v = view_of(c, 0);
*(int32_t *)arg_p(c, 1) = v->format;
ret_x(c, v->set_format);
}
// NVNboolean nvnTextureViewGetTarget(const NVNtextureView *v, NVNtextureTarget *target)
NVN_API(nvnTextureViewGetTarget) {
const view_t *v = view_of(c, 0);
*(int32_t *)arg_p(c, 1) = v->target;
ret_x(c, v->set_target);
}
// NVNboolean nvnTextureViewGetSwizzle(const NVNtextureView *v, NVNtextureSwizzle *r, *g, *b, *a)
NVN_API(nvnTextureViewGetSwizzle) {
const view_t *v = view_of(c, 0);
for (int i = 0; i < 4; i++) *(int32_t *)arg_p(c, 1 + i) = v->swz[i];
ret_x(c, v->set_swizzle);
}
// NVNboolean nvnTextureViewGetDepthStencilMode(const NVNtextureView *v, NVNtextureDepthStencilMode *mode)
NVN_API(nvnTextureViewGetDepthStencilMode) {
const view_t *v = view_of(c, 0);
*(int32_t *)arg_p(c, 1) = v->dsmode;
ret_x(c, v->set_dsmode);
}
// NVNboolean nvnTextureViewCompare(const NVNtextureView *a, const NVNtextureView *b)
NVN_API(nvnTextureViewCompare) {
const view_t *x = view_of(c, 0), *y = view_of(c, 1);
int same = x->set_levels == y->set_levels && x->set_layers == y->set_layers && x->set_format == y->set_format &&
x->set_target == y->set_target && x->set_swizzle == y->set_swizzle && x->set_dsmode == y->set_dsmode;
if (same && x->set_levels) same = x->base_level == y->base_level && x->nlevels == y->nlevels;
if (same && x->set_layers) same = x->min_layer == y->min_layer && x->nlayers == y->nlayers;
if (same && x->set_format) same = x->format == y->format;
if (same && x->set_target) same = x->target == y->target;
if (same && x->set_swizzle) same = !memcmp(x->swz, y->swz, 4);
if (same && x->set_dsmode) same = x->dsmode == y->dsmode;
ret_x(c, same);
}
// ---------- sampler queries
static const samp_par *sbp_of(const Cpu *c) { return &((const samp_builder_t *)arg_p(c, 0))->p; }
static const samp_par *sp_of(const Cpu *c) { return &((const samp_t *)arg_p(c, 0))->p; }
// void nvnSamplerBuilderSetBorderColor{i,ui}(NVNsamplerBuilder *b, const int / uint32_t *rgba)
static void border_from_int(const Cpu *c, int is_signed) {
samp_par *p = &((samp_builder_t *)arg_p(c, 0))->p;
const int32_t *v = arg_p(c, 1);
for (int i = 0; i < 4; i++) p->border[i] = is_signed ? (float)v[i] : (float)(uint32_t)v[i];
}
NVN_API(nvnSamplerBuilderSetBorderColori) { border_from_int(c, 1); }
NVN_API(nvnSamplerBuilderSetBorderColorui) { border_from_int(c, 0); }
NVN_API(nvnSamplerBuilderGetDevice) { ret_x(c, (uintptr_t)((const samp_builder_t *)arg_p(c, 0))->dev); }
// Shared shapes: (o, float *lo, float *hi), (o, NVNcompareMode *, NVNcompareFunc *), (o, float / int / uint32_t *rgba)
static void lod_clamp(const Cpu *c, const samp_par *p) {
*(float *)arg_p(c, 1) = p->lod_min;
*(float *)arg_p(c, 2) = p->lod_max;
}
static void compare(const Cpu *c, const samp_par *p) {
*(int32_t *)arg_p(c, 1) = p->cmp_mode;
*(int32_t *)arg_p(c, 2) = p->cmp_func;
}
static void border_f(const Cpu *c, const samp_par *p) { memcpy(arg_p(c, 1), p->border, 16); }
static void border_i(const Cpu *c, const samp_par *p) {
for (int i = 0; i < 4; i++) ((int32_t *)arg_p(c, 1))[i] = (int32_t)p->border[i];
}
static void border_ui(const Cpu *c, const samp_par *p) {
for (int i = 0; i < 4; i++) ((uint32_t *)arg_p(c, 1))[i] = (uint32_t)p->border[i];
}
NVN_API(nvnSamplerBuilderGetLodClamp) { lod_clamp(c, sbp_of(c)); }
NVN_API(nvnSamplerBuilderGetLodBias) { ret_s(c, sbp_of(c)->lod_bias); }
NVN_API(nvnSamplerBuilderGetCompare) { compare(c, sbp_of(c)); }
NVN_API(nvnSamplerBuilderGetBorderColor) { border_f(c, sbp_of(c)); }
NVN_API(nvnSamplerBuilderGetBorderColori) { border_i(c, sbp_of(c)); }
NVN_API(nvnSamplerBuilderGetBorderColorui) { border_ui(c, sbp_of(c)); }
NVN_API(nvnSamplerBuilderGetMaxAnisotropy) { ret_s(c, sbp_of(c)->aniso); }
NVN_API(nvnSamplerBuilderGetLodSnap) { ret_s(c, sbp_of(c)->lod_snap); }
// void nvnSamplerGetMinMagFilter(const NVNsampler *s, NVNminFilter *min, NVNmagFilter *mag)
NVN_API(nvnSamplerGetMinMagFilter) {
const samp_par *p = sp_of(c);
*(int32_t *)arg_p(c, 1) = p->minf;
*(int32_t *)arg_p(c, 2) = p->magf;
}
// void nvnSamplerGetWrapMode(const NVNsampler *s, NVNwrapMode *s, *t, *r)
NVN_API(nvnSamplerGetWrapMode) {
const samp_par *p = sp_of(c);
*(int32_t *)arg_p(c, 1) = p->ws;
*(int32_t *)arg_p(c, 2) = p->wt;
*(int32_t *)arg_p(c, 3) = p->wr;
}
NVN_API(nvnSamplerGetLodClamp) { lod_clamp(c, sp_of(c)); }
NVN_API(nvnSamplerGetLodBias) { ret_s(c, sp_of(c)->lod_bias); }
NVN_API(nvnSamplerGetCompare) { compare(c, sp_of(c)); }
NVN_API(nvnSamplerGetBorderColor) { border_f(c, sp_of(c)); }
NVN_API(nvnSamplerGetBorderColori) { border_i(c, sp_of(c)); }
NVN_API(nvnSamplerGetBorderColorui) { border_ui(c, sp_of(c)); }
NVN_API(nvnSamplerGetMaxAnisotropy) { ret_s(c, sp_of(c)->aniso); }
// NVNboolean nvnSamplerCompare(const NVNsampler *a, const NVNsampler *b)
NVN_API(nvnSamplerCompare) { ret_x(c, !memcmp(sp_of(c), &((const samp_t *)arg_p(c, 1))->p, sizeof(samp_par))); }
// uint64_t nvnSamplerGetDebugID(const NVNsampler *s)
NVN_API(nvnSamplerGetDebugID) { ret_x(c, arg_x(c, 0)); }
// ---------- fixed-function state objects: setters without a host equivalent store the value (the replay reports any
// non-default use); getters return what was set
#define ST(T) ((T *)arg_p(c, 0))
// void nvnColorStateSetLogicOp(NVNcolorState *s, NVNlogicOp op) / SetAlphaTest(NVNcolorState *s, NVNalphaFunc f)
NVN_API(nvnColorStateSetLogicOp) {
unsigned op = (unsigned)arg_x(c, 1);
if (op > 15) rt_fatal("nvn: logic op %u", op);
ST(color_st)->logic_op = op;
}
NVN_API(nvnColorStateSetAlphaTest) {
unsigned f = (unsigned)arg_x(c, 1);
if (f > 15) rt_fatal("nvn: alpha func %u", f);
ST(color_st)->alpha_func = f;
// the replay does not apply the test (vk_pipeline.c): say once that the game asks for one
if (f && f != NVN_ALPHA_FUNC_ALWAYS) {
static int once_;
if (!once_++) rt_log("nvn: SetAlphaTest %u: the alpha test is not applied", f);
}
}
// NVNboolean nvnColorStateGetBlendEnable(const NVNcolorState *s, int index)
NVN_API(nvnColorStateGetBlendEnable) {
int i = (int)arg_x(c, 1);
ret_x(c, i >= 0 && i < 8 && (ST(color_st)->blend_en >> i & 1));
}
NVN_API(nvnColorStateGetLogicOp) { ret_x(c, ST(color_st)->logic_op); }
NVN_API(nvnColorStateGetAlphaTest) { ret_x(c, ST(color_st)->alpha_func); }
// void nvnChannelMaskStateGetChannelMask(const NVNchannelMaskState *s, int index, NVNboolean *r, *g, *b, *a)
NVN_API(nvnChannelMaskStateGetChannelMask) {
int i = (int)arg_x(c, 1);
uint32_t m = i >= 0 && i < 8 ? ST(chmask_st)->mask >> (4 * i) : 0;
for (int k = 0; k < 4; k++) *(NVNboolean *)arg_p(c, 2 + k) = (NVNboolean)(m >> k & 1);
}
NVN_API(nvnBlendStateGetBlendTarget) { ret_x(c, ST(blend_st)->target); }
// void nvnBlendStateGetBlendFunc(const NVNblendState *s, NVNblendFunc *src_rgb, *dst_rgb, *src_a, *dst_a)
NVN_API(nvnBlendStateGetBlendFunc) {
const blend_st *b = ST(blend_st);
*(int32_t *)arg_p(c, 1) = b->src_rgb;
*(int32_t *)arg_p(c, 2) = b->dst_rgb;
*(int32_t *)arg_p(c, 3) = b->src_a;
*(int32_t *)arg_p(c, 4) = b->dst_a;
}
// void nvnBlendStateGetBlendEquation(const NVNblendState *s, NVNblendEquation *rgb, NVNblendEquation *alpha)
NVN_API(nvnBlendStateGetBlendEquation) {
*(int32_t *)arg_p(c, 1) = ST(blend_st)->eq_rgb;
*(int32_t *)arg_p(c, 2) = ST(blend_st)->eq_a;
}
// void nvnBlendStateSetAdvancedMode(NVNblendState *s, NVNblendAdvancedMode mode): only "none" is implemented; overlap,
// premultiplied source and normalized destination only matter with an advanced mode
NVN_API(nvnBlendStateSetAdvancedMode) {
unsigned m = (unsigned)arg_x(c, 1);
if (m) rt_fatal("nvn: advanced blend mode %u is not implemented", m);
}
NVN_API(nvnBlendStateSetAdvancedOverlap) { (void)c; }
NVN_API(nvnBlendStateSetAdvancedPremultipliedSrc) { (void)c; }
NVN_API(nvnBlendStateSetAdvancedNormalizedDst) { (void)c; }
NVN_API(nvnMultisampleStateGetMultisampleEnable) { ret_x(c, ST(ms_st)->enable); }
NVN_API(nvnMultisampleStateGetSamples) { ret_x(c, ST(ms_st)->samples); }
NVN_API(nvnMultisampleStateGetAlphaToCoverageEnable) { ret_x(c, ST(ms_st)->a2c); }
NVN_API(nvnMultisampleStateGetRasterSamples) { ret_x(c, ST(ms_st)->raster_samples); }
// void nvnMultisampleStateSetRasterSamples(NVNmultisampleState *s, int n)
NVN_API(nvnMultisampleStateSetRasterSamples) {
int n = (int)arg_x(c, 1);
if (n < 0 || n > 16) rt_fatal("nvn: raster samples %d", n);
ST(ms_st)->raster_samples = (uint8_t)n;
}
// void nvnMultisampleStateSetAlphaToCoverageDither(NVNmultisampleState *s, NVNboolean enable)
NVN_API(nvnMultisampleStateSetAlphaToCoverageDither) { ST(ms_st)->a2c_dither = nvn_arg_bool(c, 1) != 0; }
NVN_API(nvnPolygonStateGetCullFace) { ret_x(c, ST(poly_st)->cull); }
NVN_API(nvnPolygonStateGetFrontFace) { ret_x(c, ST(poly_st)->front); }
NVN_API(nvnPolygonStateGetPolygonMode) { ret_x(c, ST(poly_st)->mode); }
NVN_API(nvnPolygonStateGetPolygonOffsetEnables) { ret_x(c, ST(poly_st)->offset_en); }
NVN_API(nvnDepthStencilStateGetDepthTestEnable) { ret_x(c, ST(ds_st)->dt); }
NVN_API(nvnDepthStencilStateGetDepthWriteEnable) { ret_x(c, ST(ds_st)->dw); }
NVN_API(nvnDepthStencilStateGetDepthFunc) { ret_x(c, ST(ds_st)->df); }
NVN_API(nvnDepthStencilStateGetStencilTestEnable) { ret_x(c, ST(ds_st)->st); }
// NVNstencilFunc nvnDepthStencilStateGetStencilFunc(const NVNdepthStencilState *s, NVNface face)
NVN_API(nvnDepthStencilStateGetStencilFunc) {
const ds_st *d = ST(ds_st);
ret_x(c, (uint32_t)arg_x(c, 1) == NVN_FACE_BACK ? d->sf_b : d->sf_f);
}
// void nvnDepthStencilStateGetStencilOp(const NVNdepthStencilState *s, NVNface face, NVNstencilOp *fail,
// NVNstencilOp *depth_fail, NVNstencilOp *depth_pass)
NVN_API(nvnDepthStencilStateGetStencilOp) {
const ds_st *d = ST(ds_st);
int back = (uint32_t)arg_x(c, 1) == NVN_FACE_BACK;
*(int32_t *)arg_p(c, 2) = (int32_t)(back ? d->fail_b : d->fail_f);
*(int32_t *)arg_p(c, 3) = (int32_t)(back ? d->zfail_b : d->zfail_f);
*(int32_t *)arg_p(c, 4) = (int32_t)(back ? d->zpass_b : d->zpass_f);
}
// void nvnVertexAttribStateGetFormat(const NVNvertexAttribState *s, NVNformat *format, ptrdiff_t *offset)
NVN_API(nvnVertexAttribStateGetFormat) {
*(int32_t *)arg_p(c, 1) = ST(va_st)->fmt;
*(int64_t *)arg_p(c, 2) = ST(va_st)->offset;
}
NVN_API(nvnVertexAttribStateGetStreamIndex) { ret_x(c, ST(va_st)->stream); }
NVN_API(nvnVertexStreamStateGetStride) { ret_x(c, ST(vs_st)->stride); }
NVN_API(nvnVertexStreamStateGetDivisor) { ret_x(c, ST(vs_st)->divisor); }
#undef ST
// ---------- rasterisation state without a host equivalent: lines are 1 pixel wide and point size comes from the vertex
// shader. Non-default values are reported once; the drawing proceeds without them.
#define ONCE(...) do { static int once_; if (!once_++) rt_log("nvn: " __VA_ARGS__); } while (0)
// void nvnCommandBufferSetLineWidth / SetPointSize(NVNcommandBuffer *cmd, float value)
NVN_API(nvnCommandBufferSetLineWidth) {
float w = arg_s(c, 0);
if (w != 1.0f) ONCE("SetLineWidth %g: lines are drawn 1 pixel wide", w);
}
// SetPointSize is recorded in nvn_api_cmd.c (OP_BIND_STATE kind 6): the replay feeds it to the vertex shaders through the
// support buffer (translator patch 0008).
// void nvnCommandBufferSetConservativeRasterEnable(NVNcommandBuffer *cmd, NVNboolean enable)
NVN_API(nvnCommandBufferSetConservativeRasterEnable) {
if (nvn_arg_bool(c, 1)) ONCE("conservative rasterisation requested: drawn normally");
}
// void nvnCommandBufferSetConservativeRasterDilate(NVNcommandBuffer *cmd, float dilate)
NVN_API(nvnCommandBufferSetConservativeRasterDilate) { (void)c; }
// void nvnCommandBufferSetSampleMask(NVNcommandBuffer *cmd, int mask)
NVN_API(nvnCommandBufferSetSampleMask) {
int m = (int)arg_x(c, 1);
if ((m & 0xffff) != 0xffff) ONCE("SetSampleMask %#x: fixed-function sample mask not applied", m);
}
// void nvnCommandBufferSetAlphaRef(NVNcommandBuffer *cmd, float ref): used only with the alpha test, which the replay
// reports
NVN_API(nvnCommandBufferSetAlphaRef) {
static int once_;
if (!once_++) rt_log("nvn: SetAlphaRef %g: the alpha test is not applied", (double)arg_s(c, 0));
}
// void nvnCommandBufferSetDepthBounds(NVNcommandBuffer *cmd, NVNboolean enable, float min, float max): disabling is
// exact; an enabled range is drawn without the test and reported
NVN_API(nvnCommandBufferSetDepthBounds) {
float lo = arg_s(c, 0), hi = arg_s(c, 1);
if (nvn_arg_bool(c, 1) && (lo > 0.0f || hi < 1.0f))
ONCE("SetDepthBounds [%g, %g]: depth-bounds test not applied (fragments outside the range are kept)", lo, hi);
}
// void nvnCommandBufferSetProvokingVertexMode(NVNcommandBuffer *cmd, int mode) (NVN 55): the NVN default is the last
// vertex (GL convention); only the default is accepted
NVN_API(nvnCommandBufferSetProvokingVertexMode) {
int mode = (int)arg_x(c, 1);
if (mode != 0) ONCE("SetProvokingVertexMode %d: provoking-vertex conversion not implemented", mode);
}
#undef ONCE
// ---------- Tegra tiled-cache, ZCull and rasterisation hints
NVN_API(nvnCommandBufferSetTiledCacheAction) { (void)c; } // tiled-cache flush / enable hint
NVN_API(nvnCommandBufferSetTiledCacheTileSize) { (void)c; }
NVN_API(nvnCommandBufferSetSubpixelPrecisionBias) { (void)c; }
NVN_API(nvnCommandBufferDiscardColor) { (void)c; } // contents become undefined: keeping them is valid
NVN_API(nvnCommandBufferDiscardDepthStencil) { (void)c; }
NVN_API(nvnCommandBufferSaveZCullData) { (void)c; } // no depth-cull cache: nothing to save or restore
NVN_API(nvnCommandBufferRestoreZCullData) { (void)c; }
NVN_API(nvnCommandBufferOverrideSubtileSize) { (void)c; } // NVN 55 hints, no prototype in nvn.h
NVN_API(nvnCommandBufferSetZCullZF32CompressionEnable) { (void)c; }
// nvnProgramGetSubtileSize (NVN 55): its signature is not identified
NVN_API(nvnProgramGetSubtileSize) { (void)c; rt_fatal("nvn: nvnProgramGetSubtileSize: signature not identified"); }