mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Fill the fragment inputs a mesh program does not write, with the point sprite's coordinate for point meshes.
A mesh pipeline whose fragment stage reads a location the mesh program never writes now gets a fragment module where that input is a private variable set to (0, 0, 0, 1) at entry, or to (s, t, 0, 1) of gl_PointCoord when the mesh program draws points. The fragment shaders recorded with two of the game's point-drawing mesh programs read location 5, which those programs never write; Vulkan leaves such an input undefined. Checks: tests/fs_fill_inputs_check.py passes; 237 recorded fragment modules rewritten offline pass spirv-val; a 60 s Kakariko run with the change shows the area as before with no Vulkan errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -455,6 +455,11 @@ files.
|
||||
A change to how a gamepad's axes are read (`runtime/input.c` `pad_axis`: an axis counts once it has been seen at rest)
|
||||
passes `python tests\pad_rest_check.py`, which builds that function with stand-ins for SDL. No game files.
|
||||
|
||||
A change to how a mesh pipeline's fragment module gets the inputs no mesh output feeds (`runtime/vk_pipeline.c`
|
||||
`spirv_mesh_outputs`, `spirv_fs_fill_inputs`) passes `python tests\fs_fill_inputs_check.py`, which builds both
|
||||
functions and runs them on mesh and fragment shaders it compiles with glslang, then validates the result. No game
|
||||
files.
|
||||
|
||||
A change to which render passes count as drawn frame after frame (`runtime/vk_target_frames.h`: a draw into one is
|
||||
skipped while its pipeline builds, a draw into any other waits) passes `python tests\target_frames_check.py`, which
|
||||
builds the header alone and replays an area change after a loading screen from a player's log: the scene, probe and
|
||||
|
||||
@@ -124,8 +124,11 @@ Grep-level look on 2026-10-22, statuses updated 2026-10-26; "saw" is what the gr
|
||||
translator patch 0006 initialises the unlinked outputs. Mesh programs: a component of a position or generic vector
|
||||
that the program's output map leaves out is written as (0, 0, 0, 1) by the mesh entry function
|
||||
(`tools/xlate/MeshSpirv.cs`) and by the fallback's vertex stage (`runtime/mesh_fb.c` `meshfb_vs`); a whole location
|
||||
the fragment stage reads and the mesh program lacks is still not padded with mesh shaders (`have = ~0u` for mesh in
|
||||
`vk_pipeline.c`). **Implemented; array-span runtime validation pending.**
|
||||
the fragment stage reads and the mesh program does not write is filled in the fragment module of the mesh pipeline
|
||||
(`vk_pipeline.c` `spirv_fs_fill_inputs`): (0, 0, 0, 1), or for a mesh program that draws points the point sprite's
|
||||
coordinate (s, t, 0, 1): the fragment shaders recorded with two of the game's point-drawing mesh programs read
|
||||
location 5, which those programs never write.
|
||||
**Implemented; array-span runtime validation pending.**
|
||||
3. **Masked clears.** `vk_exec.c` skips colour clears with a partial channel mask (`W_CLEAR_MASK`); `clear_ds` skips stencil
|
||||
clears with a partial mask (`W_STENCIL_MASK`). NX2navi issues such clears. Check the log for
|
||||
`colour clear with channel mask` and `stencil clear with mask`. The hardware recipe (1.3) shows what the replay must do: colour clear = scissor 0 + channel mask, stencil clear = `value & mask` merged under the front write mask; so a draw with `colorWriteMask` / `stencilWriteMask` set to the clear mask is the exact equivalent. **Missing.**
|
||||
|
||||
@@ -1512,6 +1512,186 @@ static uint32_t *spirv_add_outputs(const uint32_t *w, uint64_t n, uint32_t missi
|
||||
return o;
|
||||
}
|
||||
|
||||
// The locations a mesh module writes (its Output variables with a Location), and whether it draws points (execution
|
||||
// mode OutputPoints). 0 when the module cannot be read.
|
||||
static uint32_t spirv_mesh_outputs(const uint32_t *w, uint64_t n, int *points) {
|
||||
enum { OP_EXECUTION_MODE = 16, MODE_OUTPUT_POINTS = 27 };
|
||||
uint32_t bound = n >= 5 && w[3] < (1u << 22) ? w[3] : 0, mask = 0;
|
||||
uint8_t *out = bound ? calloc(bound, 1) : NULL;
|
||||
int8_t *loc = bound ? malloc(bound) : NULL;
|
||||
*points = 0;
|
||||
if (!out || !loc) return free(out), free(loc), 0;
|
||||
memset(loc, -1, bound);
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
if (!len || i + len > n) break;
|
||||
const uint32_t *a = w + i + 1;
|
||||
if (op == OP_EXECUTION_MODE && len >= 3 && a[1] == MODE_OUTPUT_POINTS) *points = 1;
|
||||
else if (op == SPV_OP_DECORATE && len >= 4 && a[1] == SPV_DEC_LOCATION && a[0] < bound && a[2] < 32) loc[a[0]] = (int8_t)a[2];
|
||||
else if (op == SPV_OP_VARIABLE && len >= 4 && a[2] == SPV_SC_OUTPUT && a[1] < bound) out[a[1]] = 1;
|
||||
else if (op == SPV_OP_FUNCTION) break;
|
||||
i += len;
|
||||
}
|
||||
for (uint32_t id = 0; id < bound; id++)
|
||||
if (out[id] && loc[id] >= 0) mask |= 1u << loc[id];
|
||||
free(out), free(loc);
|
||||
return mask;
|
||||
}
|
||||
|
||||
// A fragment module whose inputs at the locations of `missing` read what Maxwell gives a fragment input no earlier
|
||||
// stage writes: (0, 0, 0, 1), or with `points` the point sprite's coordinate (s, t, 0, 1). Each such Input variable
|
||||
// (a float or a float vector) becomes a Private one, the access chains through it Private pointers, its decorations
|
||||
// go, and the entry function stores the value into it before anything else. NULL when nothing is to be done or the
|
||||
// module does not have the expected layout.
|
||||
static uint32_t *spirv_fs_fill_inputs(const uint32_t *w, uint64_t n, uint32_t missing, int points, uint64_t *out_n) {
|
||||
enum { SC_INPUT = 1, SC_PRIVATE = 6, OP_EXECUTION_MODE = 16, OP_TYPE_POINTER = 32, OP_LABEL = 248, OP_LOAD = 61,
|
||||
OP_STORE = 62, OP_ACCESS_CHAIN = 65, OP_IN_BOUNDS_ACCESS_CHAIN = 66, OP_COMPOSITE_CONSTRUCT = 80,
|
||||
OP_COMPOSITE_EXTRACT = 81, OP_DECORATE_ID = 332, BUILTIN_POINT_COORD = 16, MAX_FILL = 32, MAX_PRIV = 64 };
|
||||
if (!missing || n < 5 || w[3] >= (1u << 22)) return NULL;
|
||||
uint32_t old_bound = w[3], bound = old_bound;
|
||||
uint32_t *pt_type = calloc(old_bound, 4), *vec_comp = calloc(old_bound, 4), *vec_n = calloc(old_bound, 4);
|
||||
int8_t *loc = malloc(old_bound);
|
||||
uint8_t *fill = calloc(old_bound, 1); // 1: a filled variable, 2: an access chain through one
|
||||
uint32_t *o = NULL;
|
||||
if (!pt_type || !vec_comp || !vec_n || !loc || !fill) goto done;
|
||||
memset(loc, -1, old_bound);
|
||||
uint32_t f32 = 0, v2 = 0, entry = 0;
|
||||
uint64_t ep_at = 0, ann_end = 0, fn_at = 0;
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
if (!len || i + len > n) goto done;
|
||||
const uint32_t *a = w + i + 1;
|
||||
if (op == SPV_OP_ENTRY_POINT && !ep_at) ep_at = i, entry = a[1];
|
||||
else if (op == SPV_OP_DECORATE || op == 72 || op == OP_DECORATE_ID) {
|
||||
ann_end = i + len;
|
||||
if (op == SPV_OP_DECORATE && len >= 4 && a[1] == SPV_DEC_LOCATION && a[0] < old_bound && a[2] < 32)
|
||||
loc[a[0]] = (int8_t)a[2];
|
||||
} else if (op == SPV_OP_TYPE_FLOAT && len >= 3 && a[1] == 32) f32 = a[0];
|
||||
else if (op == SPV_OP_TYPE_VECTOR && len >= 4 && a[0] < old_bound) {
|
||||
vec_comp[a[0]] = a[1], vec_n[a[0]] = a[2];
|
||||
if (a[1] == f32 && a[2] == 2) v2 = a[0];
|
||||
} else if (op == OP_TYPE_POINTER && len >= 4 && a[0] < old_bound) pt_type[a[0]] = a[2];
|
||||
else if (op == SPV_OP_FUNCTION) { fn_at = i; break; }
|
||||
i += len;
|
||||
}
|
||||
if (!ep_at || !ann_end || !fn_at || !f32 || !entry || ann_end > fn_at) goto done;
|
||||
// the variables to fill, and the Private pointer types they and the chains through them need (pass 1)
|
||||
uint32_t vars[MAX_FILL], vtype[MAX_FILL], nvars = 0, priv[MAX_PRIV][2], npriv = 0; // priv: Input pointer type -> Private
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
const uint32_t *a = w + i + 1;
|
||||
uint32_t ptr = 0;
|
||||
if (op == SPV_OP_VARIABLE && len >= 4 && a[2] == SC_INPUT && a[1] < old_bound && a[0] < old_bound && i < fn_at) {
|
||||
int l = loc[a[1]];
|
||||
uint32_t t = pt_type[a[0]];
|
||||
int floaty = t == f32 || (t < old_bound && vec_comp[t] == f32 && vec_n[t] >= 2 && vec_n[t] <= 4);
|
||||
if (l >= 0 && (missing >> l & 1) && floaty && nvars < MAX_FILL) {
|
||||
fill[a[1]] = 1, vars[nvars] = a[1], vtype[nvars++] = t, ptr = a[0];
|
||||
}
|
||||
} else if ((op == OP_ACCESS_CHAIN || op == OP_IN_BOUNDS_ACCESS_CHAIN) && len >= 4 && a[2] < old_bound && fill[a[2]] &&
|
||||
a[1] < old_bound && a[0] < old_bound) {
|
||||
fill[a[1]] = 2, ptr = a[0];
|
||||
}
|
||||
if (ptr) {
|
||||
uint32_t k = 0;
|
||||
while (k < npriv && priv[k][0] != ptr) k++;
|
||||
if (k == npriv) {
|
||||
if (npriv == MAX_PRIV) goto done;
|
||||
priv[npriv][0] = ptr, priv[npriv++][1] = bound++;
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
if (!nvars) goto done;
|
||||
// the globals added before the first function: two constants, gl_PointCoord, and the Private pointer types not yet
|
||||
// declared (a filled variable's goes right before the variable, below; an access chain's is only used in a function)
|
||||
uint32_t extra[MAX_PRIV * 4 + 32], ne = 0, c0 = bound++, c1 = bound++, pc_ptr = 0, pc_var = 0;
|
||||
uint8_t declared[MAX_PRIV] = {0};
|
||||
extra[ne++] = 4u << 16 | SPV_OP_CONSTANT, extra[ne++] = f32, extra[ne++] = c0, extra[ne++] = 0;
|
||||
extra[ne++] = 4u << 16 | SPV_OP_CONSTANT, extra[ne++] = f32, extra[ne++] = c1, extra[ne++] = 0x3f800000;
|
||||
if (points) {
|
||||
if (!v2) v2 = bound++, extra[ne++] = 4u << 16 | SPV_OP_TYPE_VECTOR, extra[ne++] = v2, extra[ne++] = f32, extra[ne++] = 2;
|
||||
pc_ptr = bound++, pc_var = bound++;
|
||||
extra[ne++] = 4u << 16 | OP_TYPE_POINTER, extra[ne++] = pc_ptr, extra[ne++] = SC_INPUT, extra[ne++] = v2;
|
||||
extra[ne++] = 4u << 16 | SPV_OP_VARIABLE, extra[ne++] = pc_ptr, extra[ne++] = pc_var, extra[ne++] = SC_INPUT;
|
||||
}
|
||||
o = malloc((n + ne + (uint64_t)npriv * 4 + 16 + (uint64_t)nvars * 12 + 32) * 4);
|
||||
if (!o) goto done;
|
||||
uint32_t *p = o;
|
||||
memcpy(p, w, 5 * 4), p += 5;
|
||||
int in_entry = 0, stored = 0;
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
const uint32_t *a = w + i + 1;
|
||||
if (i == fn_at) {
|
||||
for (uint32_t k = 0; k < npriv; k++)
|
||||
if (!declared[k]) *p++ = 4u << 16 | OP_TYPE_POINTER, *p++ = priv[k][1], *p++ = SC_PRIVATE, *p++ = pt_type[priv[k][0]];
|
||||
memcpy(p, extra, ne * 4), p += ne;
|
||||
}
|
||||
if (i == ep_at) { // the interface: filled variables out before SPIR-V 1.4 (Private is not listed), gl_PointCoord in
|
||||
uint32_t *head = p, name_words = (uint32_t)(strnlen((const char *)(a + 2), (len - 3) * 4) / 4 + 1);
|
||||
for (uint32_t k = 0; k < len; k++) {
|
||||
uint32_t v = w[i + k];
|
||||
if (k >= 3 + name_words && v < old_bound && fill[v] == 1 && w[1] < 0x00010400) continue;
|
||||
*p++ = v;
|
||||
}
|
||||
if (pc_var) *p++ = pc_var;
|
||||
*head = (uint32_t)(p - head) << 16 | SPV_OP_ENTRY_POINT;
|
||||
i += len;
|
||||
continue;
|
||||
}
|
||||
if ((op == SPV_OP_DECORATE || op == OP_DECORATE_ID) && len >= 3 && a[0] < old_bound && fill[a[0]] == 1) {
|
||||
i += len; // Location, Flat and the like belong to Input variables
|
||||
if (i == ann_end && pc_var)
|
||||
*p++ = 4u << 16 | SPV_OP_DECORATE, *p++ = pc_var, *p++ = SPV_DEC_BUILTIN, *p++ = BUILTIN_POINT_COORD;
|
||||
continue;
|
||||
}
|
||||
if (op == SPV_OP_VARIABLE && len >= 4 && a[1] < old_bound && fill[a[1]] == 1) // its pointer type first
|
||||
for (uint32_t k = 0; k < npriv; k++)
|
||||
if (priv[k][0] == a[0] && !declared[k])
|
||||
declared[k] = 1, *p++ = 4u << 16 | OP_TYPE_POINTER, *p++ = priv[k][1], *p++ = SC_PRIVATE, *p++ = pt_type[priv[k][0]];
|
||||
memcpy(p, w + i, len * 4);
|
||||
if ((op == SPV_OP_VARIABLE || op == OP_ACCESS_CHAIN || op == OP_IN_BOUNDS_ACCESS_CHAIN) && len >= 4 &&
|
||||
a[1] < old_bound && fill[a[1]]) {
|
||||
for (uint32_t k = 0; k < npriv; k++)
|
||||
if (priv[k][0] == a[0]) p[1] = priv[k][1];
|
||||
if (op == SPV_OP_VARIABLE) p[3] = SC_PRIVATE;
|
||||
}
|
||||
p += len;
|
||||
if (i + len == ann_end && pc_var) // gl_PointCoord's BuiltIn after the last decoration
|
||||
*p++ = 4u << 16 | SPV_OP_DECORATE, *p++ = pc_var, *p++ = SPV_DEC_BUILTIN, *p++ = BUILTIN_POINT_COORD;
|
||||
if (op == SPV_OP_FUNCTION && len >= 5) in_entry = a[1] == entry;
|
||||
i += len;
|
||||
if (op == OP_LABEL && in_entry && !stored) { // the entry's first block: its OpVariables, then the stores
|
||||
while (i < n && (w[i] & 0xffff) == SPV_OP_VARIABLE) memcpy(p, w + i, (w[i] >> 16) * 4), p += w[i] >> 16, i += w[i] >> 16;
|
||||
uint32_t sx = c0, sy = c0;
|
||||
if (pc_var) {
|
||||
uint32_t pc = bound++;
|
||||
sx = bound++, sy = bound++;
|
||||
*p++ = 4u << 16 | OP_LOAD, *p++ = v2, *p++ = pc, *p++ = pc_var;
|
||||
*p++ = 5u << 16 | OP_COMPOSITE_EXTRACT, *p++ = f32, *p++ = sx, *p++ = pc, *p++ = 0;
|
||||
*p++ = 5u << 16 | OP_COMPOSITE_EXTRACT, *p++ = f32, *p++ = sy, *p++ = pc, *p++ = 1;
|
||||
}
|
||||
for (uint32_t k = 0; k < nvars; k++) {
|
||||
uint32_t t = vtype[k], c = t == f32 ? 1 : vec_n[t], comp[4] = {sx, sy, c0, c1}, val = comp[0];
|
||||
if (c > 1) {
|
||||
val = bound++;
|
||||
*p++ = (3 + c) << 16 | OP_COMPOSITE_CONSTRUCT, *p++ = t, *p++ = val;
|
||||
for (uint32_t q = 0; q < c; q++) *p++ = comp[q]; // x y z w in order: (s, t, 0, 1) or (0, 0, 0, 1)
|
||||
}
|
||||
*p++ = 3u << 16 | OP_STORE, *p++ = vars[k], *p++ = val;
|
||||
}
|
||||
stored = 1;
|
||||
}
|
||||
}
|
||||
if (!stored) { free(o), o = NULL; goto done; }
|
||||
o[3] = bound;
|
||||
*out_n = (uint64_t)(p - o);
|
||||
done:
|
||||
free(pt_type), free(vec_comp), free(vec_n), free(loc), free(fill);
|
||||
return o;
|
||||
}
|
||||
|
||||
// Two fixes for what the translator declares but a shader does not use (NULL: nothing to change):
|
||||
// - the fragment shader interlock capability and extension, which the translator adds to every fragment shader when the host
|
||||
// reports interlock support (SpirvGenerator.cs) and vk_device.c does not enable; kept when the shader interlocks;
|
||||
@@ -2847,24 +3027,33 @@ static VkPipeline create_graphics(const VkPipeState *s, const GKey *k, const Lay
|
||||
}
|
||||
if (s->fs) {
|
||||
GfxShaderIo fin[32], vout[32];
|
||||
uint32_t need = gfx_shader_inputs(s->fs, fin), have = mesh ? ~0u : gfx_shader_outputs(s->vs, vout), missing = 0;
|
||||
// a mesh stage's outputs from its module (spirv_mesh_outputs): the translation reflects none
|
||||
int points = 0;
|
||||
uint32_t need = gfx_shader_inputs(s->fs, fin), missing = 0,
|
||||
have = mesh ? (vcode ? spirv_mesh_outputs(vcode, vw, &points) : ~0u) : gfx_shader_outputs(s->vs, vout);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (!(need >> i & 1) || fin[i].packed) continue;
|
||||
if (!(have >> i & 1)) missing |= 1u << i;
|
||||
else if (!mesh && vout[i].type != fin[i].type)
|
||||
LOG_ONCE("vk_pipeline: varying %d is type %d in the vertex stage, %d in the fragment stage", i, vout[i].type, fin[i].type);
|
||||
}
|
||||
if (missing && !l->set1) {
|
||||
if (missing && !l->set1 && !mesh) {
|
||||
uint64_t n2 = 0;
|
||||
vpatched = spirv_add_outputs(vcode, vw, missing, fin, &n2);
|
||||
if (!vpatched) LOG_ONCE("vk_pipeline: cannot add missing vertex outputs (mask %#x)", missing);
|
||||
else vcode = vpatched, vw = n2;
|
||||
}
|
||||
const uint32_t *fcode = gfx_shader_spirv(s->fs, &fw);
|
||||
if (missing && mesh && fcode) { // inputs no mesh output feeds read as Maxwell gives them (spirv_fs_fill_inputs)
|
||||
uint64_t n2 = 0;
|
||||
uint32_t *f = spirv_fs_fill_inputs(fcode, fw, missing, points, &n2);
|
||||
if (f) fcode = fpatched = f, fw = n2;
|
||||
else LOG_ONCE("vk_pipeline: cannot fill fragment inputs the mesh stage lacks (mask %#x)", missing);
|
||||
}
|
||||
if (k->dsb) {
|
||||
uint64_t n2 = 0;
|
||||
uint32_t *d = spirv_dual_source(fcode, fw, &n2);
|
||||
if (d) fcode = fpatched = d, fw = n2;
|
||||
if (d) free(fpatched), fcode = fpatched = d, fw = n2;
|
||||
else LOG_ONCE("vk_pipeline: SRC1 blend factors but the fragment shader writes no colour output 1");
|
||||
}
|
||||
if (l->set1 || fs_off) {
|
||||
|
||||
158
tests/fs_fill_inputs_check.py
Normal file
158
tests/fs_fill_inputs_check.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""Fragment inputs that no mesh output feeds (runtime/vk_pipeline.c spirv_mesh_outputs, spirv_fs_fill_inputs): builds
|
||||
the two functions, cut from vk_pipeline.c by their text, and runs them on modules glslang makes here:
|
||||
|
||||
- a mesh shader that draws points and writes no generic output, and one that draws triangles and writes location 1;
|
||||
- a fragment shader that reads locations 1 and 5 (5 through a component, as the translator's shaders do).
|
||||
|
||||
Checks that the point mesh's mask is 0 with points, the triangle mesh's is location 1; that the fragment module
|
||||
rewritten for the point mesh has a PointCoord input and no Input at location 5, and for the triangle mesh keeps
|
||||
location 1 as an Input; and that every rewritten module passes spirv-val. No game files.
|
||||
|
||||
Usage: python tests/fs_fill_inputs_check.py [--cc <clang>]
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
||||
|
||||
MESH_POINTS = '''#version 460
|
||||
#extension GL_EXT_mesh_shader : require
|
||||
layout(local_size_x = 1) in;
|
||||
layout(points, max_vertices = 1, max_primitives = 1) out;
|
||||
void main() {
|
||||
SetMeshOutputsEXT(1, 1);
|
||||
gl_MeshVerticesEXT[0].gl_Position = vec4(0.0, 0.0, 0.5, 1.0);
|
||||
gl_MeshVerticesEXT[0].gl_PointSize = 4.0;
|
||||
gl_PrimitivePointIndicesEXT[0] = 0;
|
||||
}
|
||||
'''
|
||||
MESH_TRIANGLES = '''#version 460
|
||||
#extension GL_EXT_mesh_shader : require
|
||||
layout(local_size_x = 1) in;
|
||||
layout(triangles, max_vertices = 3, max_primitives = 1) out;
|
||||
layout(location = 1) out vec4 attr1[];
|
||||
void main() {
|
||||
SetMeshOutputsEXT(3, 1);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
gl_MeshVerticesEXT[i].gl_Position = vec4(float(i), 0.0, 0.5, 1.0);
|
||||
attr1[i] = vec4(1.0);
|
||||
}
|
||||
gl_PrimitiveTriangleIndicesEXT[0] = uvec3(0, 1, 2);
|
||||
}
|
||||
'''
|
||||
FRAG = '''#version 460
|
||||
layout(location = 1) in vec4 a1;
|
||||
layout(location = 5) in vec4 a5;
|
||||
layout(location = 0) out vec4 color;
|
||||
void main() { color = a1 + vec4(a5.y, a5.x, 0.0, a5.w); }
|
||||
'''
|
||||
|
||||
HARNESS = r'''
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
enum { SPV_OP_ENTRY_POINT = 15, SPV_OP_TYPE_INT = 21, SPV_OP_TYPE_FLOAT = 22, SPV_OP_TYPE_VECTOR = 23,
|
||||
SPV_OP_TYPE_POINTER = 32, SPV_OP_CONSTANT = 43, SPV_OP_CONSTANT_COMPOSITE = 44,
|
||||
SPV_OP_CONSTANT_NULL = 46, SPV_OP_FUNCTION = 54, SPV_OP_VARIABLE = 59,
|
||||
SPV_OP_DECORATE = 71, SPV_DEC_BUILTIN = 11, SPV_DEC_LOCATION = 30, SPV_SC_OUTPUT = 3 };
|
||||
@BODY@
|
||||
static uint32_t *rd(const char *p, uint64_t *n) {
|
||||
FILE *f = fopen(p, "rb");
|
||||
if (!f) exit(2);
|
||||
fseek(f, 0, SEEK_END);
|
||||
long l = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
uint32_t *w = malloc((size_t)l);
|
||||
if (fread(w, 1, (size_t)l, f) != (size_t)l) exit(2);
|
||||
fclose(f);
|
||||
*n = (uint64_t)l / 4;
|
||||
return w;
|
||||
}
|
||||
int main(int argc, char **argv) { // mesh.spv frag.spv out.spv: prints "<mask> <points>", writes the rewrite
|
||||
(void)argc;
|
||||
uint64_t mn, fn, on = 0;
|
||||
uint32_t *m = rd(argv[1], &mn), *fr = rd(argv[2], &fn);
|
||||
int points = 0;
|
||||
uint32_t have = spirv_mesh_outputs(m, mn, &points);
|
||||
uint32_t *o = spirv_fs_fill_inputs(fr, fn, ((1u << 1) | (1u << 5)) & ~have, points, &on);
|
||||
printf("%u %d\n", have, points);
|
||||
if (!o) return 1;
|
||||
FILE *f = fopen(argv[3], "wb");
|
||||
fwrite(o, 4, on, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def inputs_and_builtins(spv):
|
||||
w = struct.unpack('<%dI' % (len(spv) // 4), spv)
|
||||
loc, builtin, sc = {}, {}, {}
|
||||
i = 5
|
||||
while i < len(w):
|
||||
n, op = w[i] >> 16, w[i] & 0xFFFF
|
||||
if op == 71 and w[i + 2] == 30:
|
||||
loc[w[i + 1]] = w[i + 3]
|
||||
if op == 71 and w[i + 2] == 11:
|
||||
builtin[w[i + 1]] = w[i + 3]
|
||||
if op == 59:
|
||||
sc[w[i + 2]] = w[i + 3]
|
||||
i += n
|
||||
return {loc[v] for v, s in sc.items() if s == 1 and v in loc}, {builtin[v] for v, s in sc.items() if s == 1 and v in builtin}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument('--cc', help='the C compiler (default: MSYS2 UCRT64 clang on Windows, clang elsewhere)')
|
||||
args = ap.parse_args()
|
||||
win = os.name == 'nt'
|
||||
cc = args.cc or (str(MSYS_BIN / 'clang.exe') if win and MSYS_BIN.exists() else 'clang')
|
||||
glslang = str(MSYS_BIN / 'glslangValidator.exe') if win and MSYS_BIN.exists() else shutil.which('glslangValidator')
|
||||
val = str(MSYS_BIN / 'spirv-val.exe') if win and MSYS_BIN.exists() else shutil.which('spirv-val')
|
||||
env = dict(os.environ)
|
||||
if win and MSYS_BIN.exists():
|
||||
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
||||
src = (ROOT / 'runtime' / 'vk_pipeline.c').read_text(encoding='utf-8')
|
||||
body = src[src.index('// The locations a mesh module writes'):src.index('// Two fixes for what the translator declares')]
|
||||
fails = 0
|
||||
with tempfile.TemporaryDirectory(prefix='nx2navi-fs-fill-') as tmp:
|
||||
t = Path(tmp)
|
||||
for name, text, stage in (('points', MESH_POINTS, 'mesh'), ('triangles', MESH_TRIANGLES, 'mesh'), ('frag', FRAG, 'frag')):
|
||||
(t / f'{name}.{stage}').write_text(text)
|
||||
subprocess.check_call([glslang, '-V', '--target-env', 'vulkan1.3', '-o', str(t / f'{name}.spv'),
|
||||
str(t / f'{name}.{stage}')], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
(t / 'check.c').write_text(HARNESS.replace('@BODY@', body), encoding='utf-8')
|
||||
exe = t / ('check.exe' if win else 'check')
|
||||
subprocess.check_call([cc, '-O2', '-w', str(t / 'check.c'), '-o', str(exe)], env=env)
|
||||
for mesh, want_mask, want_points in (('points', 0, 1), ('triangles', 1 << 1, 0)):
|
||||
r = subprocess.run([str(exe), str(t / f'{mesh}.spv'), str(t / 'frag.spv'), str(t / f'out_{mesh}.spv')],
|
||||
capture_output=True, text=True, env=env)
|
||||
mask, points = (int(x) for x in r.stdout.split())
|
||||
if (mask, points) != (want_mask, want_points) or r.returncode:
|
||||
print(f'FAIL {mesh}: mask {mask:#x} points {points} (want {want_mask:#x} {want_points}), rewrite {r.returncode == 0}')
|
||||
fails += 1
|
||||
continue
|
||||
out = (t / f'out_{mesh}.spv').read_bytes()
|
||||
ins, builtins = inputs_and_builtins(out)
|
||||
want_ins = set() if mesh == 'points' else {1}
|
||||
if ins != want_ins or (mesh == 'points') != (16 in builtins):
|
||||
print(f'FAIL {mesh}: inputs {sorted(ins)} builtins {sorted(builtins)}')
|
||||
fails += 1
|
||||
v = subprocess.run([val, '--target-env', 'vulkan1.3', str(t / f'out_{mesh}.spv')], capture_output=True, text=True)
|
||||
if v.returncode:
|
||||
print(f'FAIL {mesh}: spirv-val: {(v.stdout + v.stderr).strip()}')
|
||||
fails += 1
|
||||
print('fs fill inputs: ok' if not fails else f'fs fill inputs: {fails} failures')
|
||||
return 1 if fails else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user