From ebec5e30284a6ef2c55fe4f1d1c123eb88c19a74 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 8 Apr 2025 11:03:00 -0700 Subject: [PATCH 01/39] nv2a: Fix assert when setting fog gen mode to fog_x --- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 2 +- hw/xbox/nv2a/pgraph/vsh.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 445ce73066..19ebf12f3b 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -414,7 +414,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, " float fogDistance = fogCoord;\n"); break; default: - assert(false); + assert(!"Invalid foggen mode"); break; } diff --git a/hw/xbox/nv2a/pgraph/vsh.h b/hw/xbox/nv2a/pgraph/vsh.h index 405b6c9aa6..36cac16edf 100644 --- a/hw/xbox/nv2a/pgraph/vsh.h +++ b/hw/xbox/nv2a/pgraph/vsh.h @@ -55,8 +55,6 @@ enum VshFoggen { FOGGEN_RADIAL, FOGGEN_PLANAR, FOGGEN_ABS_PLANAR, - FOGGEN_ERROR4, - FOGGEN_ERROR5, FOGGEN_FOG_X }; From 2cc926588b9fea2aa5bd5b4882cb7440c6553991 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 11 Apr 2025 03:52:24 -0700 Subject: [PATCH 02/39] nv2a/gl: Fix COLOR_LE_G8B8 GL surface format type --- hw/xbox/nv2a/pgraph/gl/constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/pgraph/gl/constants.h b/hw/xbox/nv2a/pgraph/gl/constants.h index d78b0054e3..e3e78df8d1 100644 --- a/hw/xbox/nv2a/pgraph/gl/constants.h +++ b/hw/xbox/nv2a/pgraph/gl/constants.h @@ -298,7 +298,7 @@ static const SurfaceFormatInfo kelvin_surface_color_format_gl_map[] = { [NV097_SET_SURFACE_FORMAT_COLOR_LE_B8] = {1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, GL_COLOR_ATTACHMENT0}, [NV097_SET_SURFACE_FORMAT_COLOR_LE_G8B8] = - {2, GL_RG8, GL_RG, GL_UNSIGNED_SHORT, GL_COLOR_ATTACHMENT0}, + {2, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, GL_COLOR_ATTACHMENT0}, }; static const SurfaceFormatInfo kelvin_surface_zeta_float_format_gl_map[] = { From 1f876ce0da6e1b6b2396db494e10b8c0b5db8bb0 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 16 Apr 2025 09:22:56 -0700 Subject: [PATCH 03/39] build.sh: Add xemu_version fallback to macOS builds --- build.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index d84b67ee4b..81fca2fb59 100755 --- a/build.sh +++ b/build.sh @@ -66,8 +66,14 @@ package_macos() { cp Info.plist dist/xemu.app/Contents/ - plutil -replace CFBundleShortVersionString -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist - plutil -replace CFBundleVersion -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist + if [[ -e "${project_source_dir}/XEMU_VERSION" ]]; then + xemu_version="$(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-)" + else + xemu_version="0.0.0" + fi + + plutil -replace CFBundleShortVersionString -string "${xemu_version}" dist/xemu.app/Contents/Info.plist + plutil -replace CFBundleVersion -string "${xemu_version}" dist/xemu.app/Contents/Info.plist codesign --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - "${exe_path}" python3 ./scripts/gen-license.py --version-file=macos-libs/$target_arch/INSTALLED > dist/LICENSE.txt From 86c85023e6f970a20c559f717a21dc263ed69dd6 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 16 Apr 2025 08:15:07 -0700 Subject: [PATCH 04/39] nv2a: Handle SET_FOG_COORD and SET_WEIGHT* commands --- hw/xbox/nv2a/nv2a_regs.h | 5 ++++ hw/xbox/nv2a/pgraph/methods.h.inc | 5 ++++ hw/xbox/nv2a/pgraph/pgraph.c | 47 ++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index fa62536181..99fd3ee526 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1099,6 +1099,11 @@ # define NV097_SET_TEXCOORD3_4F 0x00001620 # define NV097_SET_TEXCOORD3_2S 0x00001610 # define NV097_SET_TEXCOORD3_4S 0x00001630 +# define NV097_SET_FOG_COORD 0x00001698 +# define NV097_SET_WEIGHT1F 0x0000169C +# define NV097_SET_WEIGHT2F 0x000016A0 +# define NV097_SET_WEIGHT3F 0x000016B0 +# define NV097_SET_WEIGHT4F 0x000016C0 # define NV097_SET_VERTEX_DATA_ARRAY_OFFSET 0x00001720 # define NV097_SET_VERTEX_DATA_ARRAY_FORMAT 0x00001760 # define NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE 0x0000000F diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index fd6184426e..d2892c5501 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -134,6 +134,11 @@ DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_2F, 2) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_4F, 4) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_2S, 1) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_4S, 2) +DEF_METHOD(NV097, SET_FOG_COORD) +DEF_METHOD(NV097, SET_WEIGHT1F) +DEF_METHOD_RANGE(NV097, SET_WEIGHT2F, 2) +DEF_METHOD_RANGE(NV097, SET_WEIGHT3F, 3) +DEF_METHOD_RANGE(NV097, SET_WEIGHT4F, 4) DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA_ARRAY_FORMAT, 16) DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA_ARRAY_OFFSET, 16) DEF_METHOD(NV097, SET_LOGIC_OP_ENABLE) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index 0f17fde184..14de8a78ff 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -2010,6 +2010,26 @@ DEF_METHOD_INC(NV097, SET_VERTEX4F) } } +DEF_METHOD(NV097, SET_FOG_COORD) +{ + VertexAttribute *attribute = &pg->vertex_attributes[NV2A_VERTEX_ATTR_FOG]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_FOG); + attribute->inline_value[0] = *(float*)¶meter; + attribute->inline_value[1] = attribute->inline_value[0]; + attribute->inline_value[2] = attribute->inline_value[0]; + attribute->inline_value[3] = attribute->inline_value[0]; +} + +DEF_METHOD(NV097, SET_WEIGHT1F) +{ + VertexAttribute *attribute = &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[0] = *(float*)¶meter; + attribute->inline_value[1] = 0.f; + attribute->inline_value[2] = 0.f; + attribute->inline_value[3] = 1.f; +} + DEF_METHOD_INC(NV097, SET_NORMAL3S) { int slot = (method - NV097_SET_NORMAL3S) / 4; @@ -2144,7 +2164,6 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD1_4F) SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD1_4F, NV2A_VERTEX_ATTR_TEXTURE1); } - DEF_METHOD_INC(NV097, SET_TEXCOORD2_4F) { SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD2_4F, NV2A_VERTEX_ATTR_TEXTURE2); @@ -2155,8 +2174,34 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD3_4F) SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD3_4F, NV2A_VERTEX_ATTR_TEXTURE3); } +DEF_METHOD_INC(NV097, SET_WEIGHT4F) +{ + SET_VERTEX_ATTRIBUTE_F(NV097_SET_WEIGHT4F, NV2A_VERTEX_ATTR_WEIGHT); +} + #undef SET_VERTEX_ATTRIBUTE_F +DEF_METHOD_INC(NV097, SET_WEIGHT2F) +{ + int slot = (method - NV097_SET_WEIGHT2F) / 4; + VertexAttribute *attribute = + &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[slot] = *(float*)¶meter; + attribute->inline_value[2] = 0.0f; + attribute->inline_value[3] = 1.0f; +} + +DEF_METHOD_INC(NV097, SET_WEIGHT3F) +{ + int slot = (method - NV097_SET_WEIGHT3F) / 4; + VertexAttribute *attribute = + &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[slot] = *(float*)¶meter; + attribute->inline_value[3] = 1.0f; +} + #define SET_VERTEX_ATRIBUTE_TEX_2F(command, attr_index) \ do { \ int slot = (method - (command)) / 4; \ From 7a34eedd6fe699dc7d55686e005d1d4205e8ad8c Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 26 Mar 2025 14:10:06 -0700 Subject: [PATCH 05/39] nv2a: Partially handle SET_LIGHT_CONTROL --- hw/xbox/nv2a/nv2a_regs.h | 7 +++++++ hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 20 ++++++++++++++++---- hw/xbox/nv2a/pgraph/glsl/vsh.c | 7 +++++++ hw/xbox/nv2a/pgraph/methods.h.inc | 1 + hw/xbox/nv2a/pgraph/pgraph.c | 12 ++++++++++++ hw/xbox/nv2a/pgraph/shaders.c | 5 +++++ hw/xbox/nv2a/pgraph/shaders.h | 3 +++ 7 files changed, 51 insertions(+), 4 deletions(-) diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 99fd3ee526..0990d635fe 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -315,11 +315,14 @@ #define NV_PGRAPH_CSV0_C 0x00000FB8 # define NV_PGRAPH_CSV0_C_CHEOPS_PROGRAM_START 0x0000FF00 # define NV_PGRAPH_CSV0_C_SPECULAR_ENABLE (1 << 16) +# define NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR (1 << 17) +# define NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR (1 << 18) # define NV_PGRAPH_CSV0_C_SPECULAR (3 << 19) # define NV_PGRAPH_CSV0_C_DIFFUSE (3 << 21) # define NV_PGRAPH_CSV0_C_AMBIENT (3 << 23) # define NV_PGRAPH_CSV0_C_EMISSION (3 << 25) # define NV_PGRAPH_CSV0_C_NORMALIZATION_ENABLE (1 << 27) +# define NV_PGRAPH_CSV0_C_LOCALEYE (1 << 30) # define NV_PGRAPH_CSV0_C_LIGHTING (1 << 31) #define NV_PGRAPH_CSV1_B 0x00000FBC #define NV_PGRAPH_CSV1_A 0x00000FC0 @@ -882,6 +885,10 @@ # define NV097_SET_CONTROL0_STENCIL_WRITE_ENABLE (1 << 0) # define NV097_SET_CONTROL0_Z_FORMAT (1 << 12) # define NV097_SET_CONTROL0_Z_PERSPECTIVE_ENABLE (1 << 16) +# define NV097_SET_LIGHT_CONTROL 0x00000294 +# define NV097_SET_LIGHT_CONTROL_SEPARATE_SPECULAR 1 +# define NV097_SET_LIGHT_CONTROL_LOCALEYE (1 << 16) +# define NV097_SET_LIGHT_CONTROL_ALPHA_FROM_MATERIAL_SPECULAR (1 << 17) # define NV097_SET_COLOR_MATERIAL 0x00000298 # define NV097_SET_FOG_MODE 0x0000029C # define NV097_SET_FOG_MODE_V_LINEAR 0x2601 diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 19ebf12f3b..52e6296791 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -384,13 +384,25 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, " oD1 = specular;\n"); } - if (!state->specular_enable) { - mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); - } - mstring_append(body, " oB0 = backDiffuse;\n"); mstring_append(body, " oB1 = backSpecular;\n"); + if (!state->specular_enable) { + mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); + mstring_append(body, " oB1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); + } else { + if (!state->separate_specular) { + mstring_append(body, " oD1 = specular;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } + if (state->ignore_specular_alpha) { + mstring_append(body, + " oD1.w = 1.0;\n" + " oB1.a = 1.0;\n" + ); + } + } + /* Fog */ if (state->fog_enable) { diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index 5fff5cf410..25c846bbde 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -256,6 +256,13 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) " vtxD1 = clamp(oD1, 0.0, 1.0);\n" " vtxB1 = clamp(oB1, 0.0, 1.0);\n" ); + + if (state->ignore_specular_alpha) { + mstring_append(body, + " vtxD1.w = 1.0;\n" + " vtxB1.w = 1.0;\n" + ); + } } else { mstring_append(body, " vtxD1 = vec4(0.0, 0.0, 0.0, 1.0);\n" diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index d2892c5501..0b3c014e7d 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -27,6 +27,7 @@ DEF_METHOD(NV097, SET_COMBINER_SPECULAR_FOG_CW0) DEF_METHOD(NV097, SET_COMBINER_SPECULAR_FOG_CW1) DEF_METHOD_CASE_4(NV097, SET_TEXTURE_ADDRESS, 64) DEF_METHOD(NV097, SET_CONTROL0) +DEF_METHOD(NV097, SET_LIGHT_CONTROL) DEF_METHOD(NV097, SET_COLOR_MATERIAL) DEF_METHOD(NV097, SET_FOG_MODE) DEF_METHOD(NV097, SET_FOG_GEN_MODE) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index 14de8a78ff..fa6d734405 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -1075,6 +1075,18 @@ DEF_METHOD(NV097, SET_CONTROL0) z_perspective); } +DEF_METHOD(NV097, SET_LIGHT_CONTROL) +{ + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR, + (parameter & NV097_SET_LIGHT_CONTROL_SEPARATE_SPECULAR) != 0); + + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_LOCALEYE, + (parameter & NV097_SET_LIGHT_CONTROL_LOCALEYE) != 0); + + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR, + (parameter & NV097_SET_LIGHT_CONTROL_ALPHA_FROM_MATERIAL_SPECULAR) != 0); +} + DEF_METHOD(NV097, SET_COLOR_MATERIAL) { PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_EMISSION, diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c index dce6d05bb3..ce80dc127a 100644 --- a/hw/xbox/nv2a/pgraph/shaders.c +++ b/hw/xbox/nv2a/pgraph/shaders.c @@ -92,6 +92,11 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SPECULAR); } + state.separate_specular = GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR); + state.ignore_specular_alpha = !GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR); + /* vertex program stuff */ state.vertex_program = vertex_program, state.z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h index 7ff93a6302..9496cbbea2 100644 --- a/hw/xbox/nv2a/pgraph/shaders.h +++ b/hw/xbox/nv2a/pgraph/shaders.h @@ -79,6 +79,9 @@ typedef struct ShaderState { enum MaterialColorSource diffuse_src; enum MaterialColorSource specular_src; + bool separate_specular; + bool ignore_specular_alpha; + bool lighting; enum VshLight light[NV2A_MAX_LIGHTS]; From 69c8df2a3e3e342e3ef99c817638b6c7793bd6fb Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Sat, 29 Mar 2025 11:40:57 -0700 Subject: [PATCH 06/39] nv2a: Partial implementation of SET_SPECULAR_PARAMS --- hw/xbox/nv2a/nv2a_regs.h | 2 + hw/xbox/nv2a/pgraph/gl/renderer.h | 1 + hw/xbox/nv2a/pgraph/gl/shaders.c | 7 ++ hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 66 ++++++++++------- hw/xbox/nv2a/pgraph/methods.h.inc | 2 + hw/xbox/nv2a/pgraph/pgraph.c | 118 ++++++++++++++++++++++++++++++ hw/xbox/nv2a/pgraph/pgraph.h | 5 ++ hw/xbox/nv2a/pgraph/shaders.c | 3 + hw/xbox/nv2a/pgraph/shaders.h | 2 + hw/xbox/nv2a/pgraph/vk/renderer.h | 1 + 10 files changed, 181 insertions(+), 26 deletions(-) diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 0990d635fe..0c0fb70eef 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1056,6 +1056,7 @@ # define NV097_SET_TEXGEN_VIEW_MODEL_LOCAL_VIEWER 0 # define NV097_SET_TEXGEN_VIEW_MODEL_INFINITE_VIEWER 1 # define NV097_SET_FOG_PLANE 0x000009D0 +# define NV097_SET_SPECULAR_PARAMS 0x000009E0 # define NV097_SET_SCENE_AMBIENT_COLOR 0x00000A10 # define NV097_SET_VIEWPORT_OFFSET 0x00000A20 # define NV097_SET_POINT_PARAMS 0x00000A30 @@ -1264,6 +1265,7 @@ # define NV097_SET_CLEAR_RECT_HORIZONTAL 0x00001D98 # define NV097_SET_CLEAR_RECT_VERTICAL 0x00001D9C # define NV097_SET_SPECULAR_FOG_FACTOR 0x00001E20 +# define NV097_SET_SPECULAR_PARAMS_BACK 0x00001E28 # define NV097_SET_COMBINER_COLOR_OCW 0x00001E40 # define NV097_SET_COMBINER_CONTROL 0x00001E60 # define NV097_SET_SHADOW_ZSLOPE_THRESHOLD 0x00001E68 diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 3529006898..89ed6ecfd3 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -122,6 +122,7 @@ typedef struct ShaderBinding { GLint light_infinite_direction_loc[NV2A_MAX_LIGHTS]; GLint light_local_position_loc[NV2A_MAX_LIGHTS]; GLint light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; GLint clip_region_loc[8]; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index ad1c21f4a2..ad2f3e1b9b 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -193,8 +193,11 @@ static void update_shader_constant_locations(ShaderBinding *binding) if (binding->state.fixed_function) { binding->material_alpha_loc = glGetUniformLocation(binding->gl_program, "material_alpha"); + binding->specular_power_loc = + glGetUniformLocation(binding->gl_program, "specularPower"); } else { binding->material_alpha_loc = -1; + binding->specular_power_loc = -1; } } @@ -836,6 +839,10 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, } } + if (binding->specular_power_loc != -1) { + glUniform1f(binding->specular_power_loc, pg->specular_power); + } + /* estimate the viewport by assuming it matches the surface ... */ unsigned int aa_width = 1, aa_height = 1; pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 52e6296791..a26c1fa17f 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -230,10 +230,16 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } /* Lighting */ - if (state->lighting) { - + if (!state->lighting) { + mstring_append(body, " oD0 = diffuse;\n"); + mstring_append(body, " oD1 = specular;\n"); + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } else { //FIXME: Do 2 passes if we want 2 sided-lighting? + mstring_append_fmt(uniforms, "%sfloat specularPower;\n", u); + static char alpha_source_diffuse[] = "diffuse.a"; static char alpha_source_specular[] = "specular.a"; static char alpha_source_material[] = "material_alpha"; @@ -269,12 +275,6 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz continue; } - /* FIXME: It seems that we only have to handle the surface colors if - * they are not part of the material [= vertex colors]. - * If they are material the cpu will premultiply light - * colors - */ - mstring_append_fmt(body, "/* Light %d */ {\n", i); if (state->light[i] == LIGHT_LOCAL @@ -310,14 +310,10 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz u, i, u, i); mstring_append_fmt(body, " float attenuation = 1.0;\n" - " float nDotVP = max(0.0, dot(tNormal, normalize(vec3(lightInfiniteDirection%d))));\n" - " float nDotHV = max(0.0, dot(tNormal, vec3(lightInfiniteHalfVector%d)));\n", + " float nDotVP = max(0.0, dot(tNormal, normalize(lightInfiniteDirection%d)));\n" + " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", i, i); - /* FIXME: Do specular */ - - /* FIXME: tBackDiffuse */ - break; case LIGHT_LOCAL: /* Everything done already */ @@ -349,11 +345,11 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " if (nDotVP == 0.0) {\n" " pf = 0.0;\n" " } else {\n" - " pf = pow(nDotHV, /* specular(l, m, n, l1, m1, n1) */ 0.001);\n" + " pf = pow(nDotHV, specularPower);\n" " }\n" " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" - " vec3 lightSpecular = lightSpecularColor(%d) * pf;\n", + " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", i, i, i); mstring_append(body, @@ -374,26 +370,44 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz break; } - mstring_append(body, - " oD1.xyz += specular.xyz * lightSpecular;\n"); + switch (state->specular_src) { + case MATERIAL_COLOR_SRC_MATERIAL: + mstring_append(body, + " oD1.xyz += lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_DIFFUSE: + mstring_append(body, + " oD1.xyz += diffuse.xyz * lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_SPECULAR: + mstring_append(body, + " oD1.xyz += specular.xyz * lightSpecular;\n"); + break; + } mstring_append(body, "}\n"); } - } else { - mstring_append(body, " oD0 = diffuse;\n"); - mstring_append(body, " oD1 = specular;\n"); - } - mstring_append(body, " oB0 = backDiffuse;\n"); - mstring_append(body, " oB1 = backSpecular;\n"); + /* TODO: Implement two-sided lighting */ + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } if (!state->specular_enable) { mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); mstring_append(body, " oB1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); } else { if (!state->separate_specular) { - mstring_append(body, " oD1 = specular;\n"); - mstring_append(body, " oB1 = backSpecular;\n"); + if (state->lighting) { + mstring_append(body, + " oD0.xyz += oD1.xyz;\n" + " oB0.xyz += oB1.xyz;\n" + ); + } + mstring_append(body, + " oD1 = specular;\n" + " oB1 = backSpecular;\n" + ); } if (state->ignore_specular_alpha) { mstring_append(body, diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index 0b3c014e7d..2f7b7b4198 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -96,6 +96,7 @@ DEF_METHOD_RANGE(NV097, SET_FOG_PARAMS, 3) DEF_METHOD_RANGE(NV097, SET_TEXGEN_PLANE_S, 4*4*4) DEF_METHOD(NV097, SET_TEXGEN_VIEW_MODEL) DEF_METHOD_RANGE(NV097, SET_FOG_PLANE, 4) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS, 6) DEF_METHOD_RANGE(NV097, SET_SCENE_AMBIENT_COLOR, 3) DEF_METHOD_RANGE(NV097, SET_VIEWPORT_OFFSET, 4) DEF_METHOD_RANGE(NV097, SET_POINT_PARAMS, 8) @@ -183,6 +184,7 @@ DEF_METHOD(NV097, CLEAR_SURFACE) DEF_METHOD(NV097, SET_CLEAR_RECT_HORIZONTAL) DEF_METHOD(NV097, SET_CLEAR_RECT_VERTICAL) DEF_METHOD_RANGE(NV097, SET_SPECULAR_FOG_FACTOR, 2) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS_BACK, 6) DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) DEF_METHOD_RANGE(NV097, SET_COMBINER_COLOR_OCW, 8) DEF_METHOD(NV097, SET_COMBINER_CONTROL) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index fa6d734405..feb5f858ca 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -19,6 +19,8 @@ * License along with this library; if not, see . */ +#include + #include "hw/xbox/nv2a/nv2a_int.h" #include "ui/xemu-notifications.h" #include "ui/xemu-settings.h" @@ -1803,6 +1805,113 @@ DEF_METHOD_INC(NV097, SET_FOG_PLANE) pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_FOG] = true; } +struct CurveCoefficients { + float a; + float b; + float c; +}; + +static const struct CurveCoefficients curve_coefficients[] = { + {1.000108475163, -9.838607076280, 54.829089549713}, + {1.199164441703, -3.292603784852, 7.799987995214}, + {8.653441252033, 29.189473787191, 43.586027561823}, + {-531.307758450301, 117.398468683934, 113.155490738338}, + {-4.662713151292, 1.221108944572, 1.217360986939}, + {-124.435242105211, 35.401219563514, 35.408114377045}, + {10672560.259502287954, 21565843.555823743343, 10894794.336297152564}, + {-51973801.463933646679, -104199997.554352939129, -52225454.356278456748}, + {972270.324080004124, 2025882.096547174733, 1054898.052467488218}, +}; + +static const float kCoefficient0StepPoints[] = { + -0.022553957999, // power = 1.25 + -0.421539008617, // power = 4.00 + -0.678715527058, // power = 9.00 + -0.838916420937, // power = 20.00 + -0.961754500866, // power = 90.00 + -0.990773200989, // power = 375.00 + -0.994858562946, // power = 650.00 + -0.996561050415, // power = 1000.00 + -0.999547004700, // power = 1250.00 +}; + +static float reconstruct_quadratic(float c0, const struct CurveCoefficients *coefficients) { + return coefficients->a + coefficients->b * c0 + coefficients->c * c0 * c0; +} + +static float reconstruct_saturation_growth_rate(float c0, const struct CurveCoefficients *coefficients) { + return (coefficients->a * c0) / (coefficients->b + coefficients->c * c0); +} + +static float (* const reconstruct_func_map[])(float, const struct CurveCoefficients *) = { + reconstruct_quadratic, // 1.0..1.25 max error 0.01 % + reconstruct_quadratic, // 1.25..4.0 max error 2.2 % + reconstruct_quadratic, // 4.0..9.0 max error 2.3 % + reconstruct_saturation_growth_rate, // 9.0..20.0 max error 1.4 % + reconstruct_saturation_growth_rate, // 20.0..90.0 max error 2.1 % + reconstruct_saturation_growth_rate, // 90.0..375.0 max error 2.8% + reconstruct_quadratic, // 375..650 max error 1.0 % + reconstruct_quadratic, // 650..1000 max error 1.7% + reconstruct_quadratic, // 1000..1250 max error 1.0% +}; + +static float reconstruct_specular_power(const float *params) { + // See https://github.com/dracc/xgu/blob/db3172d8c983629f0dc971092981846da22438ae/xgux.h#L279 + + // Values < 1.0 will result in a positive c1 and (c2 - c0 * 2) will be very + // close to the original value. + if (params[1] > 0.0f && params[2] < 1.0f) { + return params[2] - (params[0] * 2.0f); + } + + float c0 = params[0]; + float c3 = params[3]; + // FIXME: This handling is not correct, but is distinct without crashing. + // It does not appear possible for a DirectX-generated value to be positive, + // so while this differs from hardware behavior, it may be irrelevant in + // practice. + if (c0 > 0.0f || c3 > 0.0f) { + return 0.0001f; + } + + float reconstructed_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c0 > kCoefficient0StepPoints[i]) { + reconstructed_power = reconstruct_func_map[i](c0, &curve_coefficients[i]); + break; + } + } + + float reconstructed_half_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c3 > kCoefficient0StepPoints[i]) { + reconstructed_half_power = reconstruct_func_map[i](c3, &curve_coefficients[i]); + break; + } + } + + // The range can be extended beyond 1250 by using the half power params. This + // will only work for DirectX generated values, arbitrary params could + // erroneously trigger this. + // + // There are some very low power (~1) values that have inverted powers, but + // they are easily identified by comparatively high c0 parameters. + if (reconstructed_power == 0.f || (reconstructed_half_power > reconstructed_power && c0 < -0.1f)) { + return reconstructed_half_power * 2.f; + } + + return reconstructed_power; +} + +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS) / 4; + pg->specular_params[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power = reconstruct_specular_power(pg->specular_params); + } +} + DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR) { int slot = (method - NV097_SET_SCENE_AMBIENT_COLOR) / 4; @@ -2785,6 +2894,15 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_FOG_FACTOR) pgraph_reg_w(pg, NV_PGRAPH_SPECFOGFACTOR0 + slot*4, parameter); } +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS_BACK) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS_BACK) / 4; + pg->specular_params_back[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power_back = reconstruct_specular_power(pg->specular_params_back); + } +} + DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) { pgraph_reg_w(pg, NV_PGRAPH_SHADERCLIPMODE, parameter); diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 64b671e71d..c74e370bba 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -197,6 +197,11 @@ typedef struct PGRAPHState { float light_local_position[NV2A_MAX_LIGHTS][3]; float light_local_attenuation[NV2A_MAX_LIGHTS][3]; + float specular_params[6]; + float specular_power; + float specular_params_back[6]; + float specular_power_back; + float point_params[8]; VertexAttribute vertex_attributes[NV2A_VERTEXSHADER_ATTRIBUTES]; diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c index ce80dc127a..79f66026d9 100644 --- a/hw/xbox/nv2a/pgraph/shaders.c +++ b/hw/xbox/nv2a/pgraph/shaders.c @@ -97,6 +97,9 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) state.ignore_specular_alpha = !GET_MASK( pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR); + state.specular_power = pg->specular_power; + state.specular_power_back = pg->specular_power_back; + /* vertex program stuff */ state.vertex_program = vertex_program, state.z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h index 9496cbbea2..b384b679bf 100644 --- a/hw/xbox/nv2a/pgraph/shaders.h +++ b/hw/xbox/nv2a/pgraph/shaders.h @@ -81,6 +81,8 @@ typedef struct ShaderState { bool separate_specular; bool ignore_specular_alpha; + float specular_power; + float specular_power_back; bool lighting; enum VshLight light[NV2A_MAX_LIGHTS]; diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index eb0726ac2f..d728805475 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -190,6 +190,7 @@ typedef struct ShaderBinding { int light_infinite_direction_loc[NV2A_MAX_LIGHTS]; int light_local_position_loc[NV2A_MAX_LIGHTS]; int light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; int clip_region_loc; From 34ed0f75dea71a4479bacb4ae73207dee5d41e83 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Sun, 30 Mar 2025 18:08:02 -0700 Subject: [PATCH 07/39] nv2a: Handle LOCAL_RANGE --- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 84 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index a26c1fa17f..74ee67c4ad 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -287,15 +287,15 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append_fmt(body, " vec3 VP = lightLocalPosition%d - tPosition.xyz/tPosition.w;\n" " float d = length(VP);\n" -//FIXME: if (d > lightLocalRange) { .. don't process this light .. } /* inclusive?! */ - what about directional lights? - " VP = normalize(VP);\n" - " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n" - " + lightLocalAttenuation%d.y * d\n" - " + lightLocalAttenuation%d.z * d * d);\n" - " vec3 halfVector = normalize(VP + eyePosition.xyz / eyePosition.w);\n" /* FIXME: Not sure if eyePosition is correct */ - " float nDotVP = max(0.0, dot(tNormal, VP));\n" - " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", - i, i, i, i); + " if (d <= lightLocalRange(%d)) {\n" /* FIXME: Double check that range is inclusive */ + " VP = normalize(VP);\n" + " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n" + " + lightLocalAttenuation%d.y * d\n" + " + lightLocalAttenuation%d.z * d * d);\n" + " vec3 halfVector = normalize(VP + eyePosition.xyz / eyePosition.w);\n" /* FIXME: Not sure if eyePosition is correct */ + " float nDotVP = max(0.0, dot(tNormal, VP));\n" + " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", + i, i, i, i, i); } @@ -309,9 +309,10 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz "%svec3 lightInfiniteDirection%d;\n", u, i, u, i); mstring_append_fmt(body, - " float attenuation = 1.0;\n" - " float nDotVP = max(0.0, dot(tNormal, normalize(lightInfiniteDirection%d)));\n" - " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", + " {\n" + " float attenuation = 1.0;\n" + " float nDotVP = max(0.0, dot(tNormal, normalize(lightInfiniteDirection%d)));\n" + " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", i, i); break; @@ -321,18 +322,18 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz case LIGHT_SPOT: /* https://docs.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor#spotlight-factor */ mstring_append_fmt(body, - " vec4 spotDir = lightSpotDirection(%d);\n" - " float invScale = 1/length(spotDir.xyz);\n" - " float cosHalfPhi = -invScale*spotDir.w;\n" - " float cosHalfTheta = invScale + cosHalfPhi;\n" - " float spotDirDotVP = dot(spotDir.xyz, VP);\n" - " float rho = invScale*spotDirDotVP;\n" - " if (rho > cosHalfTheta) {\n" - " } else if (rho <= cosHalfPhi) {\n" - " attenuation = 0.0;\n" - " } else {\n" - " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */ - " }\n", + " vec4 spotDir = lightSpotDirection(%d);\n" + " float invScale = 1/length(spotDir.xyz);\n" + " float cosHalfPhi = -invScale*spotDir.w;\n" + " float cosHalfTheta = invScale + cosHalfPhi;\n" + " float spotDirDotVP = dot(spotDir.xyz, VP);\n" + " float rho = invScale*spotDirDotVP;\n" + " if (rho > cosHalfTheta) {\n" + " } else if (rho <= cosHalfPhi) {\n" + " attenuation = 0.0;\n" + " } else {\n" + " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */ + " }\n", i); break; default: @@ -341,51 +342,52 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } mstring_append_fmt(body, - " float pf;\n" - " if (nDotVP == 0.0) {\n" - " pf = 0.0;\n" - " } else {\n" - " pf = pow(nDotHV, specularPower);\n" - " }\n" - " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" - " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" - " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", + " float pf;\n" + " if (nDotVP == 0.0) {\n" + " pf = 0.0;\n" + " } else {\n" + " pf = pow(nDotHV, specularPower);\n" + " }\n" + " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" + " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" + " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", i, i, i); mstring_append(body, - " oD0.xyz += lightAmbient;\n"); + " oD0.xyz += lightAmbient;\n"); switch (state->diffuse_src) { case MATERIAL_COLOR_SRC_MATERIAL: mstring_append(body, - " oD0.xyz += lightDiffuse;\n"); + " oD0.xyz += lightDiffuse;\n"); break; case MATERIAL_COLOR_SRC_DIFFUSE: mstring_append(body, - " oD0.xyz += diffuse.xyz * lightDiffuse;\n"); + " oD0.xyz += diffuse.xyz * lightDiffuse;\n"); break; case MATERIAL_COLOR_SRC_SPECULAR: mstring_append(body, - " oD0.xyz += specular.xyz * lightDiffuse;\n"); + " oD0.xyz += specular.xyz * lightDiffuse;\n"); break; } switch (state->specular_src) { case MATERIAL_COLOR_SRC_MATERIAL: mstring_append(body, - " oD1.xyz += lightSpecular;\n"); + " oD1.xyz += lightSpecular;\n"); break; case MATERIAL_COLOR_SRC_DIFFUSE: mstring_append(body, - " oD1.xyz += diffuse.xyz * lightSpecular;\n"); + " oD1.xyz += diffuse.xyz * lightSpecular;\n"); break; case MATERIAL_COLOR_SRC_SPECULAR: mstring_append(body, - " oD1.xyz += specular.xyz * lightSpecular;\n"); + " oD1.xyz += specular.xyz * lightSpecular;\n"); break; } - mstring_append(body, "}\n"); + mstring_append(body, " }\n" + "}\n"); } /* TODO: Implement two-sided lighting */ From 679f6d06bd61ef8cb5c09c663c63bab36e76b899 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Sun, 30 Mar 2025 21:20:18 -0700 Subject: [PATCH 08/39] nv2a: Handle LOCALEYE light control --- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 35 +++++++++++++++++++++++-------- hw/xbox/nv2a/pgraph/shaders.c | 2 ++ hw/xbox/nv2a/pgraph/shaders.h | 1 + 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 74ee67c4ad..f1a2956ca0 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -270,6 +270,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, "oD1 = vec4(0.0, 0.0, 0.0, specular.a);\n"); + if (state->local_eye) { + mstring_append(body, + "vec3 VPeye = normalize(eyePosition.xyz / eyePosition.w - tPosition.xyz / tPosition.w);\n" + ); + } + for (i = 0; i < NV2A_MAX_LIGHTS; i++) { if (state->light[i] == LIGHT_OFF) { continue; @@ -285,18 +291,20 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz "%svec3 lightLocalAttenuation%d;\n", u, i, u, i); mstring_append_fmt(body, - " vec3 VP = lightLocalPosition%d - tPosition.xyz/tPosition.w;\n" + " vec3 tPos = tPosition.xyz/tPosition.w;\n" + " vec3 VP = lightLocalPosition%d - tPos;\n" " float d = length(VP);\n" " if (d <= lightLocalRange(%d)) {\n" /* FIXME: Double check that range is inclusive */ " VP = normalize(VP);\n" " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n" " + lightLocalAttenuation%d.y * d\n" " + lightLocalAttenuation%d.z * d * d);\n" - " vec3 halfVector = normalize(VP + eyePosition.xyz / eyePosition.w);\n" /* FIXME: Not sure if eyePosition is correct */ + " vec3 halfVector = normalize(VP + %s);\n" " float nDotVP = max(0.0, dot(tNormal, VP));\n" " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", - i, i, i, i, i); - + i, i, i, i, i, + state->local_eye ? "VPeye" : "vec3(0.0, 0.0, 0.0)" + ); } switch(state->light[i]) { @@ -311,10 +319,19 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append_fmt(body, " {\n" " float attenuation = 1.0;\n" - " float nDotVP = max(0.0, dot(tNormal, normalize(lightInfiniteDirection%d)));\n" - " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", - i, i); - + " vec3 lightDirection = normalize(lightInfiniteDirection%d);\n" + " float nDotVP = max(0.0, dot(tNormal, lightDirection));\n", + i); + if (state->local_eye) { + mstring_append(body, + " float nDotHV = max(0.0, dot(tNormal, normalize(lightDirection + VPeye)));\n" + ); + } else { + mstring_append_fmt(body, + " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", + i + ); + } break; case LIGHT_LOCAL: /* Everything done already */ @@ -413,7 +430,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } if (state->ignore_specular_alpha) { mstring_append(body, - " oD1.w = 1.0;\n" + " oD1.a = 1.0;\n" " oB1.a = 1.0;\n" ); } diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c index 79f66026d9..cddbf450f0 100644 --- a/hw/xbox/nv2a/pgraph/shaders.c +++ b/hw/xbox/nv2a/pgraph/shaders.c @@ -96,6 +96,8 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR); state.ignore_specular_alpha = !GET_MASK( pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR); + state.local_eye = GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_LOCALEYE); state.specular_power = pg->specular_power; state.specular_power_back = pg->specular_power_back; diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h index b384b679bf..4cc07e3a9d 100644 --- a/hw/xbox/nv2a/pgraph/shaders.h +++ b/hw/xbox/nv2a/pgraph/shaders.h @@ -81,6 +81,7 @@ typedef struct ShaderState { bool separate_specular; bool ignore_specular_alpha; + bool local_eye; float specular_power; float specular_power_back; From 5685a6290cfbf7b022ec5e58a8ffb09f664c04e8 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Wed, 16 Apr 2025 20:15:16 -0700 Subject: [PATCH 09/39] nv2a/vk: Set specular power uniform --- hw/xbox/nv2a/pgraph/vk/shaders.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hw/xbox/nv2a/pgraph/vk/shaders.c b/hw/xbox/nv2a/pgraph/vk/shaders.c index 421a81ba60..5d6e345ffb 100644 --- a/hw/xbox/nv2a/pgraph/vk/shaders.c +++ b/hw/xbox/nv2a/pgraph/vk/shaders.c @@ -313,6 +313,9 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->uniform_attrs_loc = uniform_index(&binding->vertex->uniforms, "inlineValue"); + + binding->specular_power_loc = + uniform_index(&binding->vertex->uniforms, "specularPower"); } static void shader_cache_entry_init(Lru *lru, LruNode *node, void *state) @@ -607,6 +610,11 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, } } + if (binding->specular_power_loc != -1) { + uniform1f(&binding->vertex->uniforms, binding->specular_power_loc, + pg->specular_power); + } + /* estimate the viewport by assuming it matches the surface ... */ unsigned int aa_width = 1, aa_height = 1; pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); From 270dbe01ea6b36c38f8cf2bc6a66449caa719a01 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 17 Apr 2025 22:22:19 -0700 Subject: [PATCH 10/39] nv2a: Increase MAX_BATCH_LENGTH beyond highest known retail use --- hw/xbox/nv2a/nv2a_regs.h | 13 ++++++++++++- hw/xbox/nv2a/pgraph/pgraph.c | 6 +++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 0c0fb70eef..2bdac1d93f 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1467,7 +1467,18 @@ #define NV2A_NUM_SUBCHANNELS 8 #define NV2A_CACHE1_SIZE 128 -#define NV2A_MAX_BATCH_LENGTH 0x1FFFF +/* This is a multi-use limit. Testing on an Xbox 1.0, it is possible to send + * arrays of at least 0x0FFFFF elements without issue, however sending + * NV097_DRAW_ARRAYS with a start value > 0xFFFF raises an exception implying + * that there may be a vertex limit. Since xemu uses batch length for vertex + * elements in NV097_INLINE_ARRAY the size should ideally be high enough to + * accommodate 0xFFFF vertices with maximum attributes specified. + * + * Retail games are known to send at least 0x410FA elements in a single draw, so + * a somewhat larger value is selected to balance memory use with real-world + * limits. + */ +#define NV2A_MAX_BATCH_LENGTH 0x07FFFF #define NV2A_VERTEXSHADER_ATTRIBUTES 16 #define NV2A_MAX_TEXTURES 4 diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index feb5f858ca..fc6420fff0 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -2692,7 +2692,11 @@ DEF_METHOD(NV097, DRAW_ARRAYS) int32_t count = GET_MASK(parameter, NV097_DRAW_ARRAYS_COUNT) + 1; if (pg->inline_elements_length) { - /* FIXME: Determine HW behavior for overflow case. */ + /* FIXME: HW throws an exception if the start index is > 0xFFFF. This + * would prevent this assert from firing for any reasonable choice of + * NV2A_MAX_BATCH_LENGTH (which must be larger to accommodate + * NV097_INLINE_ARRAY anyway) + */ assert((pg->inline_elements_length + count) < NV2A_MAX_BATCH_LENGTH); assert(!pg->draw_arrays_prevent_connect); From fee1e58204949b91800772590e6cbb191442f32f Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 18 Apr 2025 11:21:27 -0700 Subject: [PATCH 11/39] vmstate: Add VMSTATE_UINT32_SUB_ARRAY_V --- include/migration/vmstate.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index f313f2f408..eee7359f2d 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -1085,6 +1085,9 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_UINT32_SUB_ARRAY(_f, _s, _start, _num) \ VMSTATE_SUB_ARRAY(_f, _s, _start, _num, 0, vmstate_info_uint32, uint32_t) +#define VMSTATE_UINT32_SUB_ARRAY_V(_f, _s, _start, _num, _v) \ + VMSTATE_SUB_ARRAY(_f, _s, _start, _num, _v, vmstate_info_uint32, uint32_t) + #define VMSTATE_UINT32_2DARRAY(_f, _s, _n1, _n2) \ VMSTATE_UINT32_2DARRAY_V(_f, _s, _n1, _n2, 0) From 0c2a6178192043db8b30c4d61a96129b442102f5 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 18 Apr 2025 11:22:25 -0700 Subject: [PATCH 12/39] nv2a: Bump vmstate version for new NV2A_MAX_BATCH_LENGTH --- hw/xbox/nv2a/nv2a.c | 8 +++++--- hw/xbox/nv2a/nv2a_regs.h | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hw/xbox/nv2a/nv2a.c b/hw/xbox/nv2a/nv2a.c index 0124a0d451..02cfbca3e9 100644 --- a/hw/xbox/nv2a/nv2a.c +++ b/hw/xbox/nv2a/nv2a.c @@ -423,7 +423,7 @@ const VMStateDescription vmstate_nv2a_pgraph_vertex_attributes = { static const VMStateDescription vmstate_nv2a = { .name = "nv2a", - .version_id = 2, + .version_id = 3, .minimum_version_id = 1, .post_save = nv2a_post_save, .post_load = nv2a_post_load, @@ -507,9 +507,11 @@ static const VMStateDescription vmstate_nv2a = { VMSTATE_BOOL_ARRAY(pgraph.ltc1_dirty, NV2AState, NV2A_LTC1_COUNT), VMSTATE_STRUCT_ARRAY(pgraph.vertex_attributes, NV2AState, NV2A_VERTEXSHADER_ATTRIBUTES, 1, vmstate_nv2a_pgraph_vertex_attributes, VertexAttribute), VMSTATE_UINT32(pgraph.inline_array_length, NV2AState), - VMSTATE_UINT32_ARRAY(pgraph.inline_array, NV2AState, NV2A_MAX_BATCH_LENGTH), + VMSTATE_UINT32_SUB_ARRAY(pgraph.inline_array, NV2AState, 0, NV2A_MAX_BATCH_LENGTH_V2), + VMSTATE_UINT32_SUB_ARRAY_V(pgraph.inline_array, NV2AState, NV2A_MAX_BATCH_LENGTH_V2, NV2A_MAX_BATCH_LENGTH - NV2A_MAX_BATCH_LENGTH_V2, 3), VMSTATE_UINT32(pgraph.inline_elements_length, NV2AState), // fixme - VMSTATE_UINT32_ARRAY(pgraph.inline_elements, NV2AState, NV2A_MAX_BATCH_LENGTH), + VMSTATE_UINT32_SUB_ARRAY(pgraph.inline_elements, NV2AState, 0, NV2A_MAX_BATCH_LENGTH_V2), + VMSTATE_UINT32_SUB_ARRAY_V(pgraph.inline_elements, NV2AState, NV2A_MAX_BATCH_LENGTH_V2, NV2A_MAX_BATCH_LENGTH - NV2A_MAX_BATCH_LENGTH_V2, 3), VMSTATE_UINT32(pgraph.inline_buffer_length, NV2AState), // fixme VMSTATE_UINT32(pgraph.draw_arrays_length, NV2AState), VMSTATE_UINT32(pgraph.draw_arrays_max_count, NV2AState), diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 2bdac1d93f..c1804a2cef 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1477,8 +1477,12 @@ * Retail games are known to send at least 0x410FA elements in a single draw, so * a somewhat larger value is selected to balance memory use with real-world * limits. + * + * NV2A_MAX_BATCH_LENGTH_V2 is the previous limit, for migration. + * FIXME: Remove NV2A_MAX_BATCH_LENGTH_V2 at some point in the future. */ #define NV2A_MAX_BATCH_LENGTH 0x07FFFF +#define NV2A_MAX_BATCH_LENGTH_V2 0x1FFFF #define NV2A_VERTEXSHADER_ATTRIBUTES 16 #define NV2A_MAX_TEXTURES 4 From 1a6b858fe83e6817ed8fa931ad591bf4922b65f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 04:31:22 +0000 Subject: [PATCH 13/39] ci: bump softprops/action-gh-release from 2.2.1 to 2.2.2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda...da05d552573ad5aba039eaac05058a918a7bf631) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb2a6cc123..fb9f70df1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -420,7 +420,7 @@ jobs: run: | cp dist/xemu-win-x86_64-release-pdb/xemu-win-x86_64-release.zip dist/xemu-win-x86_64-release-pdb/xemu-win-release.zip - name: Publish release - uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 with: tag_name: v${{ env.XEMU_VERSION }} name: v${{ env.XEMU_VERSION }} From 89185e6937c7d597ba353bc4d4800dde0015ea39 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 22 Apr 2025 14:28:31 -0700 Subject: [PATCH 14/39] nv2a/psh: Fix default alpha for unbound texture samplers --- hw/xbox/nv2a/pgraph/glsl/psh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index e3a566a347..448281f46c 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -949,7 +949,7 @@ static MString* psh_convert(struct PixelShader *ps) switch (ps->tex_modes[i]) { case PS_TEXTUREMODES_NONE: - mstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_NONE */\n", + mstring_append_fmt(vars, "vec4 t%d = vec4(0.0, 0.0, 0.0, 1.0); /* PS_TEXTUREMODES_NONE */\n", i); break; case PS_TEXTUREMODES_PROJECT2D: { From 362c27b23525f700ee65c2d06538bed53abeb5ba Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 26 Apr 2025 15:53:02 -0700 Subject: [PATCH 15/39] ci: Auto-update subproject wraps periodically --- .github/workflows/bump-subproject-wraps.yml | 74 ++++++++ scripts/bump-subproject-wraps.py | 197 ++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 .github/workflows/bump-subproject-wraps.yml create mode 100644 scripts/bump-subproject-wraps.py diff --git a/.github/workflows/bump-subproject-wraps.yml b/.github/workflows/bump-subproject-wraps.yml new file mode 100644 index 0000000000..8eb239d107 --- /dev/null +++ b/.github/workflows/bump-subproject-wraps.yml @@ -0,0 +1,74 @@ +name: Bump Meson subprojects + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +permissions: + contents: write + pull-requests: write + +jobs: + bump_wraps: + name: "Bump Meson subprojects" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 + with: + enable-cache: false + + - name: Check for updates + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + uv run -s scripts/bump-subproject-wraps.py -m \ + subprojects/curl.wrap \ + subprojects/genconfig.wrap \ + subprojects/glslang.wrap \ + subprojects/imgui.wrap \ + subprojects/implot.wrap \ + subprojects/json.wrap \ + subprojects/nv2a_vsh_cpu.wrap \ + subprojects/SPIRV-Reflect.wrap \ + subprojects/tomlplusplus.wrap \ + subprojects/volk.wrap \ + subprojects/VulkanMemoryAllocator.wrap \ + subprojects/xxhash.wrap \ + > updated.json + + - name: Create PRs for updates + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + git config user.name "xemu-robot" + git config user.email "robot@xemu.app" + + jq -c '.[]' updated.json | while read -r item; do + path=$(echo "$item" | jq -r '.path') + file_basename=$(basename "$path") + name="${file_basename%%.*}" + + owner=$(echo "$item" | jq -r '.owner') + repo=$(echo "$item" | jq -r '.repo') + old_rev=$(echo "$item" | jq -r '.old_rev') + new_rev=$(echo "$item" | jq -r '.new_rev') + new_tag=$(echo "$item" | jq -r '.new_tag') + + echo "➤ Processing $name" + branch="sync/bump-${name//\//-}-${GITHUB_RUN_ID}" + + git switch --quiet -c "$branch" origin/master + git add "$path" + git commit -m "meson: Bump ${name} to ${new_tag}" + git push -u origin "$branch" + + gh pr create \ + --title "meson: Bump ${name} to ${new_tag}" \ + --body "Automatic bump of \`${name}\` to [${new_tag}](https://github.com/${owner}/${repo}/compare/${old_rev}..${new_rev})." \ + --base master + done diff --git a/scripts/bump-subproject-wraps.py b/scripts/bump-subproject-wraps.py new file mode 100644 index 0000000000..f9768b366a --- /dev/null +++ b/scripts/bump-subproject-wraps.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# /// script +# dependencies = ["requests"] +# /// +""" +Update Meson wrap file `revision` fields to point to latest release. +""" +from __future__ import annotations +import argparse +import configparser +import json +import logging +import os +import re +import sys +from pathlib import Path +from dataclasses import dataclass, asdict + +import requests + + +log = logging.getLogger(__name__) + + +SEMVER_RE = re.compile( + r""" + ^v? + (?P0|[1-9]\d*)\. + (?P0|[1-9]\d*)\. + (?P0|[1-9]\d*) + $""", + re.VERBOSE, +) + +ROOT = Path(__file__).resolve().parents[1] +WRAP_DIR = ROOT / "subprojects" +SESSION = requests.Session() +GH_TOKEN = os.getenv("GH_TOKEN", "") +if GH_TOKEN: + SESSION.headers["Authorization"] = f"Bearer {GH_TOKEN}" +SESSION.headers["Accept"] = "application/vnd.github+json" + + +def gh_sha_for_tag(owner: str, repo: str, tag: str) -> str: + data = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/git/ref/tags/{tag}", timeout=30 + ).json() + + # First level: get the object it points to + obj_type = data["object"]["type"] + obj_sha = data["object"]["sha"] + + if obj_type == "commit": + # Lightweight tag + return obj_sha + elif obj_type == "tag": + # Annotated tag: need to dereference + tag_obj_url = data["object"]["url"] + tag_data = requests.get(tag_obj_url).json() + return tag_data["object"]["sha"] + else: + raise Exception(f"Unknown object type: {obj_type}") + + +def gh_latest_release( + owner: str, repo: str, pattern: re.Pattern +) -> None | tuple[str, str]: + """ + Return (tag_name, commit_sha) for the most recent matching release. + """ + releases = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/releases", timeout=30 + ).json() + viable = [t for t in releases if pattern.match(t["tag_name"])] + + if not viable: + return None + + tag_name = viable[0]["tag_name"] + sha = gh_sha_for_tag(owner, repo, tag_name) + + return tag_name, sha + + +def gh_latest_tag(owner: str, repo: str, pattern: re.Pattern) -> tuple[str, str]: + """ + Return (tag_name, commit_sha) for the most recent matching tag. + """ + tags = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/tags", timeout=30 + ).json() + viable = [t for t in tags if pattern.match(t["name"])] + + if not viable: + return None + + return viable[0]["name"], viable[0]["commit"]["sha"] + + +@dataclass +class UpdatedWrap: + path: str + owner: str + repo: str + old_rev: str + new_rev: str + new_tag: str + + +def update_wrap(path: Path) -> None | UpdatedWrap: + """ + Return (tag_name, commit_sha) if updated, otherwise None. + """ + cp = configparser.ConfigParser(interpolation=None) + cp.read(path, encoding="utf-8") + + if "wrap-git" not in cp: + # FIXME: Support wrap-file from wrapdb + return None + + w = cp["wrap-git"] + url = w.get("url", "") + rev = w.get("revision", "").strip() + m = re.match(r".*github\.com[:/](?P[^/]+)/(?P[^/.]+)(?:\.git)?", url) + if not (m and rev): + return None + + owner, repo = m.group("owner"), m.group("repo") + try: + pattern = cp.get("update", "tag_regex", fallback=None) + pattern = re.compile(pattern) if pattern else SEMVER_RE + + latest = gh_latest_release(owner, repo, pattern) + if latest is None: + log.info("Couldn't find latest release for %s/%s", owner, repo) + log.info("Searching for tags directly...") + latest = gh_latest_tag(owner, repo, pattern) + if latest is None: + log.info("Couldn't find latest tag for %s/%s", owner, repo) + return None + tag, sha = latest + except Exception as e: + log.exception(e) + return None + + if sha.startswith(rev): + log.info("%s already at %s (%s)", path.name, tag, sha) + return None + + log.info("%s updated to %s (%s)", path.name, tag, sha) + + w["revision"] = sha + + with open(path, "w", encoding="utf-8") as file: + cp.write(file) + + # XXX: ConfigParser writes two extra newlines. Trim the last one. + file.seek(file.tell() - 1, 0) + file.truncate() + + return UpdatedWrap(str(path), owner, repo, rev, sha, tag) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument( + "--manifest", + "-m", + action="store_true", + default=False, + help="Print JSON-formatted updated manifest", + ) + ap.add_argument( + "wraps", nargs="*", help="Which wraps to update, or all if unspecified" + ) + args = ap.parse_args() + + wraps = args.wraps + if wraps: + wraps = [Path(p) for p in wraps] + else: + wraps = WRAP_DIR.glob("*.wrap") + + logging.basicConfig(level=logging.INFO) + + updated = [] + for wrap in wraps: + info = update_wrap(wrap) + if info: + updated.append(asdict(info)) + + if args.manifest: + json.dump(updated, sys.stdout, indent=2) + + +if __name__ == "__main__": + main() From c035ff9f5d5a74e35fe9d76640910432a3943bc2 Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Sat, 26 Apr 2025 23:03:30 +0000 Subject: [PATCH 16/39] meson: Bump VulkanMemoryAllocator to v3.2.1 --- subprojects/VulkanMemoryAllocator.wrap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subprojects/VulkanMemoryAllocator.wrap b/subprojects/VulkanMemoryAllocator.wrap index b5f1c7f217..56550f5c96 100644 --- a/subprojects/VulkanMemoryAllocator.wrap +++ b/subprojects/VulkanMemoryAllocator.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator -revision=v3.2.1 -depth=1 +url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator +revision = c788c52156f3ef7bc7ab769cb03c110a53ac8fcb +depth = 1 From 1e1ef6fbb9492c5e9b8d43bd2bc07b5bd6ff8df1 Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Sat, 26 Apr 2025 23:03:29 +0000 Subject: [PATCH 17/39] meson: Bump volk to 1.4.304 --- subprojects/volk.wrap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subprojects/volk.wrap b/subprojects/volk.wrap index 46578b64ce..1cb25819be 100644 --- a/subprojects/volk.wrap +++ b/subprojects/volk.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/zeux/volk -revision=1.4.304 -depth=1 +url = https://github.com/zeux/volk +revision = 0b17a763ba5643e32da1b2152f8140461b3b7345 +depth = 1 From c4b554fd80f218155efeb42b44a5a3e4ab031d24 Mon Sep 17 00:00:00 2001 From: mborgerson Date: Sat, 26 Apr 2025 16:13:13 -0700 Subject: [PATCH 18/39] meson: Add tag regex for subproject wrap auto-updater --- subprojects/SPIRV-Reflect.wrap | 9 ++++++--- subprojects/glslang.wrap | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/subprojects/SPIRV-Reflect.wrap b/subprojects/SPIRV-Reflect.wrap index bdc1486c5c..784ee56af5 100644 --- a/subprojects/SPIRV-Reflect.wrap +++ b/subprojects/SPIRV-Reflect.wrap @@ -1,4 +1,7 @@ [wrap-git] -url=https://github.com/KhronosGroup/SPIRV-Reflect -revision=vulkan-sdk-1.4.309.0 -depth=1 +url = https://github.com/KhronosGroup/SPIRV-Reflect +revision = vulkan-sdk-1.4.309.0 +depth = 1 + +[update] +tag_regex = ^(vulkan-sdk-)?[\d\.]+$ diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap index 9aa182135a..96edb2d7ba 100644 --- a/subprojects/glslang.wrap +++ b/subprojects/glslang.wrap @@ -2,3 +2,6 @@ url=https://github.com/KhronosGroup/glslang revision=vulkan-sdk-1.4.309.0 depth=1 + +[update] +tag_regex = ^(vulkan-sdk-)?[\d\.]+$ From 668017518a6da4e335b82bc08d4b3aa9971744c8 Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Sat, 26 Apr 2025 23:03:27 +0000 Subject: [PATCH 19/39] meson: Bump tomlplusplus to v3.4.0 --- subprojects/tomlplusplus.wrap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subprojects/tomlplusplus.wrap b/subprojects/tomlplusplus.wrap index 4f2d59c52f..7f678ce951 100644 --- a/subprojects/tomlplusplus.wrap +++ b/subprojects/tomlplusplus.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/marzer/tomlplusplus -revision=c635f218c0aefc801d9748841930365e54fe3089 -depth=1 +url = https://github.com/marzer/tomlplusplus +revision = 30172438cee64926dc41fdd9c11fb3ba5b2ba9de +depth = 1 From a3dc07970648b9254bf17192542ded77ffa8a993 Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Sat, 26 Apr 2025 23:21:39 +0000 Subject: [PATCH 20/39] meson: Bump glslang to 15.3.0 --- subprojects/glslang.wrap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap index 96edb2d7ba..5eb28687e5 100644 --- a/subprojects/glslang.wrap +++ b/subprojects/glslang.wrap @@ -1,7 +1,7 @@ [wrap-git] -url=https://github.com/KhronosGroup/glslang -revision=vulkan-sdk-1.4.309.0 -depth=1 +url = https://github.com/KhronosGroup/glslang +revision = fc9889c889561c5882e83819dcaffef5ed45529b +depth = 1 [update] tag_regex = ^(vulkan-sdk-)?[\d\.]+$ From 4dfbe29cbd5f45ce0b89467a3e273b963ecf2556 Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Sat, 26 Apr 2025 23:21:42 +0000 Subject: [PATCH 21/39] meson: Bump SPIRV-Reflect to vulkan-sdk-1.4.309.0 --- subprojects/SPIRV-Reflect.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/SPIRV-Reflect.wrap b/subprojects/SPIRV-Reflect.wrap index 784ee56af5..8107b8b7a7 100644 --- a/subprojects/SPIRV-Reflect.wrap +++ b/subprojects/SPIRV-Reflect.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/KhronosGroup/SPIRV-Reflect -revision = vulkan-sdk-1.4.309.0 +revision = c637858562fbce1b6f5dc7ca48d4e8a5bd117b70 depth = 1 [update] From 8f89ef14d7c2ea5fd2d6ebf2139add792b612803 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 26 Apr 2025 16:44:20 -0700 Subject: [PATCH 22/39] ci: Use XEMU_ROBOT_TOKEN for PR creation to trigger build workflow --- .github/workflows/bump-subproject-wraps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bump-subproject-wraps.yml b/.github/workflows/bump-subproject-wraps.yml index 8eb239d107..163d55e259 100644 --- a/.github/workflows/bump-subproject-wraps.yml +++ b/.github/workflows/bump-subproject-wraps.yml @@ -42,7 +42,7 @@ jobs: - name: Create PRs for updates env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.XEMU_ROBOT_TOKEN }} run: | set -euo pipefail git config user.name "xemu-robot" From 5af43523abf55ce3586014f1d06631ba62351951 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 04:03:11 +0000 Subject: [PATCH 23/39] ci: bump actions/download-artifact from 4.2.1 to 4.3.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.2.1 to 4.3.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/95815c38cf2ff2164869cbab79da8d1f422bc89e...d3f86a106a0bac45b974a628896c90dbdf5c8093) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb9f70df1f..d55279e8fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,7 +83,7 @@ jobs: steps: - name: Download source package - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package @@ -140,7 +140,7 @@ jobs: arch: aarch64 steps: - name: Download artifacts - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_name }} @@ -202,7 +202,7 @@ jobs: key: cache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.configuration }}-${{ github.sha }} restore-keys: cache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.configuration }}- - name: Download source package - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package @@ -305,7 +305,7 @@ jobs: artifact_filename: xemu-macos-arm64-release.zip steps: - name: Download source package - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package @@ -358,12 +358,12 @@ jobs: configuration: ["debug", "release"] steps: - name: Download x86_64 build - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: xemu-macos-x86_64-${{ matrix.configuration }} path: xemu-macos-x86_64-${{ matrix.configuration }} - name: Download arm64 build - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: xemu-macos-arm64-${{ matrix.configuration }} path: xemu-macos-arm64-${{ matrix.configuration }} @@ -398,7 +398,7 @@ jobs: needs: [Ubuntu, macOSUniversal, Windows, WindowsPdb] steps: - name: Download artifacts - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: path: dist - name: Extract source package @@ -462,7 +462,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download source package - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package From 7108c7a37c6b167704ae9f863244a45a43618918 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 04:03:14 +0000 Subject: [PATCH 24/39] ci: bump actions/setup-python from 5.5.0 to 5.6.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.5.0 to 5.6.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5.5.0...v5.6.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 5.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d55279e8fa..7b57efd6be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -310,7 +310,7 @@ jobs: name: src.tar.gz - name: Extract source package run: tar xf src.tar.gz - - uses: actions/setup-python@v5.5.0 + - uses: actions/setup-python@v5.6.0 with: python-version: '3.12' - name: Install dependencies From b48f84af792d332be7d48befc03b9e21f2c87b6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 04:03:17 +0000 Subject: [PATCH 25/39] ci: bump docker/build-push-action from 6.15.0 to 6.16.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/471d1dc4e07e5cdedd4c2171150001c434f0b7a4...14487ce63c7a62a4a324b0bfb37086795e31c6c1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-xemu-win64-toolchain.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-xemu-win64-toolchain.yml b/.github/workflows/build-xemu-win64-toolchain.yml index 7a521389a5..346e8fad8d 100644 --- a/.github/workflows/build-xemu-win64-toolchain.yml +++ b/.github/workflows/build-xemu-win64-toolchain.yml @@ -44,7 +44,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v5 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v5 with: context: ubuntu-win64-cross push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} From 6e513ed94812246cba3b17954f8da049bb41fbcb Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 29 Apr 2025 23:22:07 -0700 Subject: [PATCH 26/39] nv2a/psh: Fix 2D texture addressing in DOT_STR_3D mode --- hw/xbox/nv2a/pgraph/glsl/psh.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index 448281f46c..893a8fbc9d 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -1123,8 +1123,8 @@ static MString* psh_convert(struct PixelShader *ps) i, i-2, i-1, i); apply_border_adjustment(ps, vars, i, "dotSTR%d"); - mstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, dotSTR%d);\n", - i, i, i); + mstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, %s(dotSTR%d%s));\n", + i, i, tex_remap, i, ps->state.dim_tex[i] == 2 ? ".xy" : ""); break; case PS_TEXTUREMODES_DOT_STR_CUBE: assert(i == 3); From d59386942947eb7ae3087548bd7e273974af20c4 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 30 Apr 2025 23:43:38 -0700 Subject: [PATCH 27/39] nv2a: Move point params to uniforms Co-authored-by: Matt Borgerson --- hw/xbox/nv2a/pgraph/gl/renderer.h | 1 + hw/xbox/nv2a/pgraph/gl/shaders.c | 12 ++++++++++++ hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 10 ++++------ hw/xbox/nv2a/pgraph/vk/renderer.h | 2 +- hw/xbox/nv2a/pgraph/vk/shaders.c | 8 ++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 89ed6ecfd3..d1a64bb024 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -126,6 +126,7 @@ typedef struct ShaderBinding { GLint clip_region_loc[8]; + GLint point_params_loc[8]; GLint material_alpha_loc; } ShaderBinding; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index ad2f3e1b9b..af3cfd2f40 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -190,6 +190,11 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->clip_region_loc[i] = glGetUniformLocation(binding->gl_program, tmp); } + for (int i = 0; i < 8; ++i) { + snprintf(tmp, sizeof(tmp), "pointParams[%d]", i); + binding->point_params_loc[i] = glGetUniformLocation(binding->gl_program, tmp); + } + if (binding->state.fixed_function) { binding->material_alpha_loc = glGetUniformLocation(binding->gl_program, "material_alpha"); @@ -950,6 +955,13 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, x_min, y_min_xlat, x_max, y_max_xlat); } + for (i = 0; i < 8; ++i) { + GLint loc = binding->point_params_loc[i]; + if (loc != -1) { + glUniform1f(loc, pg->point_params[i]); + } + } + if (binding->material_alpha_loc != -1) { glUniform1f(binding->material_alpha_loc, pg->material_alpha); } diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index f1a2956ca0..6bc637d582 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -482,14 +482,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz /* FIXME: Testing */ if (state->point_params_enable) { - mstring_append_fmt( + mstring_append_fmt(uniforms, "%sfloat pointParams[8];\n", u); + mstring_append( body, " float d_e = length(position * modelViewMat0);\n" - " oPts.x = 1/sqrt(%f + %f*d_e + %f*d_e*d_e) + %f;\n", - state->point_params[0], state->point_params[1], state->point_params[2], - state->point_params[6]); - mstring_append_fmt(body, " oPts.x = min(oPts.x*%f + %f, 64.0) * %d;\n", - state->point_params[3], state->point_params[7], + " oPts.x = 1/sqrt(pointParams[0] + pointParams[1] * d_e + pointParams[2] * d_e * d_e) + pointParams[6];\n"); + mstring_append_fmt(body, " oPts.x = min(oPts.x * pointParams[3] + pointParams[7], 64.0) * %d;\n", state->surface_scale_factor); } else { mstring_append_fmt(body, " oPts.x = %f * %d;\n", state->point_size, diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index d728805475..4112517e58 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -191,9 +191,9 @@ typedef struct ShaderBinding { int light_local_position_loc[NV2A_MAX_LIGHTS]; int light_local_attenuation_loc[NV2A_MAX_LIGHTS]; int specular_power_loc; + int point_params_loc; int clip_region_loc; - int material_alpha_loc; int uniform_attrs_loc; diff --git a/hw/xbox/nv2a/pgraph/vk/shaders.c b/hw/xbox/nv2a/pgraph/vk/shaders.c index 5d6e345ffb..78122c701d 100644 --- a/hw/xbox/nv2a/pgraph/vk/shaders.c +++ b/hw/xbox/nv2a/pgraph/vk/shaders.c @@ -308,6 +308,9 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->clip_region_loc = uniform_index(&binding->fragment->uniforms, "clipRegion"); + binding->point_params_loc = + uniform_index(&binding->vertex->uniforms, "pointParams"); + binding->material_alpha_loc = uniform_index(&binding->vertex->uniforms, "material_alpha"); @@ -720,6 +723,11 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, uniform1iv(&binding->fragment->uniforms, binding->clip_region_loc, 8 * 4, (void *)clip_regions); + if (binding->point_params_loc != -1) { + uniform1iv(&binding->vertex->uniforms, binding->point_params_loc, + ARRAY_SIZE(pg->point_params), (void *)pg->point_params); + } + if (binding->material_alpha_loc != -1) { uniform1f(&binding->vertex->uniforms, binding->material_alpha_loc, pg->material_alpha); From 687bf629720a84d68b489043ad1e63f4ebc954fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 03:31:37 +0000 Subject: [PATCH 28/39] ci: bump astral-sh/setup-uv from 6.0.0 to 6.0.1 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/c7f87aa956e4c323abf06d5dec078e358f6b4d04...6b9c6063abd6010835644d4c2e1bef4cf5cd0fca) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/bump-subproject-wraps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bump-subproject-wraps.yml b/.github/workflows/bump-subproject-wraps.yml index 163d55e259..ff7740d78f 100644 --- a/.github/workflows/bump-subproject-wraps.yml +++ b/.github/workflows/bump-subproject-wraps.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install the latest version of uv - uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6 with: enable-cache: false From f7e40b2b80ad08745d6ecbdd3ffca0b655d6d6fb Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Mon, 12 May 2025 06:03:35 +0000 Subject: [PATCH 29/39] meson: Bump SPIRV-Reflect to vulkan-sdk-1.4.313.0 --- subprojects/SPIRV-Reflect.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/SPIRV-Reflect.wrap b/subprojects/SPIRV-Reflect.wrap index 8107b8b7a7..43ec680c93 100644 --- a/subprojects/SPIRV-Reflect.wrap +++ b/subprojects/SPIRV-Reflect.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/KhronosGroup/SPIRV-Reflect -revision = c637858562fbce1b6f5dc7ca48d4e8a5bd117b70 +revision = c6c0f5c9796bdef40c55065d82e0df67c38a29a4 depth = 1 [update] From 428c975f098cf3c8914e07219be02bed56a0b8d1 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 4 Apr 2025 10:32:49 -0700 Subject: [PATCH 30/39] nv2a: Allow multiframe RenderDoc captures with nv2a traces Allows multiple frames to be captured at once by holding shift while pressing F10. Temporarily toggles nv2a trace messages if control is held while pressing F10. --- hw/xbox/nv2a/debug.h | 3 ++- hw/xbox/nv2a/pgraph/debug_renderdoc.c | 4 +++- hw/xbox/nv2a/pgraph/gl/debug.c | 26 +++++++++++++++++++------- hw/xbox/nv2a/pgraph/vk/debug.c | 16 ++++++++++++++-- ui/xui/main.cc | 4 ++-- ui/xui/menubar.cc | 4 +++- 6 files changed, 43 insertions(+), 14 deletions(-) diff --git a/hw/xbox/nv2a/debug.h b/hw/xbox/nv2a/debug.h index 3873f94239..2dc4bece96 100644 --- a/hw/xbox/nv2a/debug.h +++ b/hw/xbox/nv2a/debug.h @@ -155,8 +155,9 @@ static inline void nv2a_profile_inc_counter(enum NV2A_PROF_COUNTERS_ENUM cnt) void nv2a_dbg_renderdoc_init(void); void *nv2a_dbg_renderdoc_get_api(void); bool nv2a_dbg_renderdoc_available(void); -void nv2a_dbg_renderdoc_capture_frames(int num_frames); +void nv2a_dbg_renderdoc_capture_frames(int num_frames, bool trace); extern int renderdoc_capture_frames; +extern bool renderdoc_trace_frames; #endif #ifdef __cplusplus diff --git a/hw/xbox/nv2a/pgraph/debug_renderdoc.c b/hw/xbox/nv2a/pgraph/debug_renderdoc.c index 273e307973..667f01a0c9 100644 --- a/hw/xbox/nv2a/pgraph/debug_renderdoc.c +++ b/hw/xbox/nv2a/pgraph/debug_renderdoc.c @@ -36,6 +36,7 @@ static RENDERDOC_API_1_6_0 *rdoc_api = NULL; int renderdoc_capture_frames = 0; +bool renderdoc_trace_frames = false; void nv2a_dbg_renderdoc_init(void) { @@ -89,7 +90,8 @@ bool nv2a_dbg_renderdoc_available(void) return rdoc_api != NULL; } -void nv2a_dbg_renderdoc_capture_frames(int num_frames) +void nv2a_dbg_renderdoc_capture_frames(int num_frames, bool trace) { renderdoc_capture_frames += num_frames; + renderdoc_trace_frames = trace; } diff --git a/hw/xbox/nv2a/pgraph/gl/debug.c b/hw/xbox/nv2a/pgraph/gl/debug.c index 968941dc7e..97803bc301 100644 --- a/hw/xbox/nv2a/pgraph/gl/debug.c +++ b/hw/xbox/nv2a/pgraph/gl/debug.c @@ -29,6 +29,8 @@ #include #ifdef CONFIG_RENDERDOC +#include "trace/control.h" + #pragma GCC diagnostic ignored "-Wstrict-prototypes" #include "thirdparty/renderdoc_app.h" #endif @@ -154,7 +156,8 @@ void gl_debug_frame_terminator(void) RENDERDOC_API_1_6_0 *rdoc_api = nv2a_dbg_renderdoc_get_api(); if (rdoc_api->IsTargetControlConnected()) { - if (rdoc_api->IsFrameCapturing()) { + bool capturing = rdoc_api->IsFrameCapturing(); + if (capturing && renderdoc_capture_frames == 0) { rdoc_api->EndFrameCapture(NULL, NULL); GLenum error = glGetError(); if (error != GL_NO_ERROR) { @@ -162,14 +165,23 @@ void gl_debug_frame_terminator(void) "Renderdoc EndFrameCapture triggered GL error 0x%X - ignoring\n", error); } + if (renderdoc_trace_frames) { + trace_enable_events("-nv2a_pgraph_*"); + renderdoc_trace_frames = false; + } } if (renderdoc_capture_frames > 0) { - rdoc_api->StartFrameCapture(NULL, NULL); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - fprintf(stderr, - "Renderdoc StartFrameCapture triggered GL error 0x%X - ignoring\n", - error); + if (!capturing) { + if (renderdoc_trace_frames) { + trace_enable_events("nv2a_pgraph_*"); + } + rdoc_api->StartFrameCapture(NULL, NULL); + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + fprintf(stderr, + "Renderdoc StartFrameCapture triggered GL error 0x%X - ignoring\n", + error); + } } --renderdoc_capture_frames; } diff --git a/hw/xbox/nv2a/pgraph/vk/debug.c b/hw/xbox/nv2a/pgraph/vk/debug.c index 5c31c9f119..88327b0cca 100644 --- a/hw/xbox/nv2a/pgraph/vk/debug.c +++ b/hw/xbox/nv2a/pgraph/vk/debug.c @@ -25,6 +25,8 @@ #endif #ifdef CONFIG_RENDERDOC +#include "trace/control.h" + #pragma GCC diagnostic ignored "-Wstrict-prototypes" #include "thirdparty/renderdoc_app.h" #endif @@ -46,11 +48,21 @@ void pgraph_vk_debug_frame_terminator(void) PGRAPHVkState *r = g_nv2a->pgraph.vk_renderer_state; if (rdoc_api->IsTargetControlConnected()) { - if (rdoc_api->IsFrameCapturing()) { + bool capturing = rdoc_api->IsFrameCapturing(); + if (capturing && renderdoc_capture_frames == 0) { rdoc_api->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + if (renderdoc_trace_frames) { + trace_enable_events("-nv2a_pgraph_*"); + renderdoc_trace_frames = false; + } } if (renderdoc_capture_frames > 0) { - rdoc_api->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + if (!capturing) { + if (renderdoc_trace_frames) { + trace_enable_events("nv2a_pgraph_*"); + } + rdoc_api->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + } --renderdoc_capture_frames; } } diff --git a/ui/xui/main.cc b/ui/xui/main.cc index 699805f113..a0e57820e1 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -218,7 +218,7 @@ void xemu_hud_render(void) #if defined(CONFIG_RENDERDOC) if (g_capture_renderdoc_frame) { - nv2a_dbg_renderdoc_capture_frames(1); + nv2a_dbg_renderdoc_capture_frames(1, false); g_capture_renderdoc_frame = false; } #endif @@ -291,7 +291,7 @@ void xemu_hud_render(void) !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) { g_scene_mgr.PushScene(g_popup_menu); } - + bool mod_key_down = ImGui::IsKeyDown(ImGuiKey_ModShift); for (int f_key = 0; f_key < 4; ++f_key) { if (ImGui::IsKeyPressed((enum ImGuiKey)(ImGuiKey_F5 + f_key))) { diff --git a/ui/xui/menubar.cc b/ui/xui/menubar.cc index f0b6c1d5c2..83a0b08ae2 100644 --- a/ui/xui/menubar.cc +++ b/ui/xui/menubar.cc @@ -73,7 +73,9 @@ void ProcessKeyboardShortcuts(void) #ifdef CONFIG_RENDERDOC if (ImGui::IsKeyPressed(ImGuiKey_F10) && nv2a_dbg_renderdoc_available()) { - nv2a_dbg_renderdoc_capture_frames(1); + ImGuiIO& io = ImGui::GetIO(); + int num_frames = io.KeyShift ? 5 : 1; + nv2a_dbg_renderdoc_capture_frames(num_frames, io.KeyCtrl); } #endif } From bd3cd78ae47498e03dae947a8c35a8dde66143db Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 22 Apr 2025 21:30:21 -0700 Subject: [PATCH 31/39] ui: Toggle fullscreen on mouse double click --- ui/xui/main.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/xui/main.cc b/ui/xui/main.cc index a0e57820e1..07441f3a64 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -290,6 +290,8 @@ void xemu_hud_render(void) (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) { g_scene_mgr.PushScene(g_popup_menu); + } else if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + xemu_toggle_fullscreen(); } bool mod_key_down = ImGui::IsKeyDown(ImGuiKey_ModShift); From c720af00bb9db153fc26e982ae379006fa7dbbc2 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 15 May 2025 12:54:56 -0700 Subject: [PATCH 32/39] nv2a/vsh: Replace NaN with 1.0 for Bx, Dx, Fog outputs and MUL zero-check --- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 2 +- hw/xbox/nv2a/pgraph/glsl/vsh.c | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index 0530e7ea7b..3e0ab5fbba 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -639,7 +639,7 @@ static const char* vsh_header = // Unfortunately mix() falls victim to the same handling of exceptional // (inf/NaN) handling as a multiply, so per-component comparisons are used // to guarantee HW behavior (anything * 0 must == 0). - " vec4 zero_components = sign(src0) * sign(src1);\n" + " vec4 zero_components = sign(NaNToOne(src0)) * sign(NaNToOne(src1));\n" " vec4 ret = src0 * src1;\n" " if (zero_components.x == 0.0) { ret.x = 0.0; }\n" " if (zero_components.y == 0.0) { ret.y = 0.0; }\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index 25c846bbde..f3c5dd5a43 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -81,6 +81,10 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) " t = clamp(t, uintBitsToFloat(0xDF800000), uintBitsToFloat(0x9F800000));\n" " }\n" " return t;\n" + "}\n" + "\n" + "vec4 NaNToOne(vec4 src) {\n" + " return mix(src, vec4(1.0), isnan(src));\n" "}\n"); pgraph_get_glsl_vtx_header(header, state->vulkan, state->smooth_shading, @@ -128,6 +132,7 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) } } } + mstring_append(header, "\n"); MString *body = mstring_from_str("void main() {\n"); @@ -232,17 +237,17 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) break; } - mstring_append(body, " oFog.xyzw = vec4(fogFactor);\n"); + mstring_append(body, " oFog = NaNToOne(vec4(fogFactor));\n"); } else { /* FIXME: Is the fog still calculated / passed somehow?! */ - mstring_append(body, " oFog.xyzw = vec4(1.0);\n"); + mstring_append(body, " oFog = vec4(1.0);\n"); } /* Set outputs */ mstring_append(body, "\n" - " vtxD0 = clamp(oD0, 0.0, 1.0);\n" - " vtxB0 = clamp(oB0, 0.0, 1.0);\n" + " vtxD0 = clamp(NaNToOne(oD0), 0.0, 1.0);\n" + " vtxB0 = clamp(NaNToOne(oB0), 0.0, 1.0);\n" " vtxFog = oFog.x;\n" " vtxT0 = oT0;\n" " vtxT1 = oT1;\n" @@ -253,8 +258,8 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) if (state->specular_enable) { mstring_append(body, - " vtxD1 = clamp(oD1, 0.0, 1.0);\n" - " vtxB1 = clamp(oB1, 0.0, 1.0);\n" + " vtxD1 = clamp(NaNToOne(oD1), 0.0, 1.0);\n" + " vtxB1 = clamp(NaNToOne(oB1), 0.0, 1.0);\n" ); if (state->ignore_specular_alpha) { From d8b1cae1fd59a28d4106a82d0feaec235288c3c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 04:00:39 +0000 Subject: [PATCH 33/39] ci: bump docker/build-push-action from 6.16.0 to 6.17.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.17.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/14487ce63c7a62a4a324b0bfb37086795e31c6c1...1dc73863535b631f98b2378be8619f83b136f4a0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-xemu-win64-toolchain.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-xemu-win64-toolchain.yml b/.github/workflows/build-xemu-win64-toolchain.yml index 346e8fad8d..4257895759 100644 --- a/.github/workflows/build-xemu-win64-toolchain.yml +++ b/.github/workflows/build-xemu-win64-toolchain.yml @@ -44,7 +44,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v5 + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v5 with: context: ubuntu-win64-cross push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} From c9cdd76102ae6345b4f3df20a8090186034e8bbb Mon Sep 17 00:00:00 2001 From: xemu-robot Date: Mon, 19 May 2025 06:03:58 +0000 Subject: [PATCH 34/39] meson: Bump VulkanMemoryAllocator to v3.3.0 --- subprojects/VulkanMemoryAllocator.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/VulkanMemoryAllocator.wrap b/subprojects/VulkanMemoryAllocator.wrap index 56550f5c96..321b8c7bed 100644 --- a/subprojects/VulkanMemoryAllocator.wrap +++ b/subprojects/VulkanMemoryAllocator.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator -revision = c788c52156f3ef7bc7ab769cb03c110a53ac8fcb +revision = 1d8f600fd424278486eade7ed3e877c99f0846b1 depth = 1 From ef1b08d79dd780bd4b019306996a6b8cdb7e92aa Mon Sep 17 00:00:00 2001 From: Shiralyn <68858896+ShiralynDev@users.noreply.github.com> Date: Tue, 20 May 2025 20:37:29 +0200 Subject: [PATCH 35/39] ui: Add "allow vibration" input setting --- config_spec.yml | 3 +++ ui/xemu-input.c | 2 +- ui/xui/main-menu.cc | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config_spec.yml b/config_spec.yml index d013c806ac..e95e6bddf7 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -54,6 +54,9 @@ input: auto_bind: type: bool default: true + allow_vibration: + type: bool + default: true background_input_capture: bool keyboard_controller_scancode_map: # Scancode reference : https://github.com/libsdl-org/SDL/blob/main/include/SDL_scancode.h diff --git a/ui/xemu-input.c b/ui/xemu-input.c index 31a51eda9d..de1db08f21 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -490,7 +490,7 @@ void xemu_input_update_sdl_controller_state(ControllerState *state) void xemu_input_update_rumble(ControllerState *state) { - if (!state->rumble_enabled) { + if (!state->rumble_enabled || !g_config.input.allow_vibration) { return; } diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 9bb5dcf33f..ef3bb05c89 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -485,6 +485,8 @@ void MainMenuInputView::Draw() SectionTitle("Options"); Toggle("Auto-bind controllers", &g_config.input.auto_bind, "Bind newly connected controllers to any open port"); + Toggle("Controller vibration", &g_config.input.allow_vibration, + "Allows the controllers to vibrate"); Toggle("Background controller input capture", &g_config.input.background_input_capture, "Capture even if window is unfocused (requires restart)"); From 11dcae01b9c14c49426accbc167b431cff89fc3a Mon Sep 17 00:00:00 2001 From: coldhex Date: Mon, 13 Jan 2025 18:52:25 +0200 Subject: [PATCH 36/39] nv2a: implement screen coordinate rounding to 4 bit fractional precision Xbox triangle rasterization appears to follow the usual top-left rule. However, since Xemu renders to an OpenGL framebuffer object (FBO) instead of directly to the default framebuffer, Xemu actually has what could be called the bottom-left triangle rasterization rule. I'll address that in another commit. Also, note that the ProjAdjacentGeometry_0.5625 test in nxdk_pgraph_tests is very sensitive to floating point rounding errors. For example, the nxdk_pgraph_tests commit 66b32a0b1feba32a0db7a95d6358e84f7a6246ad changed the math library which caused the test result to change also on real Xbox hardware due to floating point rounding error differences in matrix inverse computation. Apart from the bottom-left rasterization issue, the differing result between Xbox and the rounding I am proposing here for Xemu seems to stem from floating point rounding that happens in screen coordinate calculations before the rounding to 4 bit precision takes place. Fixing such rounding issues would require carrying all preceding floating point computations exactly in the same order and with same precision as Xbox. Note that Xbox Direct3D library seems to add 0.03125 (1/32) to screen coordinates by default. Likely the idea there was to make floating point screen coordinates round to the nearest screen coordinates in 4 bit fixed point precision. So the Xbox Direct3D library (and therefore games) already mitigate against precarious rounding when exactly half-integer coordinates are used by games. Actually they would use integer coordinates because it is Direct3D 8, but since nv2a appears to rasterize at half-integer coordinates like OpenGL, Xbox Direct3D also adds 0.5 to screen coordinates in addition to 1/32. --- hw/xbox/nv2a/pgraph/gl/renderer.h | 1 - hw/xbox/nv2a/pgraph/gl/shaders.c | 23 ----------------------- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 17 ++++++++++------- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 24 ++++++++++-------------- hw/xbox/nv2a/pgraph/vk/renderer.h | 1 - hw/xbox/nv2a/pgraph/vk/shaders.c | 23 ----------------------- 6 files changed, 20 insertions(+), 69 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index d1a64bb024..b9074d9644 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -111,7 +111,6 @@ typedef struct ShaderBinding { GLint vsh_constant_loc[NV2A_VERTEXSHADER_CONSTANTS]; uint32_t vsh_constants[NV2A_VERTEXSHADER_CONSTANTS][4]; - GLint inv_viewport_loc; GLint ltctxa_loc[NV2A_LTCTXA_COUNT]; GLint ltctxb_loc[NV2A_LTCTXB_COUNT]; GLint ltc1_loc[NV2A_LTC1_COUNT]; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index af3cfd2f40..dbf555a621 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -158,7 +158,6 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->fog_color_loc = glGetUniformLocation(binding->gl_program, "fogColor"); binding->fog_param_loc = glGetUniformLocation(binding->gl_program, "fogParam"); - binding->inv_viewport_loc = glGetUniformLocation(binding->gl_program, "invViewport"); for (int i = 0; i < NV2A_LTCTXA_COUNT; i++) { snprintf(tmp, sizeof(tmp), "ltctxa[%d]", i); binding->ltctxa_loc[i] = glGetUniformLocation(binding->gl_program, tmp); @@ -847,28 +846,6 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, if (binding->specular_power_loc != -1) { glUniform1f(binding->specular_power_loc, pg->specular_power); } - - /* estimate the viewport by assuming it matches the surface ... */ - unsigned int aa_width = 1, aa_height = 1; - pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); - - float m11 = 0.5 * (pg->surface_binding_dim.width/aa_width); - float m22 = -0.5 * (pg->surface_binding_dim.height/aa_height); - float m33 = zmax; - float m41 = *(float*)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][0]; - float m42 = *(float*)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][1]; - - float invViewport[16] = { - 1.0/m11, 0, 0, 0, - 0, 1.0/m22, 0, 0, - 0, 0, 1.0/m33, 0, - -1.0+m41/m11, 1.0+m42/m22, 0, 1.0 - }; - - if (binding->inv_viewport_loc != -1) { - glUniformMatrix4fv(binding->inv_viewport_loc, - 1, GL_FALSE, &invViewport[0]); - } } /* update vertex program constants */ diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 6bc637d582..02b1a8fda8 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -115,8 +115,6 @@ GLSL_DEFINE(sceneAmbientColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_FR_AMB) ".xyz") GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz") "\n" ); - mstring_append_fmt(uniforms, -"%smat4 invViewport;\n", u); /* Skinning */ unsigned int count; @@ -471,13 +469,18 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } mstring_append(body, - " oPos = tPosition * compositeMat;\n" - " oPos.w = clampAwayZeroInf(oPos.w);\n" - " oPos = invViewport * oPos;\n" + " oPos = tPosition * compositeMat;\n" + " oPos.z = oPos.z / clipRange.y;\n" + " oPos.w = clampAwayZeroInf(oPos.w);\n" + " oPos.xy /= oPos.w;\n" + " oPos.xy += c[" stringify(NV_IGRAPH_XF_XFCTX_VPOFF) "].xy;\n" + " oPos.xy = floor(oPos.xy * 16.0f) / 16.0f;\n" + " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" + " oPos.xy *= oPos.w;\n" ); - if (state->vulkan) { - mstring_append(body, " oPos.y *= -1;\n"); + if (!state->vulkan) { + mstring_append(body, " oPos.y = -oPos.y;\n"); } /* FIXME: Testing */ diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index 3e0ab5fbba..e26d0c0304 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -821,22 +821,14 @@ void pgraph_gen_vsh_prog_glsl(uint16_t version, assert(has_final); mstring_append(body, - /* the shaders leave the result in screen space, while - * opengl expects it in clip space. - * TODO: the pixel-center co-ordinate differences should handled + /* The shaders leave the result in screen space, while OpenGL expects it + * in clip space. Xbox NV2A rasterizer appears to have 4 bit precision + * fixed point fractional part and to convert floating point coordinates + * by flooring. */ - " oPos.x = 2.0 * (oPos.x - surfaceSize.x * 0.5) / surfaceSize.x;\n" - ); + " oPos.xy = floor(oPos.xy * 16.0f) / 16.0f;\n" + " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" - if (vulkan) { - mstring_append(body, - " oPos.y = 2.0 * oPos.y / surfaceSize.y - 1.0;\n"); - } else { - mstring_append(body, " oPos.y = -2.0 * (oPos.y - surfaceSize.y * 0.5) " - "/ surfaceSize.y;\n"); - } - - mstring_append(body, " oPos.z = oPos.z / clipRange.y;\n" " oPos.w = clampAwayZeroInf(oPos.w);\n" @@ -849,4 +841,8 @@ void pgraph_gen_vsh_prog_glsl(uint16_t version, */ " oPos.xyz *= oPos.w;\n" ); + + if (!vulkan) { + mstring_append(body, " oPos.y = -oPos.y;\n"); + } } diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index 4112517e58..91bbf0f31d 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -179,7 +179,6 @@ typedef struct ShaderBinding { int vsh_constant_loc; uint32_t vsh_constants[NV2A_VERTEXSHADER_CONSTANTS][4]; - int inv_viewport_loc; int ltctxa_loc; int ltctxb_loc; int ltc1_loc; diff --git a/hw/xbox/nv2a/pgraph/vk/shaders.c b/hw/xbox/nv2a/pgraph/vk/shaders.c index 78122c701d..0a6e8a2b5c 100644 --- a/hw/xbox/nv2a/pgraph/vk/shaders.c +++ b/hw/xbox/nv2a/pgraph/vk/shaders.c @@ -283,8 +283,6 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->fog_param_loc = uniform_index(&binding->vertex->uniforms, "fogParam"); - binding->inv_viewport_loc = - uniform_index(&binding->vertex->uniforms, "invViewport"); binding->ltctxa_loc = uniform_index(&binding->vertex->uniforms, "ltctxa"); binding->ltctxb_loc = uniform_index(&binding->vertex->uniforms, "ltctxb"); binding->ltc1_loc = uniform_index(&binding->vertex->uniforms, "ltc1"); @@ -617,27 +615,6 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, uniform1f(&binding->vertex->uniforms, binding->specular_power_loc, pg->specular_power); } - - /* estimate the viewport by assuming it matches the surface ... */ - unsigned int aa_width = 1, aa_height = 1; - pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); - - float m11 = 0.5 * (pg->surface_binding_dim.width / aa_width); - float m22 = -0.5 * (pg->surface_binding_dim.height / aa_height); - float m33 = zmax; - float m41 = *(float *)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][0]; - float m42 = *(float *)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][1]; - - float invViewport[16] = { - 1.0 / m11, 0, 0, 0, 0, 1.0 / m22, 0, - 0, 0, 0, 1.0 / m33, 0, -1.0 + m41 / m11, 1.0 + m42 / m22, - 0, 1.0 - }; - - if (binding->inv_viewport_loc != -1) { - uniformMatrix4fv(&binding->vertex->uniforms, - binding->inv_viewport_loc, &invViewport[0]); - } } /* update vertex program constants */ From a316d7487219e0412360097836c17b65ff877953 Mon Sep 17 00:00:00 2001 From: coldhex Date: Mon, 5 May 2025 21:56:31 +0300 Subject: [PATCH 37/39] nv2a: Use trunc in vertex rounding instead of floor Xbox seems to truncate instead of flooring, which can be inferred from interpolated depth buffer values. --- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 2 +- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 6 ++---- hw/xbox/nv2a/pgraph/glsl/vsh.c | 7 +++++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 02b1a8fda8..8cee360997 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -474,7 +474,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " oPos.w = clampAwayZeroInf(oPos.w);\n" " oPos.xy /= oPos.w;\n" " oPos.xy += c[" stringify(NV_IGRAPH_XF_XFCTX_VPOFF) "].xy;\n" - " oPos.xy = floor(oPos.xy * 16.0f) / 16.0f;\n" + " oPos.xy = roundScreenCoords(oPos.xy);\n" " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" " oPos.xy *= oPos.w;\n" ); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index e26d0c0304..3068684d32 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -822,11 +822,9 @@ void pgraph_gen_vsh_prog_glsl(uint16_t version, mstring_append(body, /* The shaders leave the result in screen space, while OpenGL expects it - * in clip space. Xbox NV2A rasterizer appears to have 4 bit precision - * fixed point fractional part and to convert floating point coordinates - * by flooring. + * in clip space. */ - " oPos.xy = floor(oPos.xy * 16.0f) / 16.0f;\n" + " oPos.xy = roundScreenCoords(oPos.xy);\n" " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" " oPos.z = oPos.z / clipRange.y;\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index f3c5dd5a43..b6b7045186 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -85,6 +85,13 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) "\n" "vec4 NaNToOne(vec4 src) {\n" " return mix(src, vec4(1.0), isnan(src));\n" + "}\n" + "\n" + // Xbox NV2A rasterizer appears to have 4 bit precision fixed-point + // fractional part and to convert floating-point coordinates by + // by truncating (not flooring). + "vec2 roundScreenCoords(vec2 pos) {\n" + " return trunc(pos * 16.0f) / 16.0f;\n" "}\n"); pgraph_get_glsl_vtx_header(header, state->vulkan, state->smooth_shading, From ce936bccdd70a18c28f35ffada8cb8c934fa0d74 Mon Sep 17 00:00:00 2001 From: coldhex Date: Tue, 20 May 2025 22:19:33 +0300 Subject: [PATCH 38/39] nv2a/gl: y-flipped rendering to framebuffer object Render scenes upside-down to framebuffer objects (FBO). The strange thing about rendering to OpenGL FBO is that it follows the bottom-left triangle rasterization rule with common PC GPUs. At least Intel and AMD. NVIDIA to be tested. My raster-rule-test github gist demonstrates this. This commit flips coordinates in y-direction, which effectively turns the bottom-left rule into top-left rule needed for Xbox compatibility. This (together with the previous commit) fixes Midtown Madness 3 Seine water rectangular seam rendering artifacts (and the remaining seams are present with Xbox hardware too.) May fix similar artifacts in other games. --- hw/xbox/nv2a/pgraph/gl/display.c | 2 +- hw/xbox/nv2a/pgraph/gl/draw.c | 5 ++-- hw/xbox/nv2a/pgraph/gl/shaders.c | 6 +---- hw/xbox/nv2a/pgraph/gl/surface.c | 42 ++++++++++++++++------------- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 4 --- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 4 --- 6 files changed, 27 insertions(+), 36 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/display.c b/hw/xbox/nv2a/pgraph/gl/display.c index 6d52a5c3b3..47400cbbd0 100644 --- a/hw/xbox/nv2a/pgraph/gl/display.c +++ b/hw/xbox/nv2a/pgraph/gl/display.c @@ -68,7 +68,7 @@ void pgraph_gl_init_display(NV2AState *d) "{\n" " vec2 texCoord = gl_FragCoord.xy/display_size;\n" " float rel = display_size.y/textureSize(tex, 0).y/line_offset;\n" - " texCoord.y = 1 + rel*(texCoord.y - 1);" + " texCoord.y = rel*(1.0f - texCoord.y);" " out_Color.rgba = texture(tex, texCoord);\n" " if (pvideo_enable) {\n" " vec2 screenCoord = gl_FragCoord.xy - 0.5;\n" diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index bfa92662e7..79c18040f9 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -92,7 +92,6 @@ void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) scissor_height = ymax - ymin + 1; pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); - ymin = pg->surface_binding_dim.height - (ymin + scissor_height); NV2A_DPRINTF("Translated clear rect to %d,%d - %d,%d\n", xmin, ymin, xmin + scissor_width - 1, ymin + scissor_height - 1); @@ -204,9 +203,10 @@ void pgraph_gl_draw_begin(NV2AState *d) } /* Front-face select */ + /* Winding is reverse here because clip-space y-coordinates are inverted */ glFrontFace(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_FRONTFACE - ? GL_CCW : GL_CW); + ? GL_CW : GL_CCW); /* Polygon offset */ /* FIXME: GL implementation-specific, maybe do this in VS? */ @@ -340,7 +340,6 @@ void pgraph_gl_draw_begin(NV2AState *d) pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); - ymin = pg->surface_binding_dim.height - (ymin + scissor_height); pgraph_apply_scaling_factor(pg, &xmin, &ymin); pgraph_apply_scaling_factor(pg, &scissor_width, &scissor_height); diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index dbf555a621..742e3c2881 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -924,12 +924,8 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, pgraph_apply_scaling_factor(pg, &x_min, &y_min); pgraph_apply_scaling_factor(pg, &x_max, &y_max); - /* Translate for the GL viewport origin */ - int y_min_xlat = MAX((int)max_gl_height - (int)y_max, 0); - int y_max_xlat = MIN((int)max_gl_height - (int)y_min, max_gl_height); - glUniform4i(r->shader_binding->clip_region_loc[i], - x_min, y_min_xlat, x_max, y_max_xlat); + x_min, y_min, x_max, y_max); } for (i = 0; i < 8; ++i) { diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c index 53df185130..ab63dd4e3b 100644 --- a/hw/xbox/nv2a/pgraph/gl/surface.c +++ b/hw/xbox/nv2a/pgraph/gl/surface.c @@ -137,11 +137,7 @@ static void init_render_to_texture(PGRAPHState *pg) "layout(location = 0) out vec4 out_Color;\n" "void main()\n" "{\n" - " vec2 texCoord;\n" - " texCoord.x = gl_FragCoord.x;\n" - " texCoord.y = (surface_size.y - gl_FragCoord.y)\n" - " + (textureSize(tex,0).y - surface_size.y);\n" - " texCoord /= textureSize(tex,0).xy;\n" + " vec2 texCoord = gl_FragCoord.xy / textureSize(tex, 0).xy;\n" " out_Color.rgba = texture(tex, texCoord);\n" "}\n"; @@ -298,7 +294,7 @@ static void render_surface_to_texture_slow(NV2AState *d, size_t bufsize = width * height * surface->fmt.bytes_per_pixel; uint8_t *buf = g_malloc(bufsize); - surface_download_to_buffer(d, surface, false, true, false, buf); + surface_download_to_buffer(d, surface, false, false, false, buf); width = texture_shape->width; height = texture_shape->height; @@ -738,7 +734,7 @@ static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force) nv2a_profile_inc_counter(NV2A_PROF_SURF_DOWNLOAD); - surface_download_to_buffer(d, surface, true, true, true, + surface_download_to_buffer(d, surface, true, false, true, d->vram_ptr + surface->vram_addr); memory_region_set_client_dirty(d->vram, surface->vram_addr, @@ -875,20 +871,26 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, surface->fmt.bytes_per_pixel); } - /* FIXME: Replace this flip/scaling */ + /* FIXME: Replace this scaling */ // This is VRAM so we can't do this inplace! - uint8_t *flipped_buf = (uint8_t *)g_malloc( - surface->height * surface->width * surface->fmt.bytes_per_pixel); - unsigned int irow; - for (irow = 0; irow < surface->height; irow++) { - memcpy(&flipped_buf[surface->width * (surface->height - irow - 1) - * surface->fmt.bytes_per_pixel], - &buf[surface->pitch * irow], - surface->width * surface->fmt.bytes_per_pixel); + uint8_t *optimal_buf = buf; + unsigned int optimal_pitch = surface->width * surface->fmt.bytes_per_pixel; + + if (surface->pitch != optimal_pitch) { + optimal_buf = (uint8_t *)g_malloc(surface->height * optimal_pitch); + + uint8_t *src = buf; + uint8_t *dst = optimal_buf; + unsigned int irow; + for (irow = 0; irow < surface->height; irow++) { + memcpy(dst, src, optimal_pitch); + src += surface->pitch; + dst += optimal_pitch; + } } - uint8_t *gl_read_buf = flipped_buf; + uint8_t *gl_read_buf = optimal_buf; unsigned int width = surface->width, height = surface->height; if (pg->surface_scale_factor > 1) { @@ -896,7 +898,7 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, pg->scale_buf = (uint8_t *)g_realloc( pg->scale_buf, width * height * surface->fmt.bytes_per_pixel); gl_read_buf = pg->scale_buf; - uint8_t *out = gl_read_buf, *in = flipped_buf; + uint8_t *out = gl_read_buf, *in = optimal_buf; surface_copy_expand(out, in, surface->width, surface->height, surface->fmt.bytes_per_pixel, d->pgraph.surface_scale_factor); @@ -915,7 +917,9 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, height, 0, surface->fmt.gl_format, surface->fmt.gl_type, gl_read_buf); glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); - g_free(flipped_buf); + if (optimal_buf != buf) { + g_free(optimal_buf); + } if (surface->swizzle) { g_free(buf); } diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 8cee360997..fb07a12a4b 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -479,10 +479,6 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " oPos.xy *= oPos.w;\n" ); - if (!state->vulkan) { - mstring_append(body, " oPos.y = -oPos.y;\n"); - } - /* FIXME: Testing */ if (state->point_params_enable) { mstring_append_fmt(uniforms, "%sfloat pointParams[8];\n", u); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index 3068684d32..fd48979447 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -839,8 +839,4 @@ void pgraph_gen_vsh_prog_glsl(uint16_t version, */ " oPos.xyz *= oPos.w;\n" ); - - if (!vulkan) { - mstring_append(body, " oPos.y = -oPos.y;\n"); - } } From 8667193001e4d9f20f9d5237d941f6f0ff3a2672 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Tue, 20 May 2025 13:02:00 -0700 Subject: [PATCH 39/39] nv2a: Prevent NaN in specular power factor calculation --- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index fb07a12a4b..daf821f3f1 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -358,7 +358,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append_fmt(body, " float pf;\n" - " if (nDotVP == 0.0) {\n" + " if (nDotVP == 0.0 || nDotHV == 0.0) {\n" " pf = 0.0;\n" " } else {\n" " pf = pow(nDotHV, specularPower);\n"