diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c
index b61e561298..fe71cd1bd0 100644
--- a/hw/xbox/nv2a/pgraph/gl/draw.c
+++ b/hw/xbox/nv2a/pgraph/gl/draw.c
@@ -21,6 +21,7 @@
#include "qemu/fast-hash.h"
#include "hw/xbox/nv2a/nv2a_int.h"
+#include "hw/xbox/nv2a/pgraph/prim_rewrite.h"
#include "debug.h"
#include "renderer.h"
@@ -401,6 +402,19 @@ void pgraph_gl_flush_draw(NV2AState *d)
}
assert(r->shader_binding);
+ PrimAssemblyState assembly = {
+ .primitive_mode = pg->primitive_mode,
+ .polygon_mode = (enum ShaderPolygonMode)GET_MASK(
+ pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
+ NV_PGRAPH_SETUPRASTER_FRONTFACEMODE),
+ .last_provoking = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3),
+ NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) ==
+ NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_LAST,
+ .flat_shading = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3),
+ NV_PGRAPH_CONTROL_3_SHADEMODE) ==
+ NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT,
+ };
+
if (pg->draw_arrays_length) {
NV2A_GL_DPRINTF(false, "Draw Arrays");
nv2a_profile_inc_counter(NV2A_PROF_DRAW_ARRAYS);
@@ -412,51 +426,83 @@ void pgraph_gl_flush_draw(NV2AState *d)
pg->draw_arrays_max_count - 1,
false, 0,
pg->draw_arrays_max_count - 1);
- glMultiDrawArrays(r->shader_binding->gl_primitive_mode,
- pg->draw_arrays_start,
- pg->draw_arrays_count,
- pg->draw_arrays_length);
+
+ PrimRewrite prim_rw = pgraph_prim_rewrite_ranges(
+ &r->prim_rewrite_buf, assembly, pg->draw_arrays_start,
+ pg->draw_arrays_count, pg->draw_arrays_length);
+
+ if (prim_rw.num_indices > 0) {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, r->gl_prim_rewrite_buffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ prim_rw.num_indices * sizeof(uint32_t),
+ prim_rw.indices, GL_STREAM_DRAW);
+ glDrawElements(r->shader_binding->gl_primitive_mode,
+ prim_rw.num_indices, GL_UNSIGNED_INT, (void *)0);
+ } else {
+ glMultiDrawArrays(r->shader_binding->gl_primitive_mode,
+ pg->draw_arrays_start, pg->draw_arrays_count,
+ pg->draw_arrays_length);
+ }
} else if (pg->inline_elements_length) {
NV2A_GL_DPRINTF(false, "Inline Elements");
nv2a_profile_inc_counter(NV2A_PROF_INLINE_ELEMENTS);
assert(pg->inline_buffer_length == 0);
assert(pg->inline_array_length == 0);
+ uint32_t *draw_indices = pg->inline_elements;
+ unsigned int draw_index_count = pg->inline_elements_length;
+ PrimRewrite prim_rw = pgraph_prim_rewrite_indexed(
+ &r->prim_rewrite_buf, assembly, pg->inline_elements,
+ pg->inline_elements_length);
+ if (prim_rw.num_indices > 0) {
+ draw_indices = prim_rw.indices;
+ draw_index_count = prim_rw.num_indices;
+ }
+
uint32_t min_element = (uint32_t)-1;
uint32_t max_element = 0;
- for (int i=0; i < pg->inline_elements_length; i++) {
- max_element = MAX(pg->inline_elements[i], max_element);
- min_element = MIN(pg->inline_elements[i], min_element);
+ for (unsigned int i = 0; i < draw_index_count; i++) {
+ max_element = MAX(draw_indices[i], max_element);
+ min_element = MIN(draw_indices[i], min_element);
}
pgraph_gl_bind_vertex_attributes(
d, min_element, max_element, false, 0,
- pg->inline_elements[pg->inline_elements_length - 1]);
+ draw_indices[draw_index_count - 1]);
- VertexKey k;
- memset(&k, 0, sizeof(VertexKey));
- k.count = pg->inline_elements_length;
- k.gl_type = GL_UNSIGNED_INT;
- k.gl_normalize = GL_FALSE;
- k.stride = sizeof(uint32_t);
- uint64_t h = fast_hash((uint8_t*)pg->inline_elements,
- pg->inline_elements_length * 4);
-
- LruNode *node = lru_lookup(&r->element_cache, h, &k);
- VertexLruNode *found = container_of(node, VertexLruNode, node);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, found->gl_buffer);
- if (!found->initialized) {
- nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4);
+ if (prim_rw.num_indices > 0) {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, r->gl_prim_rewrite_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- pg->inline_elements_length * 4,
- pg->inline_elements, GL_STATIC_DRAW);
- found->initialized = true;
+ draw_index_count * sizeof(uint32_t), draw_indices,
+ GL_STREAM_DRAW);
+ glDrawElements(r->shader_binding->gl_primitive_mode,
+ draw_index_count, GL_UNSIGNED_INT, (void *)0);
} else {
- nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4_NOTDIRTY);
+ VertexKey k;
+ memset(&k, 0, sizeof(VertexKey));
+ k.count = pg->inline_elements_length;
+ k.gl_type = GL_UNSIGNED_INT;
+ k.gl_normalize = GL_FALSE;
+ k.stride = sizeof(uint32_t);
+ uint64_t h = fast_hash((uint8_t*)pg->inline_elements,
+ pg->inline_elements_length * 4);
+
+ LruNode *node = lru_lookup(&r->element_cache, h, &k);
+ VertexLruNode *found = container_of(node, VertexLruNode, node);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, found->gl_buffer);
+ if (!found->initialized) {
+ nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ pg->inline_elements_length * 4,
+ pg->inline_elements, GL_STATIC_DRAW);
+ found->initialized = true;
+ } else {
+ nv2a_profile_inc_counter(NV2A_PROF_GEOM_BUFFER_UPDATE_4_NOTDIRTY);
+ }
+ glDrawElements(r->shader_binding->gl_primitive_mode,
+ pg->inline_elements_length, GL_UNSIGNED_INT,
+ (void *)0);
}
- glDrawElements(r->shader_binding->gl_primitive_mode,
- pg->inline_elements_length, GL_UNSIGNED_INT,
- (void *)0);
} else if (pg->inline_buffer_length) {
NV2A_GL_DPRINTF(false, "Inline Buffer");
nv2a_profile_inc_counter(NV2A_PROF_INLINE_BUFFERS);
@@ -487,15 +533,40 @@ void pgraph_gl_flush_draw(NV2AState *d)
}
}
- glDrawArrays(r->shader_binding->gl_primitive_mode,
- 0, pg->inline_buffer_length);
+ PrimRewrite prim_rw = pgraph_prim_rewrite_sequential(
+ &r->prim_rewrite_buf, assembly, 0, pg->inline_buffer_length);
+
+ if (prim_rw.num_indices > 0) {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, r->gl_prim_rewrite_buffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ prim_rw.num_indices * sizeof(uint32_t),
+ prim_rw.indices, GL_STREAM_DRAW);
+ glDrawElements(r->shader_binding->gl_primitive_mode,
+ prim_rw.num_indices, GL_UNSIGNED_INT, (void *)0);
+ } else {
+ glDrawArrays(r->shader_binding->gl_primitive_mode,
+ 0, pg->inline_buffer_length);
+ }
} else if (pg->inline_array_length) {
NV2A_GL_DPRINTF(false, "Inline Array");
nv2a_profile_inc_counter(NV2A_PROF_INLINE_ARRAYS);
unsigned int index_count = pgraph_gl_bind_inline_array(d);
- glDrawArrays(r->shader_binding->gl_primitive_mode,
- 0, index_count);
+
+ PrimRewrite prim_rw = pgraph_prim_rewrite_sequential(
+ &r->prim_rewrite_buf, assembly, 0, index_count);
+
+ if (prim_rw.num_indices > 0) {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, r->gl_prim_rewrite_buffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ prim_rw.num_indices * sizeof(uint32_t),
+ prim_rw.indices, GL_STREAM_DRAW);
+ glDrawElements(r->shader_binding->gl_primitive_mode,
+ prim_rw.num_indices, GL_UNSIGNED_INT, (void *)0);
+ } else {
+ glDrawArrays(r->shader_binding->gl_primitive_mode,
+ 0, index_count);
+ }
} else {
NV2A_GL_DPRINTF(true, "EMPTY NV097_SET_BEGIN_END");
NV2A_UNCONFIRMED("EMPTY NV097_SET_BEGIN_END");
diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h
index 6bdb1043b0..7ac0ab7400 100644
--- a/hw/xbox/nv2a/pgraph/gl/renderer.h
+++ b/hw/xbox/nv2a/pgraph/gl/renderer.h
@@ -28,6 +28,7 @@
#include "qemu/lru.h"
#include "hw/hw.h"
+#include "hw/xbox/nv2a/pgraph/prim_rewrite.h"
#include "hw/xbox/nv2a/nv2a_int.h"
#include "hw/xbox/nv2a/nv2a_regs.h"
@@ -182,6 +183,8 @@ typedef struct PGRAPHGLState {
GLuint gl_memory_buffer;
GLuint gl_vertex_array;
GLuint gl_inline_buffer[NV2A_VERTEXSHADER_ATTRIBUTES];
+ GLuint gl_prim_rewrite_buffer;
+ PrimRewriteBuf prim_rewrite_buf;
QTAILQ_HEAD(, SurfaceBinding) surfaces;
SurfaceBinding *color_binding, *zeta_binding;
diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c
index 45ec382ecd..3240f31744 100644
--- a/hw/xbox/nv2a/pgraph/gl/shaders.c
+++ b/hw/xbox/nv2a/pgraph/gl/shaders.c
@@ -29,27 +29,12 @@
#include "debug.h"
#include "renderer.h"
-static GLenum get_gl_primitive_mode(enum ShaderPolygonMode polygon_mode, enum ShaderPrimitiveMode primitive_mode)
+static GLenum get_gl_primitive_mode(enum ShaderPrimitiveMode primitive_mode)
{
switch (primitive_mode) {
case PRIM_TYPE_POINTS: return GL_POINTS;
case PRIM_TYPE_LINES: return GL_LINES;
- case PRIM_TYPE_LINE_LOOP: return GL_LINE_LOOP;
- case PRIM_TYPE_LINE_STRIP: return GL_LINE_STRIP;
case PRIM_TYPE_TRIANGLES: return GL_TRIANGLES;
- case PRIM_TYPE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
- case PRIM_TYPE_TRIANGLE_FAN: return GL_TRIANGLE_FAN;
- case PRIM_TYPE_QUADS: return GL_LINES_ADJACENCY;
- case PRIM_TYPE_QUAD_STRIP: return GL_LINE_STRIP_ADJACENCY;
- case PRIM_TYPE_POLYGON:
- if (polygon_mode == POLY_MODE_LINE) {
- return GL_LINE_LOOP;
- } else if (polygon_mode == POLY_MODE_FILL) {
- return GL_TRIANGLE_FAN;
- }
-
- assert(!"PRIM_TYPE_POLYGON with invalid polygon_mode");
- return 0;
default:
assert(!"Invalid primitive_mode");
return 0;
@@ -244,8 +229,8 @@ static void generate_shaders(PGRAPHGLState *r, ShaderBinding *binding)
glUseProgram(program);
binding->gl_program = program;
- binding->gl_primitive_mode = get_gl_primitive_mode(
- state->geom.polygon_front_mode, state->geom.primitive_mode);
+ binding->gl_primitive_mode =
+ get_gl_primitive_mode(state->geom.primitive_mode);
binding->initialized = true;
set_texture_sampler_uniforms(binding);
@@ -359,8 +344,7 @@ bool pgraph_gl_shader_load_from_memory(ShaderBinding *binding)
binding->program = NULL;
binding->gl_program = gl_program;
binding->gl_primitive_mode =
- get_gl_primitive_mode(binding->state.geom.polygon_front_mode,
- binding->state.geom.primitive_mode);
+ get_gl_primitive_mode(binding->state.geom.primitive_mode);
binding->initialized = true;
set_texture_sampler_uniforms(binding);
diff --git a/hw/xbox/nv2a/pgraph/gl/vertex.c b/hw/xbox/nv2a/pgraph/gl/vertex.c
index 13b5639b9b..8563a50fb1 100644
--- a/hw/xbox/nv2a/pgraph/gl/vertex.c
+++ b/hw/xbox/nv2a/pgraph/gl/vertex.c
@@ -271,6 +271,8 @@ void pgraph_gl_init_buffers(NV2AState *d)
glGenBuffers(NV2A_VERTEXSHADER_ATTRIBUTES, r->gl_inline_buffer);
glGenBuffers(1, &r->gl_inline_array_buffer);
+ glGenBuffers(1, &r->gl_prim_rewrite_buffer);
+ pgraph_prim_rewrite_init(&r->prim_rewrite_buf);
glGenBuffers(1, &r->gl_memory_buffer);
glBindBuffer(GL_ARRAY_BUFFER, r->gl_memory_buffer);
@@ -303,6 +305,10 @@ void pgraph_gl_finalize_buffers(PGRAPHState *pg)
glDeleteBuffers(1, &r->gl_inline_array_buffer);
r->gl_inline_array_buffer = 0;
+ glDeleteBuffers(1, &r->gl_prim_rewrite_buffer);
+ r->gl_prim_rewrite_buffer = 0;
+ pgraph_prim_rewrite_finalize(&r->prim_rewrite_buf);
+
glDeleteBuffers(1, &r->gl_memory_buffer);
r->gl_memory_buffer = 0;
diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c
index 559101130d..5bb4ffab88 100644
--- a/hw/xbox/nv2a/pgraph/glsl/geom.c
+++ b/hw/xbox/nv2a/pgraph/glsl/geom.c
@@ -21,12 +21,11 @@
#include "qemu/osdep.h"
#include "hw/xbox/nv2a/pgraph/pgraph.h"
+#include "hw/xbox/nv2a/pgraph/prim_rewrite.h"
#include "geom.h"
void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *state)
{
- state->primitive_mode = (enum ShaderPrimitiveMode)pg->primitive_mode;
-
state->polygon_front_mode = (enum ShaderPolygonMode)GET_MASK(
pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
NV_PGRAPH_SETUPRASTER_FRONTFACEMODE);
@@ -34,75 +33,26 @@ void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *state)
pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
NV_PGRAPH_SETUPRASTER_BACKFACEMODE);
+ state->primitive_mode = pgraph_prim_rewrite_get_output_mode(
+ (enum ShaderPrimitiveMode)pg->primitive_mode,
+ state->polygon_front_mode);
+
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->first_vertex_is_provoking =
- GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3),
- NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) ==
- NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_FIRST;
-
state->z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) &
NV_PGRAPH_CONTROL_0_Z_PERSPECTIVE_ENABLE;
-
- if (pg->renderer->ops.get_gpu_properties) {
- GPUProperties *gpu_props = pg->renderer->ops.get_gpu_properties();
-
- switch (state->primitive_mode) {
- case PRIM_TYPE_TRIANGLES:
- state->tri_rot0 = gpu_props->geom_shader_winding.tri;
- state->tri_rot1 = state->tri_rot0;
- break;
- case PRIM_TYPE_TRIANGLE_STRIP:
- state->tri_rot0 = gpu_props->geom_shader_winding.tri_strip0;
- state->tri_rot1 = gpu_props->geom_shader_winding.tri_strip1;
- break;
- case PRIM_TYPE_TRIANGLE_FAN:
- case PRIM_TYPE_POLYGON:
- state->tri_rot0 = gpu_props->geom_shader_winding.tri_fan;
- state->tri_rot1 = state->tri_rot0;
- break;
- default:
- break;
- }
- }
-}
-
-static const char *get_vertex_order(int rot)
-{
- if (rot == 0) {
- return "ivec3(0, 1, 2)";
- } else if (rot == 1) {
- return "ivec3(2, 0, 1)";
- } else {
- return "ivec3(1, 2, 0)";
- }
}
bool pgraph_glsl_need_geom(const GeomState *state)
{
/* FIXME: Missing support for 2-sided-poly mode */
assert(state->polygon_front_mode == state->polygon_back_mode);
- enum ShaderPolygonMode polygon_mode = state->polygon_front_mode;
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:
- case PRIM_TYPE_TRIANGLE_STRIP:
- case PRIM_TYPE_TRIANGLE_FAN:
- case PRIM_TYPE_QUADS:
- case PRIM_TYPE_QUAD_STRIP:
- return true;
- case PRIM_TYPE_POLYGON:
- if (polygon_mode == POLY_MODE_POINT) {
- assert(false);
- return false;
- }
return true;
default:
return false;
@@ -116,184 +66,54 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts)
enum ShaderPolygonMode polygon_mode = state->polygon_front_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;
- const char *provoking_index = "0";
+ const char *provoking_index = state->smooth_shading ? "index" : "0";
- /* TODO: frontface/backface culling for polygon modes POLY_MODE_LINE and
- * POLY_MODE_POINT.
- */
switch (state->primitive_mode) {
case PRIM_TYPE_POINTS: return NULL;
case PRIM_TYPE_LINES:
- case PRIM_TYPE_LINE_LOOP:
- case PRIM_TYPE_LINE_STRIP:
- provoking_index = state->first_vertex_is_provoking ? "0" : "1";
need_linez = true;
layout_in = "layout(lines) in;\n";
layout_out = "layout(line_strip, max_vertices = 2) out;\n";
body = " emit_line(0, 1, 0.0);\n";
break;
case PRIM_TYPE_TRIANGLES:
- case PRIM_TYPE_TRIANGLE_STRIP:
- case PRIM_TYPE_TRIANGLE_FAN:
- if (state->first_vertex_is_provoking) {
- provoking_index = "v[0]";
- } else if (state->primitive_mode == PRIM_TYPE_TRIANGLE_STRIP) {
- provoking_index = "v[2 - (gl_PrimitiveIDIn & 1)]";
- } else if (state->primitive_mode == PRIM_TYPE_TRIANGLE_FAN) {
- provoking_index = "v[1]";
- } else {
- provoking_index = "v[2]";
- }
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(v[0], v[1], v[2]);\n"
- " emit_vertex(v[0], pz);\n"
- " emit_vertex(v[1], pz);\n"
- " emit_vertex(v[2], pz);\n"
+ body = " mat4 pz = calc_triz(0, 1, 2);\n"
+ " emit_vertex(0, pz);\n"
+ " emit_vertex(1, pz);\n"
+ " emit_vertex(2, pz);\n"
" EndPrimitive();\n";
} else if (polygon_mode == POLY_MODE_LINE) {
need_linez = true;
layout_out = "layout(line_strip, max_vertices = 6) out;\n";
- body = " float dz = calc_triz(v[0], v[1], v[2])[3].x;\n"
- " emit_line(v[0], v[1], dz);\n"
- " emit_line(v[1], v[2], dz);\n"
- " emit_line(v[2], v[0], dz);\n";
+ body = " float dz = calc_triz(0, 1, 2)[3].x;\n"
+ " emit_line(0, 1, dz);\n"
+ " emit_line(1, 2, dz);\n"
+ " emit_line(2, 0, dz);\n";
} else {
assert(polygon_mode == POLY_MODE_POINT);
layout_out = "layout(points, max_vertices = 3) out;\n";
- body = " mat4 pz = calc_triz(v[0], v[1], v[2]);\n"
- " emit_vertex(v[0], mat4(pz[0], pz[0], pz[0], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(v[1], mat4(pz[1], pz[1], pz[1], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(v[2], mat4(pz[2], pz[2], pz[2], pz[3]));\n"
- " EndPrimitive();\n";
- }
- break;
- case PRIM_TYPE_QUADS:
- provoking_index = "3";
- need_quadz = true;
- layout_in = "layout(lines_adjacency) in;\n";
- if (polygon_mode == POLY_MODE_FILL) {
- layout_out = "layout(triangle_strip, max_vertices = 6) out;\n";
- body = " mat4 pz, pz2;\n"
- " calc_quadz(0, 1, 2, 3, pz, pz2);\n"
- " emit_vertex(1, pz);\n"
- " emit_vertex(2, pz);\n"
- " emit_vertex(0, pz);\n"
- " EndPrimitive();\n"
- " emit_vertex(2, pz2);\n"
- " emit_vertex(3, pz2);\n"
- " emit_vertex(0, pz2);\n"
- " EndPrimitive();\n";
- } else if (polygon_mode == POLY_MODE_LINE) {
- need_linez = true;
- layout_out = "layout(line_strip, max_vertices = 8) out;\n";
- body = " mat4 pz, pzs;\n"
- " calc_quadz(0, 1, 2, 3, pz, pzs);\n"
- " emit_line(0, 1, pz[3].x);\n"
- " emit_line(1, 2, pz[3].x);\n"
- " emit_line(2, 3, pzs[3].x);\n"
- " emit_line(3, 0, pzs[3].x);\n";
- } else {
- 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"
+ body = " mat4 pz = calc_triz(0, 1, 2);\n"
" emit_vertex(0, mat4(pz[0], pz[0], pz[0], pz[3]));\n"
" EndPrimitive();\n"
" emit_vertex(1, mat4(pz[1], pz[1], pz[1], pz[3]));\n"
" EndPrimitive();\n"
" emit_vertex(2, mat4(pz[2], pz[2], pz[2], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n"
" EndPrimitive();\n";
}
break;
- case PRIM_TYPE_QUAD_STRIP:
- provoking_index = "3";
- need_quadz = true;
- layout_in = "layout(lines_adjacency) in;\n";
- if (polygon_mode == POLY_MODE_FILL) {
- layout_out = "layout(triangle_strip, max_vertices = 6) out;\n";
- body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n"
- " mat4 pz, pz2;\n"
- " calc_quadz(2, 0, 1, 3, pz, pz2);\n"
- " emit_vertex(0, pz);\n"
- " emit_vertex(1, pz);\n"
- " emit_vertex(2, pz);\n"
- " EndPrimitive();\n"
- " emit_vertex(2, pz2);\n"
- " emit_vertex(1, pz2);\n"
- " emit_vertex(3, pz2);\n"
- " EndPrimitive();\n";
- } else if (polygon_mode == POLY_MODE_LINE) {
- need_linez = true;
- layout_out = "layout(line_strip, max_vertices = 8) out;\n";
- body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n"
- " mat4 pz, pzs;\n"
- " calc_quadz(2, 0, 1, 3, pz, pzs);\n"
- " emit_line(0, 1, pz[3].x);\n"
- " emit_line(1, 3, pzs[3].x);\n"
- " emit_line(3, 2, pzs[3].x);\n"
- " emit_line(2, 0, pz[3].x);\n";
- } else {
- 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"
- " emit_vertex(0, mat4(pz[1], pz[1], pz[1], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(1, mat4(pz[2], pz[2], pz[2], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(2, mat4(pz[0], pz[0], pz[0], pz[3]));\n"
- " EndPrimitive();\n"
- " emit_vertex(3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n"
- " EndPrimitive();\n";
- }
- break;
- case PRIM_TYPE_POLYGON:
- if (polygon_mode == POLY_MODE_FILL) {
- provoking_index = "v[2]";
- need_triz = true;
- layout_in = "layout(triangles) in;\n";
- layout_out = "layout(triangle_strip, max_vertices = 3) out;\n";
- body = " mat4 pz = calc_triz(v[0], v[1], v[2]);\n"
- " emit_vertex(v[0], pz);\n"
- " emit_vertex(v[1], pz);\n"
- " emit_vertex(v[2], pz);\n"
- " EndPrimitive();\n";
- } else if (polygon_mode == POLY_MODE_LINE) {
- provoking_index = "0";
- 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 = " emit_line(0, 1, 0.0);\n";
- } else {
- assert(false);
- return NULL;
- }
- break;
-
default:
assert(false);
return NULL;
}
- /* generate a geometry shader to support deprecated primitive types */
assert(layout_in);
assert(layout_out);
assert(body);
@@ -312,34 +132,6 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts)
pgraph_glsl_get_vtx_header(output, opts.vulkan, state->smooth_shading,
false, false, false);
- char vertex_order_buf[80];
- const char *vertex_order_body = "";
-
- if (need_triz) {
- /* Input triangle absolute vertex order is not guaranteed by OpenGL
- * or Vulkan, only winding order is. Reorder vertices here to first
- * vertex convention which we assumed above when setting
- * provoking_index. This mostly only matters with flat shading, but
- * we reorder always to get consistent results across GPU vendors
- * regarding floating-point rounding when calculating with vtxPos0/1/2.
- */
- mstring_append(output, "ivec3 v;\n");
- if (state->tri_rot0 == state->tri_rot1) {
- snprintf(vertex_order_buf, sizeof(vertex_order_buf), " v = %s;\n",
- get_vertex_order(state->tri_rot0));
- } else {
- snprintf(vertex_order_buf, sizeof(vertex_order_buf),
- " v = (gl_PrimitiveIDIn & 1) == 0 ? %s : %s;\n",
- get_vertex_order(state->tri_rot0),
- get_vertex_order(state->tri_rot1));
- }
- vertex_order_body = vertex_order_buf;
- }
-
- if (state->smooth_shading) {
- provoking_index = "index";
- }
-
const char *point_size_expr =
opts.gles ? "v_vtxPointSize[index]" : "gl_in[index].gl_PointSize";
mstring_append_fmt(
@@ -369,7 +161,7 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts)
provoking_index,
provoking_index);
- if (need_triz || need_quadz) {
+ if (need_triz) {
mstring_append(
output,
// Kahan's algorithm for computing a*b - c*d using FMA for higher
@@ -438,22 +230,12 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts)
"}\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,
"\n"
"void main() {\n"
"%s"
- "%s"
"}\n",
- vertex_order_body, body);
+ body);
return output;
}
diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.h b/hw/xbox/nv2a/pgraph/glsl/geom.h
index 499edc2adf..f590f94c6c 100644
--- a/hw/xbox/nv2a/pgraph/glsl/geom.h
+++ b/hw/xbox/nv2a/pgraph/glsl/geom.h
@@ -30,10 +30,7 @@ typedef struct {
enum ShaderPolygonMode polygon_front_mode;
enum ShaderPolygonMode polygon_back_mode;
bool smooth_shading;
- bool first_vertex_is_provoking;
bool z_perspective;
- short tri_rot0;
- short tri_rot1;
} GeomState;
typedef struct GenGeomGlslOptions {
diff --git a/hw/xbox/nv2a/pgraph/meson.build b/hw/xbox/nv2a/pgraph/meson.build
index fc52f609eb..a01621e297 100644
--- a/hw/xbox/nv2a/pgraph/meson.build
+++ b/hw/xbox/nv2a/pgraph/meson.build
@@ -1,4 +1,5 @@
specific_ss.add(files(
+ 'prim_rewrite.c',
'pgraph.c',
'profile.c',
'rdi.c',
diff --git a/hw/xbox/nv2a/pgraph/prim_rewrite.c b/hw/xbox/nv2a/pgraph/prim_rewrite.c
new file mode 100644
index 0000000000..2d11be8aa4
--- /dev/null
+++ b/hw/xbox/nv2a/pgraph/prim_rewrite.c
@@ -0,0 +1,505 @@
+/*
+ * Geforce NV2A PGRAPH Primitive Index Rewrite
+ *
+ * Rewrites NV2A primitive types to triangle/line/point lists on CPU.
+ * Handles provoking vertex placement for flat shading correctness.
+ *
+ * Copyright (c) 2026 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include "qemu/osdep.h"
+#include "prim_rewrite.h"
+
+void pgraph_prim_rewrite_init(PrimRewriteBuf *buf)
+{
+ buf->data = NULL;
+ buf->capacity = 0;
+}
+
+void pgraph_prim_rewrite_finalize(PrimRewriteBuf *buf)
+{
+ g_free(buf->data);
+ buf->data = NULL;
+ buf->capacity = 0;
+}
+
+static void ensure_capacity(PrimRewriteBuf *buf, unsigned int needed)
+{
+ if (needed <= buf->capacity) {
+ return;
+ }
+ buf->capacity = MAX(needed, buf->capacity * 2);
+ buf->data = g_realloc(buf->data, buf->capacity * sizeof(uint32_t));
+}
+
+enum ShaderPrimitiveMode
+pgraph_prim_rewrite_get_output_mode(enum ShaderPrimitiveMode primitive_mode,
+ enum ShaderPolygonMode polygon_mode)
+{
+ switch (primitive_mode) {
+ case PRIM_TYPE_POINTS:
+ return PRIM_TYPE_POINTS;
+ case PRIM_TYPE_LINES:
+ case PRIM_TYPE_LINE_STRIP:
+ case PRIM_TYPE_LINE_LOOP:
+ return PRIM_TYPE_LINES;
+ case PRIM_TYPE_TRIANGLES:
+ case PRIM_TYPE_TRIANGLE_STRIP:
+ case PRIM_TYPE_TRIANGLE_FAN:
+ return PRIM_TYPE_TRIANGLES;
+ case PRIM_TYPE_QUADS:
+ case PRIM_TYPE_QUAD_STRIP:
+ case PRIM_TYPE_POLYGON:
+ return polygon_mode == POLY_MODE_LINE ? PRIM_TYPE_LINES :
+ PRIM_TYPE_TRIANGLES;
+ default:
+ assert(!"Unexpected primitive mode");
+ return primitive_mode;
+ }
+}
+
+static inline bool needs_rewrite(PrimAssemblyState mode)
+{
+ switch (mode.primitive_mode) {
+ case PRIM_TYPE_POINTS:
+ return false;
+ case PRIM_TYPE_LINES:
+ case PRIM_TYPE_TRIANGLES:
+ return mode.last_provoking && mode.flat_shading;
+ default:
+ return true;
+ }
+}
+
+static unsigned int max_output_indices(enum ShaderPrimitiveMode mode,
+ enum ShaderPolygonMode polygon_mode,
+ unsigned int input_count)
+{
+ switch (mode) {
+ case PRIM_TYPE_LINES:
+ return input_count;
+ case PRIM_TYPE_LINE_STRIP:
+ return (input_count >= 2) ? (input_count - 1) * 2 : 0;
+ case PRIM_TYPE_LINE_LOOP:
+ return (input_count >= 2) ? input_count * 2 : 0;
+ case PRIM_TYPE_TRIANGLES:
+ return input_count;
+ case PRIM_TYPE_TRIANGLE_STRIP:
+ case PRIM_TYPE_TRIANGLE_FAN:
+ return (input_count >= 3) ? (input_count - 2) * 3 : 0;
+ case PRIM_TYPE_POLYGON:
+ if (polygon_mode == POLY_MODE_LINE) {
+ return (input_count >= 2) ? input_count * 2 : 0;
+ }
+ return (input_count >= 3) ? (input_count - 2) * 3 : 0;
+ case PRIM_TYPE_QUADS:
+ if (polygon_mode == POLY_MODE_LINE) {
+ return (input_count / 4) * 8;
+ }
+ return (input_count / 4) * 6;
+ case PRIM_TYPE_QUAD_STRIP:
+ if (polygon_mode == POLY_MODE_LINE) {
+ return (input_count >= 4) ? ((input_count - 2) / 2) * 8 : 0;
+ }
+ return (input_count >= 4) ? ((input_count - 2) / 2) * 6 : 0;
+ default:
+ return 0;
+ }
+}
+
+static inline uint32_t idx_at(const uint32_t *idx, unsigned int i,
+ uint32_t base)
+{
+ return idx ? idx[i] : base + i;
+}
+
+static inline void emit_vertex(PrimRewrite *r, uint32_t v)
+{
+ r->indices[r->num_indices++] = v;
+}
+
+static inline void emit_line(PrimRewrite *r, uint32_t a, uint32_t b)
+{
+ emit_vertex(r, a);
+ emit_vertex(r, b);
+}
+
+/* Place provoking vertex p at index 0. */
+static inline void emit_line_pv(PrimRewrite *r, uint32_t a, uint32_t b,
+ uint32_t p)
+{
+ if (p == a) {
+ emit_line(r, a, b);
+ } else {
+ emit_line(r, b, a);
+ }
+}
+
+static inline void emit_tri(PrimRewrite *r, uint32_t a, uint32_t b, uint32_t c)
+{
+ emit_vertex(r, a);
+ emit_vertex(r, b);
+ emit_vertex(r, c);
+}
+
+/* Rotate provoking vertex p to index 0, preserving winding of (a, b, c). */
+static inline void emit_tri_pv(PrimRewrite *r, uint32_t a, uint32_t b,
+ uint32_t c, uint32_t p)
+{
+ if (p == a) {
+ emit_tri(r, a, b, c);
+ } else if (p == b) {
+ emit_tri(r, b, c, a);
+ } else {
+ emit_tri(r, c, a, b);
+ }
+}
+
+static void rewrite_lines(PrimRewrite *r, const uint32_t *idx, uint32_t base,
+ unsigned int count, bool last_provoking)
+{
+ for (unsigned int i = 0; i + 1 < count; i += 2) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t pv = last_provoking ? v1 : v0;
+
+ emit_line_pv(r, v0, v1, pv);
+ }
+}
+
+static void rewrite_line_strip(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool last_provoking)
+{
+ for (unsigned int i = 0; i + 1 < count; i++) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t pv = last_provoking ? v1 : v0;
+
+ emit_line_pv(r, v0, v1, pv);
+ }
+}
+
+static void rewrite_line_loop(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool last_provoking)
+{
+ if (count < 2) {
+ return;
+ }
+
+ for (unsigned int i = 0; i + 1 < count; i++) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t pv = last_provoking ? v1 : v0;
+
+ emit_line_pv(r, v0, v1, pv);
+ }
+
+ uint32_t v_last = idx_at(idx, count - 1, base);
+ uint32_t v_first = idx_at(idx, 0, base);
+ uint32_t pv = last_provoking ? v_first : v_last;
+
+ emit_line_pv(r, v_last, v_first, pv);
+}
+
+static void rewrite_triangles(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool last_provoking)
+{
+ for (unsigned int i = 0; i + 2 < count; i += 3) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t pv = last_provoking ? v2 : v0;
+
+ emit_tri_pv(r, v0, v1, v2, pv);
+ }
+}
+
+static void rewrite_triangle_strip(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool last_provoking)
+{
+ for (unsigned int i = 0; i + 2 < count; i++) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t pv = last_provoking ? v2 : v0;
+
+ if (i & 1) {
+ emit_tri_pv(r, v1, v0, v2, pv);
+ } else {
+ emit_tri_pv(r, v0, v1, v2, pv);
+ }
+ }
+}
+
+static void rewrite_triangle_fan(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool last_provoking)
+{
+ if (count < 3) {
+ return;
+ }
+
+ uint32_t hub = idx_at(idx, 0, base);
+
+ for (unsigned int i = 0; i + 2 < count; i++) {
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t pv = last_provoking ? v2 : v1;
+
+ emit_tri_pv(r, hub, v1, v2, pv);
+ }
+}
+
+static void rewrite_quads(PrimRewrite *r, const uint32_t *idx, uint32_t base,
+ unsigned int count, bool flat_shading)
+{
+ for (unsigned int i = 0; i + 3 < count; i += 4) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t v3 = idx_at(idx, i + 3, base);
+
+ if (flat_shading) {
+ /* Use v1-v3 diagonal so provoking vertex v3 is in both triangles.
+ * This gives correct flat shading color but slightly different
+ * depth slope vs hardware. */
+ emit_tri(r, v3, v0, v1);
+ emit_tri(r, v3, v1, v2);
+ } else {
+ /* v0-v2 diagonal: matches hardware quad tessellation */
+ emit_tri(r, v0, v1, v2);
+ emit_tri(r, v0, v2, v3);
+ }
+ }
+}
+
+static void rewrite_quads_line(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count)
+{
+ for (unsigned int i = 0; i + 3 < count; i += 4) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t v3 = idx_at(idx, i + 3, base);
+
+ emit_line(r, v0, v1);
+ emit_line(r, v1, v2);
+ emit_line(r, v2, v3);
+ emit_line(r, v3, v0);
+ }
+}
+
+static void rewrite_quad_strip(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count,
+ bool flat_shading)
+{
+ if (count < 4) {
+ return;
+ }
+
+ for (unsigned int i = 0; i + 3 < count; i += 2) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t v3 = idx_at(idx, i + 3, base);
+
+ if (flat_shading) {
+ /* Use v0-v3 diagonal so provoking vertex v3 is in both triangles.
+ * This gives correct flat shading color but slightly different
+ * depth slope vs hardware. */
+ emit_tri(r, v3, v2, v0);
+ emit_tri(r, v3, v0, v1);
+ } else {
+ /* v1-v2 diagonal: matches hardware quad strip tessellation */
+ emit_tri(r, v0, v1, v2);
+ emit_tri(r, v2, v1, v3);
+ }
+ }
+}
+
+static void rewrite_quad_strip_line(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count)
+{
+ if (count < 4) {
+ return;
+ }
+
+ for (unsigned int i = 0; i + 3 < count; i += 2) {
+ uint32_t v0 = idx_at(idx, i, base);
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+ uint32_t v3 = idx_at(idx, i + 3, base);
+
+ emit_line(r, v0, v1);
+ emit_line(r, v1, v3);
+ emit_line(r, v3, v2);
+ emit_line(r, v2, v0);
+ }
+}
+
+static void rewrite_polygon(PrimRewrite *r, const uint32_t *idx, uint32_t base,
+ unsigned int count)
+{
+ if (count < 3) {
+ return;
+ }
+
+ uint32_t hub = idx_at(idx, 0, base);
+
+ for (unsigned int i = 0; i + 2 < count; i++) {
+ uint32_t v1 = idx_at(idx, i + 1, base);
+ uint32_t v2 = idx_at(idx, i + 2, base);
+
+ emit_tri(r, hub, v1, v2);
+ }
+}
+
+static void rewrite_polygon_line(PrimRewrite *r, const uint32_t *idx,
+ uint32_t base, unsigned int count)
+{
+ if (count < 2) {
+ return;
+ }
+
+ for (unsigned int i = 0; i + 1 < count; i++) {
+ emit_line(r, idx_at(idx, i, base), idx_at(idx, i + 1, base));
+ }
+
+ /* Close the loop */
+ emit_line(r, idx_at(idx, count - 1, base), idx_at(idx, 0, base));
+}
+
+static void rewrite_indices(PrimRewrite *r, const PrimAssemblyState *mode,
+ const uint32_t *idx, uint32_t base,
+ unsigned int num_indices)
+{
+ switch (mode->primitive_mode) {
+ case PRIM_TYPE_LINES:
+ rewrite_lines(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_LINE_STRIP:
+ rewrite_line_strip(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_LINE_LOOP:
+ rewrite_line_loop(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_TRIANGLES:
+ rewrite_triangles(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_TRIANGLE_STRIP:
+ rewrite_triangle_strip(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_TRIANGLE_FAN:
+ rewrite_triangle_fan(r, idx, base, num_indices, mode->last_provoking);
+ break;
+ case PRIM_TYPE_QUADS:
+ if (mode->polygon_mode == POLY_MODE_LINE) {
+ rewrite_quads_line(r, idx, base, num_indices);
+ } else {
+ rewrite_quads(r, idx, base, num_indices, mode->flat_shading);
+ }
+ break;
+ case PRIM_TYPE_QUAD_STRIP:
+ if (mode->polygon_mode == POLY_MODE_LINE) {
+ rewrite_quad_strip_line(r, idx, base, num_indices);
+ } else {
+ rewrite_quad_strip(r, idx, base, num_indices, mode->flat_shading);
+ }
+ break;
+ case PRIM_TYPE_POLYGON:
+ if (mode->polygon_mode == POLY_MODE_LINE) {
+ rewrite_polygon_line(r, idx, base, num_indices);
+ } else {
+ rewrite_polygon(r, idx, base, num_indices);
+ }
+ break;
+ default:
+ assert(!"Unexpected primitive mode");
+ break;
+ }
+}
+
+PrimRewrite pgraph_prim_rewrite_ranges(PrimRewriteBuf *buf,
+ PrimAssemblyState mode,
+ const int32_t *starts,
+ const int32_t *counts,
+ unsigned int num_ranges)
+{
+ PrimRewrite result = { 0 };
+
+ assert(mode.polygon_mode != POLY_MODE_POINT ||
+ mode.primitive_mode != PRIM_TYPE_POLYGON);
+
+ if (!needs_rewrite(mode)) {
+ return result;
+ }
+
+ unsigned int total_max_output = 0;
+ for (unsigned int r = 0; r < num_ranges; r++) {
+ total_max_output += max_output_indices(mode.primitive_mode,
+ mode.polygon_mode, counts[r]);
+ }
+
+ if (total_max_output == 0) {
+ return result;
+ }
+
+ ensure_capacity(buf, total_max_output);
+ result.indices = buf->data;
+
+ for (unsigned int r = 0; r < num_ranges; r++) {
+ if (counts[r] == 0) {
+ continue;
+ }
+
+ rewrite_indices(&result, &mode, NULL, starts[r], counts[r]);
+ }
+
+ return result;
+}
+
+PrimRewrite pgraph_prim_rewrite_indexed(PrimRewriteBuf *buf,
+ PrimAssemblyState mode,
+ const uint32_t *input_indices,
+ unsigned int num_input_indices)
+{
+ PrimRewrite result = { 0 };
+
+ assert(mode.polygon_mode != POLY_MODE_POINT ||
+ mode.primitive_mode != PRIM_TYPE_POLYGON);
+
+ if (!needs_rewrite(mode)) {
+ return result;
+ }
+
+ unsigned int max_output = max_output_indices(
+ mode.primitive_mode, mode.polygon_mode, num_input_indices);
+
+ if (max_output == 0) {
+ return result;
+ }
+
+ ensure_capacity(buf, max_output);
+ result.indices = buf->data;
+
+ rewrite_indices(&result, &mode, input_indices, 0, num_input_indices);
+
+ return result;
+}
diff --git a/hw/xbox/nv2a/pgraph/prim_rewrite.h b/hw/xbox/nv2a/pgraph/prim_rewrite.h
new file mode 100644
index 0000000000..53af4981af
--- /dev/null
+++ b/hw/xbox/nv2a/pgraph/prim_rewrite.h
@@ -0,0 +1,72 @@
+/*
+ * Geforce NV2A PGRAPH Primitive Index Rewrite
+ *
+ * Rewrites NV2A primitive types to triangle/line/point lists on CPU.
+ * Handles provoking vertex placement for flat shading correctness.
+ *
+ * Copyright (c) 2026 Matt Borgerson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#ifndef HW_XBOX_NV2A_PGRAPH_PRIM_REWRITE_H
+#define HW_XBOX_NV2A_PGRAPH_PRIM_REWRITE_H
+
+#include
+#include
+#include "vsh_regs.h"
+
+typedef struct PrimRewriteBuf {
+ uint32_t *data;
+ unsigned int capacity; /* in elements */
+} PrimRewriteBuf;
+
+typedef struct PrimRewrite {
+ uint32_t *indices; /* points into PrimRewriteBuf; do NOT free */
+ unsigned int num_indices;
+} PrimRewrite;
+
+typedef struct PrimAssemblyState {
+ enum ShaderPrimitiveMode primitive_mode;
+ enum ShaderPolygonMode polygon_mode;
+ bool last_provoking;
+ bool flat_shading;
+} PrimAssemblyState;
+
+void pgraph_prim_rewrite_init(PrimRewriteBuf *buf);
+void pgraph_prim_rewrite_finalize(PrimRewriteBuf *buf);
+enum ShaderPrimitiveMode
+pgraph_prim_rewrite_get_output_mode(enum ShaderPrimitiveMode primitive_mode,
+ enum ShaderPolygonMode polygon_mode);
+
+PrimRewrite pgraph_prim_rewrite_indexed(PrimRewriteBuf *buf,
+ PrimAssemblyState mode,
+ const uint32_t *input_indices,
+ unsigned int num_input_indices);
+
+PrimRewrite pgraph_prim_rewrite_ranges(PrimRewriteBuf *buf,
+ PrimAssemblyState mode,
+ const int32_t *starts,
+ const int32_t *counts,
+ unsigned int num_ranges);
+
+static inline PrimRewrite pgraph_prim_rewrite_sequential(PrimRewriteBuf *buf,
+ PrimAssemblyState mode,
+ int32_t start,
+ int32_t count)
+{
+ return pgraph_prim_rewrite_ranges(buf, mode, &start, &count, 1);
+}
+
+#endif
diff --git a/hw/xbox/nv2a/pgraph/vk/buffer.c b/hw/xbox/nv2a/pgraph/vk/buffer.c
index d87c875aa4..3993048235 100644
--- a/hw/xbox/nv2a/pgraph/vk/buffer.c
+++ b/hw/xbox/nv2a/pgraph/vk/buffer.c
@@ -153,6 +153,8 @@ void pgraph_vk_init_buffers(NV2AState *d)
r->allocator, r->storage_buffers[buffers_to_map[i]].allocation,
(void **)&r->storage_buffers[buffers_to_map[i]].mapped));
}
+
+ pgraph_prim_rewrite_init(&r->prim_rewrite_buf);
}
void pgraph_vk_finalize_buffers(NV2AState *d)
@@ -167,6 +169,8 @@ void pgraph_vk_finalize_buffers(NV2AState *d)
destroy_buffer(pg, &r->storage_buffers[i]);
}
+ pgraph_prim_rewrite_finalize(&r->prim_rewrite_buf);
+
g_free(r->uploaded_bitmap);
r->uploaded_bitmap = NULL;
}
diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c
index e69eb9a93f..6db8de4c83 100644
--- a/hw/xbox/nv2a/pgraph/vk/draw.c
+++ b/hw/xbox/nv2a/pgraph/vk/draw.c
@@ -20,6 +20,7 @@
#include "qemu/osdep.h"
#include "qemu/fast-hash.h"
#include "renderer.h"
+#include "hw/xbox/nv2a/pgraph/prim_rewrite.h"
#include
void pgraph_vk_draw_begin(NV2AState *d)
@@ -51,38 +52,15 @@ static VkPrimitiveTopology get_primitive_topology(PGRAPHState *pg)
{
PGRAPHVkState *r = pg->vk_renderer_state;
- int polygon_mode = r->shader_binding->state.geom.polygon_front_mode;
int primitive_mode = r->shader_binding->state.geom.primitive_mode;
- // FIXME: Replace with LUT
switch (primitive_mode) {
case PRIM_TYPE_POINTS:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case PRIM_TYPE_LINES:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
- case PRIM_TYPE_LINE_LOOP:
- // FIXME: line strips, except that the first and last vertices are also used as a line
- return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
- case PRIM_TYPE_LINE_STRIP:
- return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case PRIM_TYPE_TRIANGLES:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
- case PRIM_TYPE_TRIANGLE_STRIP:
- return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
- case PRIM_TYPE_TRIANGLE_FAN:
- return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
- case PRIM_TYPE_QUADS:
- return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
- case PRIM_TYPE_QUAD_STRIP:
- return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
- case PRIM_TYPE_POLYGON:
- if (polygon_mode == POLY_MODE_LINE) {
- return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; // FIXME
- } else if (polygon_mode == POLY_MODE_FILL) {
- return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
- }
- assert(!"PRIM_TYPE_POLYGON with invalid polygon_mode");
- return 0;
default:
assert(!"Invalid primitive_mode");
return 0;
@@ -2041,6 +2019,19 @@ void pgraph_vk_flush_draw(NV2AState *d)
r->num_vertex_ram_buffer_syncs = 0;
+ PrimAssemblyState assembly = {
+ .primitive_mode = pg->primitive_mode,
+ .polygon_mode = (enum ShaderPolygonMode)GET_MASK(
+ pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
+ NV_PGRAPH_SETUPRASTER_FRONTFACEMODE),
+ .last_provoking = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3),
+ NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) ==
+ NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_LAST,
+ .flat_shading = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3),
+ NV_PGRAPH_CONTROL_3_SHADEMODE) ==
+ NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT,
+ };
+
if (pg->draw_arrays_length) {
NV2A_VK_DGROUP_BEGIN("Draw Arrays");
nv2a_profile_inc_counter(NV2A_PROF_DRAW_ARRAYS);
@@ -2061,18 +2052,42 @@ void pgraph_vk_flush_draw(NV2AState *d)
sync_vertex_ram_buffer(pg);
VertexBufferRemap remap = remap_unaligned_attributes(pg, max_element);
+ PrimRewrite prim_rw = pgraph_prim_rewrite_ranges(
+ &r->prim_rewrite_buf, assembly,
+ pg->draw_arrays_start, pg->draw_arrays_count,
+ pg->draw_arrays_length);
+
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size =
+ prim_rw.num_indices * sizeof(uint32_t);
+ ensure_buffer_space(pg, BUFFER_INDEX_STAGING, rewrite_size);
+ }
+
begin_pre_draw(pg);
copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element);
pgraph_vk_begin_debug_marker(r, r->command_buffer, RGBA_BLUE,
"Draw Arrays");
begin_draw(pg);
bind_vertex_buffer(pg, remap.attributes, 0);
- for (int i = 0; i < pg->draw_arrays_length; i++) {
- uint32_t start = pg->draw_arrays_start[i],
- count = pg->draw_arrays_count[i];
- NV2A_VK_DPRINTF("- [%d] Start:%d Count:%d", i, start, count);
- vkCmdDraw(r->command_buffer, count, 1, start, 0);
+
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size = prim_rw.num_indices * sizeof(uint32_t);
+ VkDeviceSize buffer_offset = pgraph_vk_update_index_buffer(
+ pg, prim_rw.indices, rewrite_size);
+ vkCmdBindIndexBuffer(r->command_buffer,
+ r->storage_buffers[BUFFER_INDEX].buffer,
+ buffer_offset, VK_INDEX_TYPE_UINT32);
+ vkCmdDrawIndexed(r->command_buffer, prim_rw.num_indices, 1, 0, 0,
+ 0);
+ } else {
+ for (int i = 0; i < pg->draw_arrays_length; i++) {
+ uint32_t start = pg->draw_arrays_start[i],
+ count = pg->draw_arrays_count[i];
+ NV2A_VK_DPRINTF("- [%d] Start:%d Count:%d", i, start, count);
+ vkCmdDraw(r->command_buffer, count, 1, start, 0);
+ }
}
+
end_draw(pg);
pgraph_vk_end_debug_marker(r, r->command_buffer);
@@ -2084,27 +2099,35 @@ void pgraph_vk_flush_draw(NV2AState *d)
nv2a_profile_inc_counter(NV2A_PROF_INLINE_ELEMENTS);
- size_t index_data_size =
- pg->inline_elements_length * sizeof(pg->inline_elements[0]);
+ uint32_t *draw_indices = pg->inline_elements;
+ unsigned int draw_index_count = pg->inline_elements_length;
+ PrimRewrite prim_rw = pgraph_prim_rewrite_indexed(
+ &r->prim_rewrite_buf, assembly, pg->inline_elements,
+ pg->inline_elements_length);
+ if (prim_rw.num_indices > 0) {
+ draw_indices = prim_rw.indices;
+ draw_index_count = prim_rw.num_indices;
+ }
+ size_t index_data_size = draw_index_count * sizeof(uint32_t);
ensure_buffer_space(pg, BUFFER_INDEX_STAGING, index_data_size);
uint32_t min_element = (uint32_t)-1;
uint32_t max_element = 0;
- for (int i = 0; i < pg->inline_elements_length; i++) {
- max_element = MAX(pg->inline_elements[i], max_element);
- min_element = MIN(pg->inline_elements[i], min_element);
+ for (unsigned int i = 0; i < draw_index_count; i++) {
+ max_element = MAX(draw_indices[i], max_element);
+ min_element = MIN(draw_indices[i], min_element);
}
pgraph_vk_bind_vertex_attributes(
d, min_element, max_element, false, 0,
- pg->inline_elements[pg->inline_elements_length - 1]);
+ draw_indices[draw_index_count - 1]);
sync_vertex_ram_buffer(pg);
VertexBufferRemap remap = remap_unaligned_attributes(pg, max_element + 1);
begin_pre_draw(pg);
copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element + 1);
VkDeviceSize buffer_offset = pgraph_vk_update_index_buffer(
- pg, pg->inline_elements, index_data_size);
+ pg, draw_indices, index_data_size);
pgraph_vk_begin_debug_marker(r, r->command_buffer, RGBA_BLUE,
"Inline Elements");
begin_draw(pg);
@@ -2112,8 +2135,7 @@ void pgraph_vk_flush_draw(NV2AState *d)
vkCmdBindIndexBuffer(r->command_buffer,
r->storage_buffers[BUFFER_INDEX].buffer,
buffer_offset, VK_INDEX_TYPE_UINT32);
- vkCmdDrawIndexed(r->command_buffer, pg->inline_elements_length, 1, 0, 0,
- 0);
+ vkCmdDrawIndexed(r->command_buffer, draw_index_count, 1, 0, 0, 0);
end_draw(pg);
pgraph_vk_end_debug_marker(r, r->command_buffer);
@@ -2141,7 +2163,14 @@ void pgraph_vk_flush_draw(NV2AState *d)
attr->inline_buffer_populated = false;
offset += vertex_data_size;
}
+ PrimRewrite prim_rw = pgraph_prim_rewrite_sequential(
+ &r->prim_rewrite_buf, assembly, 0, pg->inline_buffer_length);
+
ensure_buffer_space(pg, BUFFER_VERTEX_INLINE_STAGING, offset);
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size = prim_rw.num_indices * sizeof(uint32_t);
+ ensure_buffer_space(pg, BUFFER_INDEX_STAGING, rewrite_size);
+ }
begin_pre_draw(pg);
VkDeviceSize buffer_offset = pgraph_vk_update_vertex_inline_buffer(
@@ -2150,7 +2179,20 @@ void pgraph_vk_flush_draw(NV2AState *d)
"Inline Buffer");
begin_draw(pg);
bind_inline_vertex_buffer(pg, buffer_offset);
- vkCmdDraw(r->command_buffer, pg->inline_buffer_length, 1, 0, 0);
+
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size = prim_rw.num_indices * sizeof(uint32_t);
+ VkDeviceSize idx_offset = pgraph_vk_update_index_buffer(
+ pg, prim_rw.indices, rewrite_size);
+ vkCmdBindIndexBuffer(r->command_buffer,
+ r->storage_buffers[BUFFER_INDEX].buffer,
+ idx_offset, VK_INDEX_TYPE_UINT32);
+ vkCmdDrawIndexed(r->command_buffer, prim_rw.num_indices, 1, 0, 0,
+ 0);
+ } else {
+ vkCmdDraw(r->command_buffer, pg->inline_buffer_length, 1, 0, 0);
+ }
+
end_draw(pg);
pgraph_vk_end_debug_marker(r, r->command_buffer);
@@ -2186,6 +2228,14 @@ void pgraph_vk_flush_draw(NV2AState *d)
pgraph_vk_bind_vertex_attributes(d, 0, index_count - 1, true,
vertex_size, index_count - 1);
+ PrimRewrite prim_rw = pgraph_prim_rewrite_sequential(
+ &r->prim_rewrite_buf, assembly, 0, index_count);
+
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size = prim_rw.num_indices * sizeof(uint32_t);
+ ensure_buffer_space(pg, BUFFER_INDEX_STAGING, rewrite_size);
+ }
+
begin_pre_draw(pg);
void *inline_array_data = pg->inline_array;
VkDeviceSize buffer_offset = pgraph_vk_update_vertex_inline_buffer(
@@ -2194,7 +2244,20 @@ void pgraph_vk_flush_draw(NV2AState *d)
"Inline Array");
begin_draw(pg);
bind_inline_vertex_buffer(pg, buffer_offset);
- vkCmdDraw(r->command_buffer, index_count, 1, 0, 0);
+
+ if (prim_rw.num_indices > 0) {
+ size_t rewrite_size = prim_rw.num_indices * sizeof(uint32_t);
+ VkDeviceSize idx_offset = pgraph_vk_update_index_buffer(
+ pg, prim_rw.indices, rewrite_size);
+ vkCmdBindIndexBuffer(r->command_buffer,
+ r->storage_buffers[BUFFER_INDEX].buffer,
+ idx_offset, VK_INDEX_TYPE_UINT32);
+ vkCmdDrawIndexed(r->command_buffer, prim_rw.num_indices, 1, 0, 0,
+ 0);
+ } else {
+ vkCmdDraw(r->command_buffer, index_count, 1, 0, 0);
+ }
+
end_draw(pg);
pgraph_vk_end_debug_marker(r, r->command_buffer);
NV2A_VK_DGROUP_END();
diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h
index 839c2a8493..cc93fe6231 100644
--- a/hw/xbox/nv2a/pgraph/vk/renderer.h
+++ b/hw/xbox/nv2a/pgraph/vk/renderer.h
@@ -27,6 +27,7 @@
#include "hw/hw.h"
#include "hw/xbox/nv2a/nv2a_int.h"
#include "hw/xbox/nv2a/nv2a_regs.h"
+#include "hw/xbox/nv2a/pgraph/prim_rewrite.h"
#include "hw/xbox/nv2a/pgraph/surface.h"
#include "hw/xbox/nv2a/pgraph/texture.h"
#include "hw/xbox/nv2a/pgraph/glsl/shaders.h"
@@ -370,6 +371,7 @@ typedef struct PGRAPHVkState {
int descriptor_set_index;
StorageBuffer storage_buffers[BUFFER_COUNT];
+ PrimRewriteBuf prim_rewrite_buf;
MemorySyncRequirement vertex_ram_buffer_syncs[NV2A_VERTEXSHADER_ATTRIBUTES];
size_t num_vertex_ram_buffer_syncs;