From d6cb7536a2acf0dcc41934a627a0d63a5700ec76 Mon Sep 17 00:00:00 2001 From: coldhex Date: Mon, 5 May 2025 22:08:30 +0300 Subject: [PATCH] nv2a: Depth buffer precision improvements and polygon offset slope factor 1. Use barycentric coordinates to interpolate depth values. Linux, Mesa and AMD Radeon RX 6600 with Vulkan driver currently has quite poor interpolation precision, which results in artifacts in at least Chronicles of Riddick. Intel integrated UHD 770 has much better precision, for example. This commit handles depth interpolation manually. Also note that the previous w-buffer interpolation used gl_FragCoord.w which can't produce all w-values, e.g. 1.0f/16777046.0f equals 1.0f/16777047.0f with 32-bit floats. This also uses depth value differences in interpolation which has the desired property that a triangle with the same z-value on all vertices will result in exactly that same z-value when interpolated. At least the game Shenmue II sky rendering relies on this. 2. Computes polygon depth bias slope for both z-buffering and w-buffering. These are computed by taking the max and abs of partial derivatives of either of the functions z=z(x,y) or w=w(x,y), where x,y,z,w are screen-space coordinates. This matches Xbox hardware for z-buffering where the partial derivatives are constants over any fixed triangle. However, for w-buffering the partial derivatives vary over any fixed triangle, but Xbox appears to compute just a single depth slope at the first visible pixel (where "first" means something like first in top-left order) and uses that over the whole triangle. This commit computes the slope per-pixel. The way to compute the partial derivatives is by using the chain-rule, e.g. dw/dx = -w^2 * d(1/w)/dx. This is useful since 1/w is linear in screen-space and therefore d(1/w)/dx is constant over any fixed triangle. But, as mentioned, finding out the w-value for the first visible pixel of a triangle is difficult in OpenGL/Vulkan and is not done here. Instead we calculate depth slope per-pixel. --- hw/xbox/nv2a/pgraph/gl/draw.c | 42 +-- hw/xbox/nv2a/pgraph/gl/shaders.c | 7 +- hw/xbox/nv2a/pgraph/glsl/common.c | 4 + hw/xbox/nv2a/pgraph/glsl/common.h | 2 + hw/xbox/nv2a/pgraph/glsl/geom.c | 444 ++++++++++++++++------- hw/xbox/nv2a/pgraph/glsl/geom.h | 1 + hw/xbox/nv2a/pgraph/glsl/psh.c | 240 ++++++++---- hw/xbox/nv2a/pgraph/glsl/psh.h | 12 + hw/xbox/nv2a/pgraph/glsl/shaders.c | 3 +- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 3 +- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 6 +- hw/xbox/nv2a/pgraph/glsl/vsh.c | 8 + hw/xbox/nv2a/pgraph/vk/draw.c | 35 +- hw/xbox/nv2a/pgraph/vk/surface-compute.c | 3 +- 14 files changed, 526 insertions(+), 284 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index 975cc79598..830494cde6 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -208,38 +208,10 @@ void pgraph_gl_draw_begin(NV2AState *d) & NV_PGRAPH_SETUPRASTER_FRONTFACE ? GL_CW : GL_CCW); - /* Polygon offset */ - /* FIXME: GL implementation-specific, maybe do this in VS? */ - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE) { - glEnable(GL_POLYGON_OFFSET_FILL); - } else { - glDisable(GL_POLYGON_OFFSET_FILL); - } - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE) { - glEnable(GL_POLYGON_OFFSET_LINE); - } else { - glDisable(GL_POLYGON_OFFSET_LINE); - } - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE) { - glEnable(GL_POLYGON_OFFSET_POINT); - } else { - glDisable(GL_POLYGON_OFFSET_POINT); - } - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - (NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE)) { - uint32_t zfactor_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETFACTOR); - GLfloat zfactor = *(float*)&zfactor_u32; - uint32_t zbias_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETBIAS); - GLfloat zbias = *(float*)&zbias_u32; - // FIXME: with Linux and Mesa, zbias must be multiplied by 0.5 in - // order to have the same depth value offset as Xbox. - glPolygonOffset(zfactor, zbias); - } + /* Polygon offset is handled in geometry and fragment shaders explicitly */ + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_POLYGON_OFFSET_LINE); + glDisable(GL_POLYGON_OFFSET_POINT); /* Depth testing */ if (depth_test) { @@ -255,12 +227,6 @@ void pgraph_gl_draw_begin(NV2AState *d) glEnable(GL_DEPTH_CLAMP); - if (GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_SHADEMODE) == - NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT) { - glProvokingVertex(GL_FIRST_VERTEX_CONVENTION); - } - if (stencil_test) { glEnable(GL_STENCIL_TEST); diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index 30b4c5cbba..4400133434 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -31,10 +31,6 @@ static GLenum get_gl_primitive_mode(enum ShaderPolygonMode polygon_mode, enum ShaderPrimitiveMode primitive_mode) { - if (polygon_mode == POLY_MODE_POINT) { - return GL_POINTS; - } - switch (primitive_mode) { case PRIM_TYPE_POINTS: return GL_POINTS; case PRIM_TYPE_LINES: return GL_LINES; @@ -705,6 +701,9 @@ static void apply_uniform_updates(const UniformInfo *info, int *locs, case UniformElementType_int: glUniform1iv(locs[i], info[i].count, value); break; + case UniformElementType_ivec2: + glUniform2iv(locs[i], info[i].count, value); + break; case UniformElementType_ivec4: glUniform4iv(locs[i], info[i].count, value); break; diff --git a/hw/xbox/nv2a/pgraph/glsl/common.c b/hw/xbox/nv2a/pgraph/glsl/common.c index 338f58ab9a..887d2a2e23 100644 --- a/hw/xbox/nv2a/pgraph/glsl/common.c +++ b/hw/xbox/nv2a/pgraph/glsl/common.c @@ -48,6 +48,10 @@ MString *pgraph_glsl_get_vtx_header(MString *out, bool location, bool smooth, { smooth_s, vec4_s, "vtxT1" }, { smooth_s, vec4_s, "vtxT2" }, { smooth_s, vec4_s, "vtxT3" }, + { flat_s, vec4_s, "vtxPos0" }, + { flat_s, vec4_s, "vtxPos1" }, + { flat_s, vec4_s, "vtxPos2" }, + { flat_s, float_s, "triMZ" }, }; for (int i = 0; i < ARRAY_SIZE(attr); i++) { diff --git a/hw/xbox/nv2a/pgraph/glsl/common.h b/hw/xbox/nv2a/pgraph/glsl/common.h index 9dc1fa0347..4b327421be 100644 --- a/hw/xbox/nv2a/pgraph/glsl/common.h +++ b/hw/xbox/nv2a/pgraph/glsl/common.h @@ -25,6 +25,7 @@ #include "qemu/osdep.h" #include "qemu/mstring.h" +typedef int ivec2[2]; typedef int ivec4[4]; typedef float mat2[2 * 2]; typedef unsigned int uint; @@ -35,6 +36,7 @@ typedef float vec4[4]; #define UNIFORM_ELEMENT_TYPE_X(DECL) \ DECL(float) \ DECL(int) \ + DECL(ivec2) \ DECL(ivec4) \ DECL(mat2) \ DECL(uint) \ diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c index 893f7126f3..2170696a08 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.c +++ b/hw/xbox/nv2a/pgraph/glsl/geom.c @@ -37,6 +37,9 @@ void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *state) state->smooth_shading = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), NV_PGRAPH_CONTROL_3_SHADEMODE) == NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH; + + state->z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & + NV_PGRAPH_CONTROL_0_Z_PERSPECTIVE_ENABLE; } bool pgraph_glsl_need_geom(const GeomState *state) @@ -45,63 +48,24 @@ bool pgraph_glsl_need_geom(const GeomState *state) assert(state->polygon_front_mode == state->polygon_back_mode); enum ShaderPolygonMode polygon_mode = state->polygon_front_mode; - /* POINT mode shouldn't require any special work */ - if (polygon_mode == POLY_MODE_POINT) { - return false; - } - switch (state->primitive_mode) { + case PRIM_TYPE_POINTS: + return false; + case PRIM_TYPE_LINES: + case PRIM_TYPE_LINE_LOOP: + case PRIM_TYPE_LINE_STRIP: case PRIM_TYPE_TRIANGLES: - if (polygon_mode == POLY_MODE_FILL) { - return false; - } - return true; case PRIM_TYPE_TRIANGLE_STRIP: - if (polygon_mode == POLY_MODE_FILL) { - return false; - } - assert(polygon_mode == POLY_MODE_LINE); - return true; case PRIM_TYPE_TRIANGLE_FAN: - if (polygon_mode == POLY_MODE_FILL) { - return false; - } - assert(polygon_mode == POLY_MODE_LINE); - return true; case PRIM_TYPE_QUADS: - if (polygon_mode == POLY_MODE_LINE) { - return true; - } else if (polygon_mode == POLY_MODE_FILL) { - return true; - } else { - assert(false); - return false; - } - break; case PRIM_TYPE_QUAD_STRIP: - if (polygon_mode == POLY_MODE_LINE) { - return true; - } else if (polygon_mode == POLY_MODE_FILL) { - return true; - } else { - assert(false); - return false; - } - break; + return true; case PRIM_TYPE_POLYGON: - if (polygon_mode == POLY_MODE_LINE) { - return false; - } - if (polygon_mode == POLY_MODE_FILL) { - if (state->smooth_shading) { - return false; - } - return true; - } else { + if (polygon_mode == POLY_MODE_POINT) { assert(false); return false; } - break; + return true; default: return false; } @@ -113,106 +77,225 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) assert(state->polygon_front_mode == state->polygon_back_mode); enum ShaderPolygonMode polygon_mode = state->polygon_front_mode; - /* POINT mode shouldn't require any special work */ - if (polygon_mode == POLY_MODE_POINT) { - return NULL; - } - - /* Handle LINE and FILL mode */ + bool need_triz = false; + bool need_quadz = false; + bool need_linez = false; const char *layout_in = NULL; const char *layout_out = NULL; const char *body = NULL; + switch (state->primitive_mode) { case PRIM_TYPE_POINTS: return NULL; - case PRIM_TYPE_LINES: return NULL; - case PRIM_TYPE_LINE_LOOP: return NULL; - case PRIM_TYPE_LINE_STRIP: return NULL; - case PRIM_TYPE_TRIANGLES: - if (polygon_mode == POLY_MODE_FILL) { return NULL; } - assert(polygon_mode == POLY_MODE_LINE); - layout_in = "layout(triangles) in;\n"; - layout_out = "layout(line_strip, max_vertices = 4) out;\n"; - body = " emit_vertex(0, 0);\n" - " emit_vertex(1, 0);\n" - " emit_vertex(2, 0);\n" - " emit_vertex(0, 0);\n" + case PRIM_TYPE_LINES: + case PRIM_TYPE_LINE_LOOP: + case PRIM_TYPE_LINE_STRIP: + need_linez = true; + layout_in = "layout(lines) in;\n"; + layout_out = "layout(line_strip, max_vertices = 2) out;\n"; + body = " mat4 pz = calc_linez(0, 1);\n" + " emit_vertex(0, 0, pz);\n" + " emit_vertex(1, 1, pz);\n" " EndPrimitive();\n"; break; + case PRIM_TYPE_TRIANGLES: + need_triz = true; + layout_in = "layout(triangles) in;\n"; + if (polygon_mode == POLY_MODE_FILL) { + layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; + body = " mat4 pz = calc_triz(0, 1, 2);\n" + " emit_vertex(0, 0, pz);\n" + " emit_vertex(1, 1, pz);\n" + " emit_vertex(2, 2, pz);\n" + " EndPrimitive();\n"; + } else if (polygon_mode == POLY_MODE_LINE) { + need_linez = true; + layout_out = "layout(line_strip, max_vertices = 4) out;\n"; + body = " float triMZ = calc_triz(0, 1, 2)[3].x;\n" + " mat4 pz1 = calc_linez(0, 1);\n" + " pz1[3].x = triMZ;\n" + " mat4 pz2 = calc_linez(1, 2);\n" + " pz2[3].x = triMZ;\n" + " mat4 pz3 = calc_linez(2, 0);\n" + " pz3[3].x = triMZ;\n" + " emit_vertex(0, 0, pz1);\n" + " emit_vertex(1, 0, pz1);\n" + " emit_vertex(2, 0, pz2);\n" + " emit_vertex(0, 0, pz3);\n" + " EndPrimitive();\n"; + } else { + assert(polygon_mode == POLY_MODE_POINT); + layout_out = "layout(points, max_vertices = 3) out;\n"; + body = " mat4 pz = calc_triz(0, 1, 2);\n" + " emit_vertex(0, 0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(1, 0, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(2, 0, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " EndPrimitive();\n"; + } + break; case PRIM_TYPE_TRIANGLE_STRIP: case PRIM_TYPE_TRIANGLE_FAN: - if (polygon_mode == POLY_MODE_FILL) { return NULL; } - assert(polygon_mode == POLY_MODE_LINE); + need_triz = true; layout_in = "layout(triangles) in;\n"; - layout_out = "layout(line_strip, max_vertices = 4) out;\n"; - body = " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 0);\n" - " }\n" - " emit_vertex(1, 0);\n" - " emit_vertex(2, 0);\n" - " emit_vertex(0, 0);\n" - " EndPrimitive();\n"; + if (polygon_mode == POLY_MODE_FILL) { + layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; + body = " mat4 pz = calc_triz(0, 1, 2);\n" + " emit_vertex(0, 0, pz);\n" + " emit_vertex(1, 1, pz);\n" + " emit_vertex(2, 2, pz);\n" + " EndPrimitive();\n"; + } else if (polygon_mode == POLY_MODE_LINE) { + need_linez = true; + layout_out = "layout(line_strip, max_vertices = 4) out;\n"; + body = " float triMZ = calc_triz(0, 1, 2)[3].x;\n" + " mat4 pz1 = calc_linez(0, 1);\n" + " pz1[3].x = triMZ;\n" + " mat4 pz2 = calc_linez(1, 2);\n" + " pz2[3].x = triMZ;\n" + " mat4 pz3 = calc_linez(2, 0);\n" + " pz3[3].x = triMZ;\n" + " if (gl_PrimitiveIDIn == 0) {\n" + " emit_vertex(0, 0, pz1);\n" + " }\n" + " emit_vertex(1, 0, pz1);\n" + " emit_vertex(2, 0, pz2);\n" + " emit_vertex(0, 0, pz3);\n" + " EndPrimitive();\n"; + } else { + assert(polygon_mode == POLY_MODE_POINT); + layout_out = "layout(points, max_vertices = 3) out;\n"; + body = " mat4 pz = calc_triz(0, 1, 2);\n" + " if (gl_PrimitiveIDIn == 0) {\n" + " emit_vertex(0, 0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(1, 0, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " EndPrimitive();\n" + " }\n" + " emit_vertex(2, 0, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " EndPrimitive();\n"; + } break; case PRIM_TYPE_QUADS: + need_quadz = true; layout_in = "layout(lines_adjacency) in;\n"; if (polygon_mode == POLY_MODE_LINE) { + need_linez = true; layout_out = "layout(line_strip, max_vertices = 5) out;\n"; - body = " emit_vertex(0, 3);\n" - " emit_vertex(1, 3);\n" - " emit_vertex(2, 3);\n" - " emit_vertex(3, 3);\n" - " emit_vertex(0, 3);\n" + body = " mat4 pz, pzs;\n" + " calc_quadz(0, 1, 2, 3, pz, pzs);\n" + " mat4 pz1 = calc_linez(0, 1);\n" + " pz1[3].x = pz[3].x;\n" + " mat4 pz2 = calc_linez(1, 2);\n" + " pz2[3].x = pz[3].x;\n" + " mat4 pz3 = calc_linez(2, 3);\n" + " pz3[3].x = pzs[3].x;\n" + " mat4 pz4 = calc_linez(3, 0);\n" + " pz4[3].x = pzs[3].x;\n" + " emit_vertex(0, 3, pz1);\n" + " emit_vertex(1, 3, pz1);\n" + " emit_vertex(2, 3, pz2);\n" + " emit_vertex(3, 3, pz3);\n" + " emit_vertex(0, 3, pz4);\n" " EndPrimitive();\n"; } else if (polygon_mode == POLY_MODE_FILL) { layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; - body = " emit_vertex(3, 3);\n" - " emit_vertex(0, 3);\n" - " emit_vertex(2, 3);\n" - " emit_vertex(1, 3);\n" + body = " mat4 pz, pz2;\n" + " calc_quadz(0, 1, 2, 3, pz, pz2);\n" + " emit_vertex(1, 3, pz);\n" + " emit_vertex(2, 3, pz2);\n" + " emit_vertex(0, 3, pz);\n" + " emit_vertex(3, 3, pz2);\n" " EndPrimitive();\n"; } else { - assert(false); - return NULL; + assert(polygon_mode == POLY_MODE_POINT); + layout_out = "layout(points, max_vertices = 4) out;\n"; + body = " mat4 pz, pz2;\n" + " calc_quadz(0, 1, 2, 3, pz, pz2);\n" + " emit_vertex(0, 3, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(1, 3, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(2, 3, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(3, 3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" + " EndPrimitive();\n"; } break; case PRIM_TYPE_QUAD_STRIP: + need_quadz = true; layout_in = "layout(lines_adjacency) in;\n"; if (polygon_mode == POLY_MODE_LINE) { + need_linez = true; layout_out = "layout(line_strip, max_vertices = 5) out;\n"; body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" + " mat4 pz, pzs;\n" + " calc_quadz(2, 0, 1, 3, pz, pzs);\n" + " mat4 pz1 = calc_linez(0, 1);\n" + " pz1[3].x = pz[3].x;\n" + " mat4 pz2 = calc_linez(1, 3);\n" + " pz2[3].x = pzs[3].x;\n" + " mat4 pz3 = calc_linez(3, 2);\n" + " pz3[3].x = pzs[3].x;\n" + " mat4 pz4 = calc_linez(2, 0);\n" + " pz4[3].x = pz[3].x;\n" " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 3);\n" + " emit_vertex(0, 3, pz1);\n" " }\n" - " emit_vertex(1, 3);\n" - " emit_vertex(3, 3);\n" - " emit_vertex(2, 3);\n" - " emit_vertex(0, 3);\n" + " emit_vertex(1, 3, pz1);\n" + " emit_vertex(3, 3, pz2);\n" + " emit_vertex(2, 3, pz3);\n" + " emit_vertex(0, 3, pz4);\n" " EndPrimitive();\n"; } else if (polygon_mode == POLY_MODE_FILL) { layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" - " emit_vertex(0, 3);\n" - " emit_vertex(1, 3);\n" - " emit_vertex(2, 3);\n" - " emit_vertex(3, 3);\n" + " mat4 pz, pz2;\n" + " calc_quadz(2, 0, 1, 3, pz, pz2);\n" + " emit_vertex(0, 3, pz);\n" + " emit_vertex(1, 3, pz2);\n" + " emit_vertex(2, 3, pz);\n" + " emit_vertex(3, 3, pz2);\n" " EndPrimitive();\n"; } else { - assert(false); - return NULL; + assert(polygon_mode == POLY_MODE_POINT); + layout_out = "layout(points, max_vertices = 4) out;\n"; + body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" + " mat4 pz, pz2;\n" + " calc_quadz(2, 0, 1, 3, pz, pz2);\n" + " if (gl_PrimitiveIDIn == 0) {\n" + " emit_vertex(0, 3, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(1, 3, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " EndPrimitive();\n" + " }\n" + " emit_vertex(2, 3, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(3, 3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" + " EndPrimitive();\n"; } break; case PRIM_TYPE_POLYGON: - if (polygon_mode == POLY_MODE_LINE) { - return NULL; - } if (polygon_mode == POLY_MODE_FILL) { - if (state->smooth_shading) { - return NULL; - } + need_triz = true; layout_in = "layout(triangles) in;\n"; layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; - body = " emit_vertex(0, 2);\n" - " emit_vertex(1, 2);\n" - " emit_vertex(2, 2);\n" + body = " mat4 pz = calc_triz(0, 1, 2);\n" + " emit_vertex(0, 0, pz);\n" + " emit_vertex(1, 0, pz);\n" + " emit_vertex(2, 0, pz);\n" + " EndPrimitive();\n"; + } else if (polygon_mode == POLY_MODE_LINE) { + need_linez = true; + // FIXME: input here is lines and not triangles so we cannot + // calculate triangle plane slope. Also, the first vertex of the + // polygon is unavailable so flat shading provoking vertex is + // wrong. + layout_in = "layout(lines) in;\n"; + layout_out = "layout(line_strip, max_vertices = 2) out;\n"; + body = " mat4 pz = calc_linez(0, 1);\n" + " emit_vertex(0, 0, pz);\n" + " emit_vertex(1, 1, pz);\n" " EndPrimitive();\n"; } else { assert(false); @@ -233,6 +316,8 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) mstring_from_fmt("#version %d\n\n" "%s" "%s" + "\n" + "#define v_vtxPos v_vtxPos0\n" "\n", opts.vulkan ? 450 : 400, layout_in, layout_out); pgraph_glsl_get_vtx_header(output, opts.vulkan, state->smooth_shading, true, @@ -241,37 +326,124 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) false, false, false); if (state->smooth_shading) { - mstring_append(output, - "void emit_vertex(int index, int _unused) {\n" - " gl_Position = gl_in[index].gl_Position;\n" - " gl_PointSize = gl_in[index].gl_PointSize;\n" - " vtxD0 = v_vtxD0[index];\n" - " vtxD1 = v_vtxD1[index];\n" - " vtxB0 = v_vtxB0[index];\n" - " vtxB1 = v_vtxB1[index];\n" - " vtxFog = v_vtxFog[index];\n" - " vtxT0 = v_vtxT0[index];\n" - " vtxT1 = v_vtxT1[index];\n" - " vtxT2 = v_vtxT2[index];\n" - " vtxT3 = v_vtxT3[index];\n" - " EmitVertex();\n" - "}\n"); + mstring_append( + output, + "void emit_vertex(int index, int _unused, mat4 pz) {\n" + " gl_Position = gl_in[index].gl_Position;\n" + " gl_PointSize = gl_in[index].gl_PointSize;\n" + " vtxD0 = v_vtxD0[index];\n" + " vtxD1 = v_vtxD1[index];\n" + " vtxB0 = v_vtxB0[index];\n" + " vtxB1 = v_vtxB1[index];\n" + " vtxFog = v_vtxFog[index];\n" + " vtxT0 = v_vtxT0[index];\n" + " vtxT1 = v_vtxT1[index];\n" + " vtxT2 = v_vtxT2[index];\n" + " vtxT3 = v_vtxT3[index];\n" + " vtxPos0 = pz[0];\n" + " vtxPos1 = pz[1];\n" + " vtxPos2 = pz[2];\n" + " triMZ = (isnan(pz[3].x) || isinf(pz[3].x)) ? 0.0 : pz[3].x;\n" + " EmitVertex();\n" + "}\n"); } else { - mstring_append(output, - "void emit_vertex(int index, int provoking_index) {\n" - " gl_Position = gl_in[index].gl_Position;\n" - " gl_PointSize = gl_in[index].gl_PointSize;\n" - " vtxD0 = v_vtxD0[provoking_index];\n" - " vtxD1 = v_vtxD1[provoking_index];\n" - " vtxB0 = v_vtxB0[provoking_index];\n" - " vtxB1 = v_vtxB1[provoking_index];\n" - " vtxFog = v_vtxFog[index];\n" - " vtxT0 = v_vtxT0[index];\n" - " vtxT1 = v_vtxT1[index];\n" - " vtxT2 = v_vtxT2[index];\n" - " vtxT3 = v_vtxT3[index];\n" - " EmitVertex();\n" - "}\n"); + mstring_append( + output, + "void emit_vertex(int index, int provoking_index, mat4 pz) {\n" + " gl_Position = gl_in[index].gl_Position;\n" + " gl_PointSize = gl_in[index].gl_PointSize;\n" + " vtxD0 = v_vtxD0[provoking_index];\n" + " vtxD1 = v_vtxD1[provoking_index];\n" + " vtxB0 = v_vtxB0[provoking_index];\n" + " vtxB1 = v_vtxB1[provoking_index];\n" + " vtxFog = v_vtxFog[index];\n" + " vtxT0 = v_vtxT0[index];\n" + " vtxT1 = v_vtxT1[index];\n" + " vtxT2 = v_vtxT2[index];\n" + " vtxT3 = v_vtxT3[index];\n" + " vtxPos0 = pz[0];\n" + " vtxPos1 = pz[1];\n" + " vtxPos2 = pz[2];\n" + " triMZ = (isnan(pz[3].x) || isinf(pz[3].x)) ? 0.0 : pz[3].x;\n" + " EmitVertex();\n" + "}\n"); + } + + if (need_triz || need_quadz) { + mstring_append( + output, + // Kahan's algorithm for computing a*b - c*d using FMA for higher + // precision. See e.g.: + // Muller et al, "Handbook of Floating-Point Arithmetic", 2nd ed. + // or + // Claude-Pierre Jeannerod, Nicolas Louvet, and Jean-Michel Muller, + // Further analysis of Kahan's algorithm for the accurate + // computation of 2x2 determinants, + // Mathematics of Computation 82(284), October 2013. + "float kahan_det(float a, float b, float c, float d) {\n" + " precise float cd = c*d;\n" + " precise float err = fma(-c, d, cd);\n" + " precise float res = fma(a, b, -cd) + err;\n" + " return res;\n" + "}\n"); + } + + if (state->z_perspective) { + if (need_triz || need_quadz) { + mstring_append( + output, + "mat4 calc_triz(int i0, int i1, int i2) {\n" + " mat2 m = mat2(v_vtxPos[i1].xy - v_vtxPos[i0].xy,\n" + " v_vtxPos[i2].xy - v_vtxPos[i0].xy);\n" + " precise vec2 b = vec2(v_vtxPos[i0].w - v_vtxPos[i1].w,\n" + " v_vtxPos[i0].w - v_vtxPos[i2].w);\n" + " b /= vec2(v_vtxPos[i1].w, v_vtxPos[i2].w) * v_vtxPos[i0].w;\n" + // The following computes dzx and dzy same as + // vec2 dz = b * inverse(m); + " float det = kahan_det(m[0].x, m[1].y, m[1].x, m[0].y);\n" + " float dzx = kahan_det(b.x, m[1].y, b.y, m[0].y) / det;\n" + " float dzy = kahan_det(b.y, m[0].x, b.x, m[1].x) / det;\n" + " float triMZ = max(abs(dzx), abs(dzy));\n" + " return mat4(v_vtxPos[i0], v_vtxPos[i1], v_vtxPos[i2], triMZ, vec3(0.0));\n" + "}\n"); + } + } else { + if (need_triz || need_quadz) { + mstring_append( + output, + "mat4 calc_triz(int i0, int i1, int i2) {\n" + " mat2 m = mat2(v_vtxPos[i1].xy - v_vtxPos[i0].xy,\n" + " v_vtxPos[i2].xy - v_vtxPos[i0].xy);\n" + " precise vec2 b = vec2(v_vtxPos[i1].z - v_vtxPos[i0].z,\n" + " v_vtxPos[i2].z - v_vtxPos[i0].z);\n" + // The following computes dzx and dzy same as + // vec2 dz = b * inverse(m); + " float det = kahan_det(m[0].x, m[1].y, m[1].x, m[0].y);\n" + " float dzx = kahan_det(b.x, m[1].y, b.y, m[0].y) / det;\n" + " float dzy = kahan_det(b.y, m[0].x, b.x, m[1].x) / det;\n" + " float triMZ = max(abs(dzx), abs(dzy));\n" + " return mat4(v_vtxPos[i0], v_vtxPos[i1], v_vtxPos[i2], triMZ, vec3(0.0));\n" + "}\n"); + } + } + + if (need_linez) { + mstring_append( + output, + "mat4 calc_linez(int i0, int i1) {\n" + " vec2 delta = v_vtxPos[i1].xy - v_vtxPos[i0].xy;\n" + " vec2 v2 = vec2(-delta.y, delta.x) + v_vtxPos[i0].xy;\n" + " return mat4(v_vtxPos[i0], v_vtxPos[i1], vec4(v2, v_vtxPos[i0].zw), vec4(0.0));\n" + "}\n"); + } + + if (need_quadz) { + mstring_append( + output, + "void calc_quadz(int i0, int i1, int i2, int i3, out mat4 triz1, out mat4 triz2) {\n" + " triz1 = calc_triz(i0, i1, i2);\n" + " triz2 = calc_triz(i0, i2, i3);\n" + "}\n"); } mstring_append_fmt(output, diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.h b/hw/xbox/nv2a/pgraph/glsl/geom.h index 41ff255161..7dbd807345 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.h +++ b/hw/xbox/nv2a/pgraph/glsl/geom.h @@ -30,6 +30,7 @@ typedef struct { enum ShaderPolygonMode polygon_front_mode; enum ShaderPolygonMode polygon_back_mode; bool smooth_shading; + bool z_perspective; } GeomState; typedef struct GenGeomGlslOptions { diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index 7484b2aa10..ddf04be25c 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -209,6 +209,26 @@ void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state) state->conv_tex[i] = kernel; } + + state->surface_zeta_format = pg->surface_shape.zeta_format; + unsigned int z_format = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER), + NV_PGRAPH_SETUPRASTER_Z_FORMAT); + + switch (pg->surface_shape.zeta_format) { + case NV097_SET_SURFACE_FORMAT_ZETA_Z16: + state->depth_format = + z_format ? DEPTH_FORMAT_F16 : DEPTH_FORMAT_D16; + break; + case NV097_SET_SURFACE_FORMAT_ZETA_Z24S8: + state->depth_format = + z_format ? DEPTH_FORMAT_F24 : DEPTH_FORMAT_D24; + break; + default: + fprintf(stderr, "Unknown zeta surface format: 0x%x\n", + pg->surface_shape.zeta_format); + assert(false); + break; + } } struct InputInfo { @@ -875,6 +895,23 @@ static MString* psh_convert(struct PixelShader *ps) "vec3 dotmap_hilo_hemisphere(vec4 col) {\n" " return col.rgb;\n" // FIXME "}\n" + // Kahan's algorithm for computing determinant using FMA for higher + // precision. See e.g.: + // Muller et al, "Handbook of Floating-Point Arithmetic", 2nd ed. + // or + // Claude-Pierre Jeannerod, Nicolas Louvet, and Jean-Michel Muller, + // Further analysis of Kahan's algorithm for the accurate + // computation of 2x2 determinants, + // Mathematics of Computation 82(284), October 2013. + "float kahan_det(vec2 a, vec2 b) {\n" + " precise float cd = a.y*b.x;\n" + " precise float err = fma(-a.y, b.x, cd);\n" + " precise float res = fma(a.x, b.y, -cd) + err;\n" + " return res;\n" + "}\n" + "float area(vec2 a, vec2 b, vec2 c) {\n" + " return kahan_det(b - a, c - a);\n" + "}\n" "const float[9] gaussian3x3 = float[9](\n" " 1.0/16.0, 2.0/16.0, 1.0/16.0,\n" " 2.0/16.0, 4.0/16.0, 2.0/16.0,\n" @@ -911,45 +948,69 @@ static MString* psh_convert(struct PixelShader *ps) "}\n"); } + if (ps->state->z_perspective) { + mstring_append( + clip, + "vec2 unscaled_xy = gl_FragCoord.xy / surfaceScale;\n" + "precise float bc0 = area(unscaled_xy, vtxPos1.xy, vtxPos2.xy);\n" + "precise float bc1 = area(unscaled_xy, vtxPos2.xy, vtxPos0.xy);\n" + "precise float bc2 = area(unscaled_xy, vtxPos0.xy, vtxPos1.xy);\n" + "bc0 /= vtxPos0.w;\n" + "bc1 /= vtxPos1.w;\n" + "bc2 /= vtxPos2.w;\n" + "float inv_bcsum = 1.0 / (bc0 + bc1 + bc2);\n" + // Denominator can be zero in case the rasterized primitive is a + // point or a degenerate line or triangle. + "if (isinf(inv_bcsum)) {\n" + " inv_bcsum = 0.0;\n" + "}\n" + "bc1 *= inv_bcsum;\n" + "bc2 *= inv_bcsum;\n" + "precise float zvalue = vtxPos0.w + (bc1*(vtxPos1.w - vtxPos0.w) + bc2*(vtxPos2.w - vtxPos0.w));\n" + // If GPU clipping is inaccurate, the point gl_FragCoord.xy might + // be above the horizon of the plane of a rasterized triangle + // making the interpolated w-coordinate above zero or negative. We + // should prevent such wrapping through infinity by clamping to + // infinity. + "if (zvalue > 0.0) {\n" + " float zslopeofs = depthFactor*triMZ*zvalue*zvalue;\n" + " zvalue += depthOffset;\n" + " zvalue += zslopeofs;\n" + "} else {\n" + " zvalue = uintBitsToFloat(0x7F7FFFFFu);\n" + "}\n" + "if (isnan(zvalue)) {\n" + " zvalue = uintBitsToFloat(0x7F7FFFFFu);\n" + "}\n"); + } else { + mstring_append( + clip, + "vec2 unscaled_xy = gl_FragCoord.xy / surfaceScale;\n" + "precise float bc0 = area(unscaled_xy, vtxPos1.xy, vtxPos2.xy);\n" + "precise float bc1 = area(unscaled_xy, vtxPos2.xy, vtxPos0.xy);\n" + "precise float bc2 = area(unscaled_xy, vtxPos0.xy, vtxPos1.xy);\n" + "float inv_bcsum = 1.0 / (bc0 + bc1 + bc2);\n" + // Denominator can be zero in case the rasterized primitive is a + // point or a degenerate line or triangle. + "if (isinf(inv_bcsum)) {\n" + " inv_bcsum = 0.0;\n" + "}\n" + "bc1 *= inv_bcsum;\n" + "bc2 *= inv_bcsum;\n" + "precise float zvalue = vtxPos0.z + (bc1*(vtxPos1.z - vtxPos0.z) + bc2*(vtxPos2.z - vtxPos0.z));\n" + "zvalue += depthOffset;\n" + "zvalue += depthFactor*triMZ;\n"); + } + + /* Depth clipping */ if (ps->state->depth_clipping) { - if (ps->state->z_perspective) { - mstring_append( - clip, "float zvalue = 1.0/gl_FragCoord.w + depthOffset;\n" - "if (zvalue < clipRange.z || clipRange.w < zvalue) {\n" - " discard;\n" - "}\n"); - } else { - /* Take care of floating point precision problems. MS dashboard - * outputs exactly 0.0 z-coordinates and then our fixed function - * vertex shader outputs -w as the z-coordinate when OpenGL is - * used. Since -w/w = -1, this should give us exactly 0.0 as - * gl_FragCoord.z here. Unfortunately, with AMD Radeon RX 6600 the - * result is slightly greater than 0. MS dashboard sets the clip - * range to [0.0, 0.0] and so the imprecision causes unwanted - * clipping. Note that since Vulkan uses NDC range [0,1] it - * doesn't suffer from this problem with Radeon. Also, despite the - * imprecision OpenGL Radeon writes the correct value 0 to the depth - * buffer (if writing is enabled.) Radeon appears to write floored - * values. To compare, Intel integrated UHD 770 has gl_FragCoord.z - * exactly 0 (and writes rounded to closest integer values to the - * depth buffer.) Radeon OpenGL problem could also be fixed by using - * glClipControl(), but it requires OpenGL 4.5. - * Above is based on experiments with Linux and Mesa. - */ - if (ps->opts.vulkan) { - mstring_append( - clip, "if (gl_FragCoord.z*clipRange.y < clipRange.z ||\n" - " gl_FragCoord.z*clipRange.y > clipRange.w) {\n" - " discard;\n" - "}\n"); - } else { - mstring_append( - clip, "if ((gl_FragCoord.z + 1.0f/16777216.0f)*clipRange.y < clipRange.z ||\n" - " (gl_FragCoord.z - 1.0f/16777216.0f)*clipRange.y > clipRange.w) {\n" - " discard;\n" - "}\n"); - } - } + mstring_append( + clip, "if (zvalue < clipRange.z || clipRange.w < zvalue) {\n" + " discard;\n" + "}\n"); + } else { + mstring_append( + clip, "zvalue = clamp(zvalue, clipRange.z, clipRange.w);\n"); } MString *vars = mstring_new(); @@ -1334,21 +1395,33 @@ static MString* psh_convert(struct PixelShader *ps) } } - if (ps->state->z_perspective) { - if (!ps->state->depth_clipping) { - mstring_append(ps->code, - "float zvalue = 1.0/gl_FragCoord.w + depthOffset;\n"); - } - /* TODO: With integer depth buffers Xbox hardware floors values and so - * does Radeon, but Intel UHD 770 rounds to nearest. Should probably - * floor here explicitly (in some way that doesn't also cause - * imprecision issues due to division by clipRange.y) - */ - mstring_append(ps->code, - "gl_FragDepth = clamp(zvalue, clipRange.z, clipRange.w)/clipRange.y;\n"); - } else if (!ps->state->depth_clipping) { - mstring_append(ps->code, - "gl_FragDepth = clamp(gl_FragCoord.z, clipRange.z/clipRange.y, clipRange.w/clipRange.y);\n"); + /* With integer depth buffers Xbox hardware floors values. For gl_FragDepth + * range [0,1] Radeon floors values to integer depth buffer, but Intel UHD + * 770 rounds to nearest. For 24-bit OpenGL/Vulkan integer depth buffer, + * we divide the desired depth integer value by 16777216.0, then add 1 in + * integer bit representation to get the same result as dividing the + * desired depth integer by 16777215.0 would give. (GPUs can't divide by + * 16777215.0, only multiply by 1.0/16777215.0 which gives different results + * due to rounding.) + */ + + switch (ps->state->depth_format) { + case DEPTH_FORMAT_D16: + // 16-bit unsigned int + mstring_append( + ps->code, + "gl_FragDepth = floor(zvalue) / 65535.0;\n"); + break; + case DEPTH_FORMAT_D24: + // 24-bit unsigned int + mstring_append( + ps->code, + "gl_FragDepth = uintBitsToFloat(floatBitsToUint(floor(zvalue) / 16777216.0) + 1u);\n"); + break; + default: + // TODO: handle floating-point depth buffers properly + mstring_append(ps->code, "gl_FragDepth = zvalue / clipRange.y;\n"); + break; } MString *final = mstring_new(); @@ -1542,31 +1615,62 @@ void pgraph_glsl_set_psh_uniform_values(PGRAPHState *pg, pgraph_glsl_set_clip_range_uniform_value(pg, values->clipRange[0]); } + bool polygon_offset_enabled = false; + if (pg->primitive_mode >= PRIM_TYPE_TRIANGLES) { + uint32_t raster = pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER); + uint32_t polygon_mode = + GET_MASK(raster, NV_PGRAPH_SETUPRASTER_FRONTFACEMODE); + + if ((polygon_mode == NV_PGRAPH_SETUPRASTER_FRONTFACEMODE_FILL && + (raster & NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE)) || + (polygon_mode == NV_PGRAPH_SETUPRASTER_FRONTFACEMODE_LINE && + (raster & NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE)) || + (polygon_mode == NV_PGRAPH_SETUPRASTER_FRONTFACEMODE_POINT && + (raster & NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE))) { + polygon_offset_enabled = true; + } + } + if (locs[PshUniform_depthOffset] != -1) { float zbias = 0.0f; - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - (NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE)) { + if (polygon_offset_enabled) { uint32_t zbias_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETBIAS); zbias = *(float *)&zbias_u32; - - if (pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETFACTOR) != 0 && - (pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & - NV_PGRAPH_CONTROL_0_Z_PERSPECTIVE_ENABLE)) { - /* TODO: emulate zfactor when z_perspective true, i.e. - * w-buffering. Perhaps calculate an additional offset based on - * triangle orientation in geometry shader and pass the result - * to fragment shader and add it to gl_FragDepth as well. - */ - NV2A_UNIMPLEMENTED("NV_PGRAPH_ZOFFSETFACTOR for w-buffering"); - } } values->depthOffset[0] = zbias; } + if (locs[PshUniform_depthFactor] != -1) { + float zfactor = 0.0f; + + if (polygon_offset_enabled) { + uint32_t zfactor_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETFACTOR); + zfactor = *(float *)&zfactor_u32; + if (zfactor != 0.0f && + (pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & + NV_PGRAPH_CONTROL_0_Z_PERSPECTIVE_ENABLE)) { + /* FIXME: for w-buffering, polygon slope in screen-space is + * computed per-pixel, but Xbox appears to use constant that + * is the polygon slope at the first visible pixel in top-left + * order. + */ + NV2A_UNIMPLEMENTED("NV_PGRAPH_ZOFFSETFACTOR only partially implemented for w-buffering"); + } + } + + values->depthFactor[0] = zfactor; + } + + if (locs[PshUniform_surfaceScale] != -1) { + unsigned int wscale = 1, hscale = 1; + pgraph_apply_anti_aliasing_factor(pg, &wscale, &hscale); + pgraph_apply_scaling_factor(pg, &wscale, &hscale); + values->surfaceScale[0][0] = wscale; + values->surfaceScale[0][1] = hscale; + } + unsigned int max_gl_width = pg->surface_binding_dim.width; unsigned int max_gl_height = pg->surface_binding_dim.height; pgraph_apply_scaling_factor(pg, &max_gl_width, &max_gl_height); diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.h b/hw/xbox/nv2a/pgraph/glsl/psh.h index 1a04c53dff..84d3137a0a 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.h +++ b/hw/xbox/nv2a/pgraph/glsl/psh.h @@ -27,6 +27,13 @@ typedef struct PGRAPHState PGRAPHState; +enum PshDepthFormat { + DEPTH_FORMAT_D24, + DEPTH_FORMAT_D16, + DEPTH_FORMAT_F24, + DEPTH_FORMAT_F16, +}; + typedef struct PshState { uint32_t combiner_control; uint32_t shader_stage_program; @@ -61,6 +68,9 @@ typedef struct PshState { bool smooth_shading; bool depth_clipping; bool z_perspective; + + unsigned int surface_zeta_format; + enum PshDepthFormat depth_format; } PshState; void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state); @@ -75,8 +85,10 @@ void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state); DECL(S, colorKey, uint, 4) \ DECL(S, colorKeyMask, uint, 4) \ DECL(S, consts, vec4, 18) \ + DECL(S, depthFactor, float, 1) \ DECL(S, depthOffset, float, 1) \ DECL(S, fogColor, vec4, 1) \ + DECL(S, surfaceScale, ivec2, 1) \ DECL(S, texScale, float, 4) DECL_UNIFORM_TYPES(PshUniform, PSH_UNIFORM_DECL_X) diff --git a/hw/xbox/nv2a/pgraph/glsl/shaders.c b/hw/xbox/nv2a/pgraph/glsl/shaders.c index 44ed5437fc..2d6cfaf7d2 100644 --- a/hw/xbox/nv2a/pgraph/glsl/shaders.c +++ b/hw/xbox/nv2a/pgraph/glsl/shaders.c @@ -73,7 +73,8 @@ bool pgraph_glsl_check_shader_state_dirty(PGRAPHState *pg, pg->swizzle_attrs != state->vsh.swizzle_attrs || pg->compressed_attrs != state->vsh.compressed_attrs || pg->primitive_mode != state->geom.primitive_mode || - pg->surface_scale_factor != state->vsh.surface_scale_factor) { + pg->surface_scale_factor != state->vsh.surface_scale_factor || + pg->surface_shape.zeta_format != state->psh.surface_zeta_format) { return true; } diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index a892c2001b..703c1595c5 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -479,11 +479,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, " 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 = roundScreenCoords(oPos.xy);\n" + " vec4 vtxPos = vec4(oPos.xy, oPos.z / oPos.w, oPos.w);\n" + " oPos.z = oPos.z / clipRange.y;\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 582194af89..4a1d57b1f1 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -755,10 +755,10 @@ void pgraph_glsl_gen_vsh_prog(uint16_t version, const uint32_t *tokens, * in clip space. */ " oPos.xy = roundScreenCoords(oPos.xy);\n" - " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" - - " oPos.z = oPos.z / clipRange.y;\n" " oPos.w = clampAwayZeroInf(oPos.w);\n" + " vec4 vtxPos = oPos;\n" + " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" + " oPos.z = oPos.z / clipRange.y;\n" /* Undo perspective divide by w. * Note that games may also have vertex shaders that do diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index 9bce9f30bf..5b0857dc75 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -245,6 +245,10 @@ MString *pgraph_glsl_gen_vsh(const VshState *state, GenVshGlslOptions opts) "#define vtxT1 v_vtxT1\n" "#define vtxT2 v_vtxT2\n" "#define vtxT3 v_vtxT3\n" + "#define vtxPos0 v_vtxPos0\n" + "#define vtxPos1 v_vtxPos1\n" + "#define vtxPos2 v_vtxPos2\n" + "#define triMZ v_triMZ\n" ); } mstring_append(header, "\n"); @@ -393,6 +397,10 @@ MString *pgraph_glsl_gen_vsh(const VshState *state, GenVshGlslOptions opts) " vtxT1 = oT1;\n" " vtxT2 = oT2;\n" " vtxT3 = oT3;\n" + " vtxPos0 = vtxPos;\n" + " vtxPos1 = vtxPos;\n" + " vtxPos2 = vtxPos;\n" + " triMZ = 0.0;\n" " gl_PointSize = oPts.x;\n" ); diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c index 28b8194468..c982d19bc4 100644 --- a/hw/xbox/nv2a/pgraph/vk/draw.c +++ b/hw/xbox/nv2a/pgraph/vk/draw.c @@ -54,10 +54,6 @@ static VkPrimitiveTopology get_primitive_topology(PGRAPHState *pg) int polygon_mode = r->shader_binding->state.geom.polygon_front_mode; int primitive_mode = r->shader_binding->state.geom.primitive_mode; - if (polygon_mode == POLY_MODE_POINT) { - return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; - } - // FIXME: Replace with LUT switch (primitive_mode) { case PRIM_TYPE_POINTS: @@ -795,12 +791,10 @@ static void create_pipeline(PGRAPHState *pg) VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_state; if (r->provoking_vertex_extension_enabled) { + // TODO: remove use of provoking vertex extension since we just want + // the default last vertex convention always. VkProvokingVertexModeEXT provoking_mode = - GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_SHADEMODE) == - NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT ? - VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT : - VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; + VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT; provoking_state = (VkPipelineRasterizationProvokingVertexStateCreateInfoEXT){ @@ -809,8 +803,6 @@ static void create_pipeline(PGRAPHState *pg) .provokingVertexMode = provoking_mode, }; rasterizer_next_struct = &provoking_state; - } else { - // FIXME: Handle in shader? } VkPipelineRasterizationStateCreateInfo rasterizer = { @@ -968,27 +960,6 @@ static void create_pipeline(PGRAPHState *pg) .pDynamicStates = dynamic_states, }; - // /* Polygon offset */ - // /* FIXME: GL implementation-specific, maybe do this in VS? */ - // if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - // NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE) - // if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - // NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE) - // if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - // NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE) - if (pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & - (NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE)) { - uint32_t zfactor_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETFACTOR); - float zfactor = *(float *)&zfactor_u32; - uint32_t zbias_u32 = pgraph_reg_r(pg, NV_PGRAPH_ZOFFSETBIAS); - float zbias = *(float *)&zbias_u32; - rasterizer.depthBiasEnable = VK_TRUE; - rasterizer.depthBiasSlopeFactor = zfactor; - rasterizer.depthBiasConstantFactor = zbias; - } - // FIXME: Dither // if (pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & // NV_PGRAPH_CONTROL_0_DITHERENABLE)) diff --git a/hw/xbox/nv2a/pgraph/vk/surface-compute.c b/hw/xbox/nv2a/pgraph/vk/surface-compute.c index 54cf610402..50180eb6b9 100644 --- a/hw/xbox/nv2a/pgraph/vk/surface-compute.c +++ b/hw/xbox/nv2a/pgraph/vk/surface-compute.c @@ -106,7 +106,8 @@ const char *unpack_z24s8_to_d32_sfloat_s8_uint_glsl = "void main() {\n" " uint idx_out = gl_GlobalInvocationID.x;\n" " uint idx_in = get_input_idx(idx_out);\n" - " depth_out[idx_out] = float(depth_stencil_in[idx_in] >> 8) / float(0xffffff);\n" + // Conversion to float depth must be the same as in fragment shader + " depth_out[idx_out] = uintBitsToFloat(floatBitsToUint(float(depth_stencil_in[idx_in] >> 8) / 16777216.0) + 1u);\n" " if (idx_out % 4 == 0) {\n" " uint stencil_value = 0;\n" " for (int i = 0; i < 4; i++) {\n" // Include next 3 pixels