From 0103939ba4fcd76d3bc5c052d4e27cddc69ac232 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Sat, 17 Oct 2026 02:16:54 -0700 Subject: [PATCH] nv2a: Fix R12 input in paired/multiout ops writing to oPos Paired MAC+ILU and duplicate output instructions can write to oPos while reading from R12. This leads to a case where xemu's serialized emulation erroneously uses the output of a previous instruction when calculating the value of a later one. E.g., in ``` /* 0x00000000 0x0080201A 0xC4002868 0x7CB0E800 */ MAD oPos.xyz, R12.xyz, R1.x, C[1].xyz + MAD R11.xy, R12.xyz, R1.x, C[1].xyz ``` the value of oPos prior to the first instruction should be used for both MAD calculations. This could alternatively be fixed by writing oPos to a temp vector and deferring the output vector update until after the token is fully processed. Note that we always process output registers writes before temp register writes so the only interesting case should be the oPos/R12 alias. An op that writes to one of its temp inputs will always execute the non-modifying output register write before updating the temp register. [Tests](https://github.com/abaire/nxdk_pgraph_tests/blob/1745a45290a5d607f9582303d6882cbf62f003de/src/tests/vertex_shader_independence_tests.cpp#L125) [HW results](https://abaire.github.io/nxdk_pgraph_tests_golden_results/results/Vertex_shader_independence_tests/index.html) --- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index eb74ac1966..38f6dc5dd9 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -209,6 +209,7 @@ static bool ilu_force_scalar[] = { false, }; +#define OUTPUT_REG_POS 0 #define OUTPUT_REG_FOG 5 static const char* out_reg_name[] = { @@ -304,7 +305,6 @@ static MString *decode_opcode_input(const uint32_t *shader_token, MString *ret_str = mstring_new(); - if (vsh_get_field(shader_token, neg_field) > 0) { mstring_append_fmt(ret_str, "-"); } @@ -354,6 +354,7 @@ static MString *decode_opcode(const uint32_t *shader_token, MString *ret = mstring_new(); int reg_num = vsh_get_field(shader_token, FLD_OUT_R); bool use_temp_var = false; + bool use_temp_opos = false; /* Test for paired opcodes (in other words : Are both <> NOP?) */ if (out_mux == OMUX_MAC @@ -369,6 +370,8 @@ static MString *decode_opcode(const uint32_t *shader_token, reg_num = 1; } + const char *write_mask_str = NULL; + /* See if we must add a muxed opcode too: */ if (vsh_get_field(shader_token, FLD_OUT_MUX) == out_mux /* Only if it's not masked away: */ @@ -386,19 +389,23 @@ static MString *decode_opcode(const uint32_t *shader_token, shader_token, FLD_OUT_ADDRESS))); } else { int out_reg = vsh_get_field(shader_token, FLD_OUT_ADDRESS) & 0xF; - mstring_append(ret,out_reg_name[out_reg]); + if (out_reg == OUTPUT_REG_POS && (use_temp_var || mask)) { + use_temp_opos = true; + mstring_append(ret, "_temp_r12"); + } else { + mstring_append(ret, out_reg_name[out_reg]); + } write_fog_register = out_reg == OUTPUT_REG_FOG; } int write_mask = vsh_get_field(shader_token, FLD_OUT_O_MASK); - const char *write_mask_str = write_fog_register ? fog_mask_str[write_mask] : mask_str[write_mask]; + write_mask_str = write_fog_register ? fog_mask_str[write_mask] : mask_str[write_mask]; mstring_append(ret, write_mask_str); mstring_append(ret, inputs); mstring_append(ret, ");\n"); } if (use_temp_var) { - assert(suffix && "Temp var flagged on non-MAC instruction"); *suffix = mstring_new(); if (strcmp(opcode, mac_opcode[MAC_ARL]) == 0) { mstring_append_fmt(ret, " ARL(_temp_addr%s);\n", inputs); @@ -428,6 +435,16 @@ static MString *decode_opcode(const uint32_t *shader_token, } } + if (use_temp_opos) { + if (!*suffix) { + *suffix = mstring_new(); + } + assert(write_mask_str && "Temp oPos flagged without a mask"); + const char *write_mask_components = write_mask_str + 1; + mstring_append_fmt(*suffix, " oPos.%s = _temp_r12.%s;\n", + write_mask_components, write_mask_components); + } + return ret; } @@ -451,7 +468,7 @@ static MString *decode_token(const uint32_t *shader_token) (vsh_get_field(shader_token, FLD_C_R_HIGH) << 2) | vsh_get_field(shader_token, FLD_C_R_LOW)); - MString *mac_suffix = NULL; + MString *suffix = NULL; if (mac != MAC_NOP) { MString *inputs_mac = mstring_new(); if (mac_opcode_params[mac].A) { @@ -485,7 +502,7 @@ static MString *decode_token(const uint32_t *shader_token) vsh_get_field(shader_token, FLD_OUT_MAC_MASK), mac_opcode[mac], mstring_get_str(inputs_mac), - &mac_suffix); + &suffix); mstring_unref(inputs_mac); } else { ret = mstring_new(); @@ -502,7 +519,7 @@ static MString *decode_token(const uint32_t *shader_token) vsh_get_field(shader_token, FLD_OUT_ILU_MASK), ilu_opcode[ilu], mstring_get_str(inputs_c), - NULL); + &suffix); mstring_append(ret, mstring_get_str(ilu_op)); @@ -512,9 +529,9 @@ static MString *decode_token(const uint32_t *shader_token) mstring_unref(input_c); - if (mac_suffix) { - mstring_append(ret, mstring_get_str(mac_suffix)); - mstring_unref(mac_suffix); + if (suffix) { + mstring_append(ret, mstring_get_str(suffix)); + mstring_unref(suffix); } return ret; @@ -543,6 +560,11 @@ static const char* vsh_header = "vec4 _temp_vec;\n" "int _temp_addr;\n" + /* Used to emulate concurrency of multi-output / paired instructions that + * write to oPos and read from R12 + */ + "vec4 _temp_r12;\n" + /* See: * http://msdn.microsoft.com/en-us/library/windows/desktop/bb174703%28v=vs.85%29.aspx * https://www.opengl.org/registry/specs/NV/vertex_program1_1.txt