nv2a: Move primitive rewriting to client

This commit is contained in:
Matt Borgerson
2026-02-12 05:20:58 -05:00
committed by izzy2lost
parent eadaffbd50
commit 2a0b800dcd
12 changed files with 820 additions and 330 deletions
+104 -33
View File
@@ -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");
+3
View File
@@ -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;
+4 -20
View File
@@ -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);
+6
View File
@@ -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;
+17 -235
View File
@@ -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;
}
-3
View File
@@ -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 {
+1
View File
@@ -1,4 +1,5 @@
specific_ss.add(files(
'prim_rewrite.c',
'pgraph.c',
'profile.c',
'rdi.c',
File diff suppressed because it is too large Load Diff
+72
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef HW_XBOX_NV2A_PGRAPH_PRIM_REWRITE_H
#define HW_XBOX_NV2A_PGRAPH_PRIM_REWRITE_H
#include <stdbool.h>
#include <stdint.h>
#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
+4
View File
@@ -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;
}
+102 -39
View File
@@ -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 <math.h>
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();
+2
View File
@@ -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;