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;