Skip to content

Commit 0f611d9

Browse files
author
coldhex
committed
nv2a: Vertex reordering for flat shading in geometry shader
Test OpenGL/Vulkan geometry shader triangle, strip and fan vertex ordering during backend initialization. OpenGL/Vulkan does not guarantee absolute vertex order for geometry shader input triangles. The test results are used to reorder input triangle vertices into the first vertex convention order so that correct provoking vertex can be chosen for flat shading. Also, this removes use of the Vulkan provoking vertex extension. The default first vertex convention is now used when emitting line strips in geometry shader. (It would of course be possible to always emit only separate line segments and then the convention wouldn't matter at all.)
1 parent e966636 commit 0f611d9

File tree

14 files changed

+1054
-72
lines changed

14 files changed

+1054
-72
lines changed

hw/xbox/nv2a/pgraph/gl/draw.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ void pgraph_gl_draw_begin(NV2AState *d)
227227

228228
glEnable(GL_DEPTH_CLAMP);
229229

230+
/* Set first vertex convention to match Vulkan default. This is needed
231+
* because geometry shader outputs line strips with data for fragment
232+
* shader.
233+
*/
234+
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
235+
230236
if (stencil_test) {
231237
glEnable(GL_STENCIL_TEST);
232238

hw/xbox/nv2a/pgraph/gl/gpuprops.c

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/*
2+
* Geforce NV2A PGRAPH OpenGL Renderer
3+
*
4+
* Copyright (c) 2012 espes
5+
* Copyright (c) 2015 Jannik Vogel
6+
* Copyright (c) 2018-2025 Matt Borgerson
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
22+
#include "debug.h"
23+
#include "renderer.h"
24+
25+
static GPUProperties pgraph_gl_gpu_properties;
26+
27+
static const char *vertex_shader_source =
28+
"#version 400\n"
29+
"out vec3 v_fragColor;\n"
30+
"\n"
31+
"vec2 positions[11] = vec2[](\n"
32+
" vec2(-0.5, -0.75),\n"
33+
" vec2(-0.25, -0.25),\n"
34+
" vec2(-0.75, -0.25),\n"
35+
" vec2(0.25, -0.25),\n"
36+
" vec2(0.25, -0.75),\n"
37+
" vec2(0.75, -0.25),\n"
38+
" vec2(0.75, -0.75),\n"
39+
" vec2(-0.75, 0.75),\n"
40+
" vec2(-0.75, 0.25),\n"
41+
" vec2(-0.25, 0.25),\n"
42+
" vec2(-0.25, 0.75)\n"
43+
");\n"
44+
"\n"
45+
"vec3 colors[11] = vec3[](\n"
46+
" vec3(0.0, 0.0, 1.0),\n"
47+
" vec3(0.0, 1.0, 0.0),\n"
48+
" vec3(0.0, 1.0, 1.0),\n"
49+
" vec3(0.0, 0.0, 1.0),\n"
50+
" vec3(0.0, 1.0, 0.0),\n"
51+
" vec3(0.0, 1.0, 1.0),\n"
52+
" vec3(1.0, 0.0, 0.0),\n"
53+
" vec3(0.0, 0.0, 1.0),\n"
54+
" vec3(0.0, 1.0, 0.0),\n"
55+
" vec3(0.0, 1.0, 1.0),\n"
56+
" vec3(1.0, 0.0, 0.0)\n"
57+
");\n"
58+
"\n"
59+
"void main() {\n"
60+
" gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n"
61+
" v_fragColor = colors[gl_VertexID];\n"
62+
"}\n";
63+
64+
static const char *geometry_shader_source =
65+
"#version 400\n"
66+
"layout(triangles) in;\n"
67+
"layout(triangle_strip, max_vertices = 3) out;\n"
68+
"out vec3 fragColor;\n"
69+
"in vec3 v_fragColor[];\n"
70+
"\n"
71+
"void emit_vertex(int index) {\n"
72+
" gl_Position = gl_in[index].gl_Position;\n"
73+
" fragColor = v_fragColor[0];\n"
74+
" EmitVertex();\n"
75+
"}\n"
76+
"\n"
77+
"void main() {\n"
78+
" emit_vertex(0);\n"
79+
" emit_vertex(1);\n"
80+
" emit_vertex(2);\n"
81+
" EndPrimitive();\n"
82+
"}\n";
83+
84+
static const char *fragment_shader_source =
85+
"#version 400\n"
86+
"out vec4 outColor;\n"
87+
"in vec3 fragColor;\n"
88+
"\n"
89+
"void main() {\n"
90+
" outColor = vec4(fragColor, 1.0);\n"
91+
"}\n";
92+
93+
static GLuint compile_shader(GLenum type, const char *source)
94+
{
95+
GLuint shader = glCreateShader(type);
96+
glShaderSource(shader, 1, &source, NULL);
97+
glCompileShader(shader);
98+
99+
GLint success;
100+
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
101+
if (!success) {
102+
char log[512];
103+
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
104+
fprintf(stderr, "ERROR::SHADER_COMPILATION_ERROR of type: %d\n%s\n",
105+
type, log);
106+
assert(false);
107+
}
108+
109+
return shader;
110+
}
111+
112+
static GLuint create_program(const char *vert_source, const char *geom_source,
113+
const char *frag_source)
114+
{
115+
GLuint vert_shader = compile_shader(GL_VERTEX_SHADER, vert_source);
116+
GLuint geom_shader = compile_shader(GL_GEOMETRY_SHADER, geom_source);
117+
GLuint frag_shader = compile_shader(GL_FRAGMENT_SHADER, frag_source);
118+
119+
GLuint shader_prog = glCreateProgram();
120+
glAttachShader(shader_prog, vert_shader);
121+
glAttachShader(shader_prog, geom_shader);
122+
glAttachShader(shader_prog, frag_shader);
123+
glLinkProgram(shader_prog);
124+
125+
GLint success;
126+
glGetProgramiv(shader_prog, GL_LINK_STATUS, &success);
127+
if (!success) {
128+
char log[512];
129+
glGetProgramInfoLog(shader_prog, sizeof(log), NULL, log);
130+
fprintf(stderr, "ERROR::PROGRAM_LINKING_ERROR\n%s\n", log);
131+
assert(false);
132+
}
133+
134+
glDeleteShader(vert_shader);
135+
glDeleteShader(geom_shader);
136+
glDeleteShader(frag_shader);
137+
138+
return shader_prog;
139+
}
140+
141+
static uint8_t *render_geom_shader_triangles(int width, int height)
142+
{
143+
// Create the framebuffer and renderbuffer for it
144+
GLuint fbo, rbo;
145+
glGenFramebuffers(1, &fbo);
146+
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
147+
glGenRenderbuffers(1, &rbo);
148+
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
149+
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
150+
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
151+
GL_RENDERBUFFER, rbo);
152+
153+
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
154+
155+
GLuint shader_prog = create_program(
156+
vertex_shader_source, geometry_shader_source, fragment_shader_source);
157+
158+
glUseProgram(shader_prog);
159+
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
160+
glClear(GL_COLOR_BUFFER_BIT);
161+
162+
glColorMask(true, true, true, true);
163+
glDisable(GL_CULL_FACE);
164+
glDisable(GL_DEPTH_TEST);
165+
glDisable(GL_STENCIL_TEST);
166+
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
167+
glViewport(0, 0, width, height);
168+
169+
GLuint vao;
170+
glGenVertexArrays(1, &vao);
171+
glBindVertexArray(vao);
172+
glDrawArrays(GL_TRIANGLES, 0, 3);
173+
glDrawArrays(GL_TRIANGLE_STRIP, 3, 4);
174+
glDrawArrays(GL_TRIANGLE_FAN, 7, 4);
175+
176+
glBindVertexArray(0);
177+
glDeleteVertexArrays(1, &vao);
178+
glUseProgram(0);
179+
glDeleteProgram(shader_prog);
180+
181+
void *pixels = g_malloc(width * height * 4);
182+
assert(pixels != NULL);
183+
glReadBuffer(GL_COLOR_ATTACHMENT0);
184+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
185+
186+
glBindFramebuffer(GL_FRAMEBUFFER, 0);
187+
glDeleteFramebuffers(1, &fbo);
188+
glBindRenderbuffer(GL_RENDERBUFFER, 0);
189+
glDeleteRenderbuffers(1, &rbo);
190+
191+
return (uint8_t *)pixels;
192+
}
193+
194+
static bool colors_match(int r1, int g1, int b1, int r2, int g2, int b2)
195+
{
196+
int dr = r1 - r2;
197+
int dg = g1 - g2;
198+
int db = b1 - b2;
199+
200+
return (dr * dr + dg * dg + db * db) <= 16;
201+
}
202+
203+
static int get_color_index(uint8_t *pixel)
204+
{
205+
int r = pixel[0];
206+
int g = pixel[1];
207+
int b = pixel[2];
208+
209+
if (colors_match(r, g, b, 0, 0, 255)) {
210+
return 0;
211+
} else if (colors_match(r, g, b, 0, 255, 0)) {
212+
return 1;
213+
} else if (colors_match(r, g, b, 0, 255, 255)) {
214+
return 2;
215+
} else if (colors_match(r, g, b, 255, 0, 0)) {
216+
return 3;
217+
} else {
218+
return -1;
219+
}
220+
}
221+
222+
static int calc_offset_from_ndc(float x, float y, int width, int height)
223+
{
224+
int x0 = (int)((x + 1.0f) * width * 0.5f);
225+
int y0 = (int)((y + 1.0f) * height * 0.5f);
226+
227+
x0 = MAX(x0, 0);
228+
y0 = MAX(y0, 0);
229+
x0 = MIN(x0, width - 1);
230+
y0 = MIN(y0, height - 1);
231+
232+
return y0 * width + x0;
233+
}
234+
235+
static void determine_triangle_winding_order(uint8_t *pixels, int width,
236+
int height, GPUProperties *props)
237+
{
238+
uint8_t *tri_pix =
239+
pixels + calc_offset_from_ndc(-0.5f, -0.5f, width, height) * 4;
240+
uint8_t *strip0_pix =
241+
pixels + calc_offset_from_ndc(0.417f, -0.417f, width, height) * 4;
242+
uint8_t *strip1_pix =
243+
pixels + calc_offset_from_ndc(0.583f, -0.583f, width, height) * 4;
244+
uint8_t *fan_pix =
245+
pixels + calc_offset_from_ndc(-0.583f, 0.417f, width, height) * 4;
246+
uint8_t *fan2_pix =
247+
pixels + calc_offset_from_ndc(-0.417f, 0.583f, width, height) * 4;
248+
249+
int tri_rot = get_color_index(tri_pix);
250+
if (tri_rot < 0 || tri_rot > 2) {
251+
fprintf(stderr,
252+
"Could not determine triangle rotation, got color: R=%d, G=%d, "
253+
"B=%d\n",
254+
tri_pix[0], tri_pix[1], tri_pix[2]);
255+
tri_rot = 0;
256+
}
257+
props->geom_shader_winding.tri = tri_rot;
258+
259+
int strip0_rot = get_color_index(strip0_pix);
260+
if (strip0_rot < 0 || strip0_rot > 2) {
261+
fprintf(stderr,
262+
"Could not determine triangle strip0 rotation, got color: "
263+
"R=%d, G=%d, B=%d\n",
264+
strip0_pix[0], strip0_pix[1], strip0_pix[2]);
265+
strip0_rot = 0;
266+
}
267+
int strip1_rot = get_color_index(strip1_pix) - 1;
268+
if (strip1_rot < 0 || strip1_rot > 2) {
269+
fprintf(stderr,
270+
"Could not determine triangle strip1 rotation, got color: "
271+
"R=%d, G=%d, B=%d\n",
272+
strip1_pix[0], strip1_pix[1], strip1_pix[2]);
273+
strip1_rot = 0;
274+
}
275+
props->geom_shader_winding.tri_strip0 = strip0_rot;
276+
props->geom_shader_winding.tri_strip1 = (3 - strip1_rot) % 3;
277+
278+
int fan_rot = get_color_index(fan_pix);
279+
int fan2_rot = get_color_index(fan2_pix);
280+
if (fan2_rot == 0) {
281+
fan2_rot = 1;
282+
}
283+
fan2_rot--;
284+
if (fan_rot != fan2_rot) {
285+
fprintf(stderr,
286+
"Unexpected inconsistency in triangle fan winding, got colors: "
287+
"R=%d, G=%d, B=%d and R=%d, G=%d, B=%d\n",
288+
fan_pix[0], fan_pix[1], fan_pix[2], fan2_pix[0], fan2_pix[1],
289+
fan2_pix[2]);
290+
fan_rot = 1;
291+
}
292+
if (fan_rot < 0 || fan_rot > 2) {
293+
fprintf(stderr,
294+
"Could not determine triangle fan rotation, got color: R=%d, "
295+
"G=%d, B=%d\n",
296+
fan_pix[0], fan_pix[1], fan_pix[2]);
297+
fan_rot = 1;
298+
}
299+
props->geom_shader_winding.tri_fan = (fan_rot + 2) % 3;
300+
}
301+
302+
void pgraph_gl_determine_gpu_properties(NV2AState *d)
303+
{
304+
const int width = 640;
305+
const int height = 480;
306+
307+
uint8_t *pixels = render_geom_shader_triangles(width, height);
308+
determine_triangle_winding_order(pixels, width, height,
309+
&pgraph_gl_gpu_properties);
310+
g_free(pixels);
311+
312+
fprintf(stderr, "GL geometry shader winding: %d, %d, %d, %d\n",
313+
pgraph_gl_gpu_properties.geom_shader_winding.tri,
314+
pgraph_gl_gpu_properties.geom_shader_winding.tri_strip0,
315+
pgraph_gl_gpu_properties.geom_shader_winding.tri_strip1,
316+
pgraph_gl_gpu_properties.geom_shader_winding.tri_fan);
317+
}
318+
319+
GPUProperties *pgraph_gl_get_gpu_properties(void)
320+
{
321+
return &pgraph_gl_gpu_properties;
322+
}

hw/xbox/nv2a/pgraph/gl/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ specific_ss.add([sdl, gloffscreen, files(
33
'debug.c',
44
'display.c',
55
'draw.c',
6+
'gpuprops.c',
67
'renderer.c',
78
'reports.c',
89
'shaders.c',

hw/xbox/nv2a/pgraph/gl/renderer.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ static void pgraph_gl_init(NV2AState *d, Error **errp)
5555
glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, r->supported_smooth_line_width_range);
5656
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, r->supported_aliased_line_width_range);
5757

58+
pgraph_gl_determine_gpu_properties(d);
5859
pgraph_gl_init_surfaces(pg);
5960
pgraph_gl_init_reports(d);
6061
pgraph_gl_init_textures(d);
@@ -195,6 +196,7 @@ static PGRAPHRenderer pgraph_gl_renderer = {
195196
.set_surface_scale_factor = pgraph_gl_set_surface_scale_factor,
196197
.get_surface_scale_factor = pgraph_gl_get_surface_scale_factor,
197198
.get_framebuffer_surface = pgraph_gl_get_framebuffer_surface,
199+
.get_gpu_properties = pgraph_gl_get_gpu_properties,
198200
}
199201
};
200202

hw/xbox/nv2a/pgraph/gl/renderer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,7 @@ void pgraph_gl_shader_write_cache_reload_list(PGRAPHState *pg);
286286
void pgraph_gl_set_surface_scale_factor(NV2AState *d, unsigned int scale);
287287
unsigned int pgraph_gl_get_surface_scale_factor(NV2AState *d);
288288
int pgraph_gl_get_framebuffer_surface(NV2AState *d);
289+
void pgraph_gl_determine_gpu_properties(NV2AState *d);
290+
GPUProperties *pgraph_gl_get_gpu_properties(void);
289291

290292
#endif

0 commit comments

Comments
 (0)