nv2a: Add support for window clipping

Fixes several games which utilize clipping, such as Halo:CE which
uses clipping in multiplayer to render multiple player screens.

Research and original PoC work done by Jannik Vogel:
- JayFoxRox/xqemu-espes@c05d91b
- JayFoxRox/xqemu-espes@a254830
This commit is contained in:
Matt Borgerson 2018-06-14 15:13:54 -07:00 committed by Jannik Vogel
parent 67500be6e6
commit 2497e2d7c4
6 changed files with 155 additions and 1 deletions

View File

@ -462,6 +462,7 @@
# define NV_PGRAPH_SETUPRASTER_FRONTFACE (1 << 23)
# define NV_PGRAPH_SETUPRASTER_CULLENABLE (1 << 28)
# define NV_PGRAPH_SETUPRASTER_Z_FORMAT (1 << 29)
# define NV_PGRAPH_SETUPRASTER_WINDOWCLIPTYPE (1 << 31)
#define NV_PGRAPH_SHADERCLIPMODE 0x00001994
#define NV_PGRAPH_SHADERCTL 0x00001998
#define NV_PGRAPH_SHADERPROG 0x0000199C
@ -554,6 +555,26 @@
#define NV_PGRAPH_TEXPALETTE1 0x00001A38
#define NV_PGRAPH_TEXPALETTE2 0x00001A3C
#define NV_PGRAPH_TEXPALETTE3 0x00001A40
#define NV_PGRAPH_WINDOWCLIPX0 0x00001A44
# define NV_PGRAPH_WINDOWCLIPX0_XMIN 0x00000FFF
# define NV_PGRAPH_WINDOWCLIPX0_XMAX 0x0FFF0000
#define NV_PGRAPH_WINDOWCLIPX1 0x00001A48
#define NV_PGRAPH_WINDOWCLIPX2 0x00001A4C
#define NV_PGRAPH_WINDOWCLIPX3 0x00001A50
#define NV_PGRAPH_WINDOWCLIPX4 0x00001A54
#define NV_PGRAPH_WINDOWCLIPX5 0x00001A58
#define NV_PGRAPH_WINDOWCLIPX6 0x00001A5C
#define NV_PGRAPH_WINDOWCLIPX7 0x00001A60
#define NV_PGRAPH_WINDOWCLIPY0 0x00001A64
# define NV_PGRAPH_WINDOWCLIPY0_YMIN 0x00000FFF
# define NV_PGRAPH_WINDOWCLIPY0_YMAX 0x0FFF0000
#define NV_PGRAPH_WINDOWCLIPY1 0x00001A68
#define NV_PGRAPH_WINDOWCLIPY2 0x00001A6C
#define NV_PGRAPH_WINDOWCLIPY3 0x00001A70
#define NV_PGRAPH_WINDOWCLIPY4 0x00001A74
#define NV_PGRAPH_WINDOWCLIPY5 0x00001A78
#define NV_PGRAPH_WINDOWCLIPY6 0x00001A7C
#define NV_PGRAPH_WINDOWCLIPY7 0x00001A80
#define NV_PGRAPH_ZSTENCILCLEARVALUE 0x00001A88
#define NV_PGRAPH_ZCLIPMIN 0x00001A90
#define NV_PGRAPH_ZOFFSETBIAS 0x00001AA4
@ -794,6 +815,11 @@
# define NV097_SET_FOG_COLOR_GREEN 0x0000FF00
# define NV097_SET_FOG_COLOR_BLUE 0x00FF0000
# define NV097_SET_FOG_COLOR_ALPHA 0xFF000000
# define NV097_SET_WINDOW_CLIP_TYPE 0x000002B4
# define NV097_SET_WINDOW_CLIP_HORIZONTAL 0x000002C0
# define NV097_SET_WINDOW_CLIP_HORIZONTAL_XMIN 0x00000FFF
# define NV097_SET_WINDOW_CLIP_HORIZONTAL_XMAX 0x0FFF0000
# define NV097_SET_WINDOW_CLIP_VERTICAL 0x000002E0
# define NV097_SET_ALPHA_TEST_ENABLE 0x00000300
# define NV097_SET_BLEND_ENABLE 0x00000304
# define NV097_SET_CULL_FACE_ENABLE 0x00000308

View File

@ -874,6 +874,20 @@ static void pgraph_method(NV2AState *d,
SET_MASK(pg->regs[NV_PGRAPH_FOGCOLOR], NV_PGRAPH_FOGCOLOR_ALPHA, alpha);
break;
}
case NV097_SET_WINDOW_CLIP_TYPE:
SET_MASK(pg->regs[NV_PGRAPH_SETUPRASTER],
NV_PGRAPH_SETUPRASTER_WINDOWCLIPTYPE, parameter);
break;
case NV097_SET_WINDOW_CLIP_HORIZONTAL ...
NV097_SET_WINDOW_CLIP_HORIZONTAL + 0x1c:
slot = (method - NV097_SET_WINDOW_CLIP_HORIZONTAL) / 4;
pg->regs[NV_PGRAPH_WINDOWCLIPX0 + slot * 4] = parameter;
break;
case NV097_SET_WINDOW_CLIP_VERTICAL ...
NV097_SET_WINDOW_CLIP_VERTICAL + 0x1c:
slot = (method - NV097_SET_WINDOW_CLIP_VERTICAL) / 4;
pg->regs[NV_PGRAPH_WINDOWCLIPY0 + slot * 4] = parameter;
break;
case NV097_SET_ALPHA_TEST_ENABLE:
SET_MASK(pg->regs[NV_PGRAPH_CONTROL_0],
NV_PGRAPH_CONTROL_0_ALPHATESTENABLE, parameter);
@ -2409,6 +2423,8 @@ static void pgraph_method(NV2AState *d,
/* FIXME: Should this really be inverted instead of ymin? */
glScissor(scissor_x, scissor_y, scissor_width, scissor_height);
/* FIXME: Respect window clip?!?! */
NV2A_DPRINTF("------------------CLEAR 0x%x %d,%d - %d,%d %x---------------\n",
parameter, xmin, ymin, xmax, ymax, d->pgraph.regs[NV_PGRAPH_COLORCLEARVALUE]);
@ -2917,7 +2933,6 @@ static void pgraph_shader_update_constants(PGRAPHState *pg,
if (binding->clip_range_loc != -1) {
glUniform2f(binding->clip_range_loc, zclip_min, zclip_max);
}
}
static void pgraph_bind_shaders(PGRAPHState *pg)
@ -2942,6 +2957,8 @@ static void pgraph_bind_shaders(PGRAPHState *pg)
ShaderState state = {
.psh = (PshState){
/* register combier stuff */
.window_clip_exclusive = pg->regs[NV_PGRAPH_SETUPRASTER]
& NV_PGRAPH_SETUPRASTER_WINDOWCLIPTYPE,
.combiner_control = pg->regs[NV_PGRAPH_COMBINECTL],
.shader_stage_program = pg->regs[NV_PGRAPH_SHADERPROG],
.other_stage_input = pg->regs[NV_PGRAPH_SHADERCTL],
@ -3037,6 +3054,45 @@ static void pgraph_bind_shaders(PGRAPHState *pg)
}
}
/* Window clip
*
* Optimization note: very quickly check to ignore any repeated or zero-size
* clipping regions. Note that if region number 7 is valid, but the rest are
* not, we will still add all of them. Clip regions seem to be typically
* front-loaded (meaning the first one or two regions are populated, and the
* following are zeroed-out), so let's avoid adding any more complicated
* masking or copying logic here for now unless we discover a valid case.
*/
assert(!state.psh.window_clip_exclusive); /* FIXME: Untested */
state.psh.window_clip_count = 0;
uint32_t last_x = 0, last_y = 0;
for (i = 0; i < 8; i++) {
const uint32_t x = pg->regs[NV_PGRAPH_WINDOWCLIPX0 + i * 4];
const uint32_t y = pg->regs[NV_PGRAPH_WINDOWCLIPY0 + i * 4];
const uint32_t x_min = GET_MASK(x, NV_PGRAPH_WINDOWCLIPX0_XMIN);
const uint32_t x_max = GET_MASK(x, NV_PGRAPH_WINDOWCLIPX0_XMAX);
const uint32_t y_min = GET_MASK(y, NV_PGRAPH_WINDOWCLIPY0_YMIN);
const uint32_t y_max = GET_MASK(y, NV_PGRAPH_WINDOWCLIPY0_YMAX);
/* Check for zero width or height clipping region */
if ((x_min == x_max) || (y_min == y_max)) {
continue;
}
/* Check for in-order duplicate regions */
if ((x == last_x) && (y == last_y)) {
continue;
}
NV2A_DPRINTF("Clipping Region %d: min=(%d, %d) max=(%d, %d)\n",
i, x_min, y_min, x_max, y_max);
state.psh.window_clip_count = i + 1;
last_x = x;
last_y = y;
}
for (i = 0; i < 8; i++) {
state.psh.rgb_inputs[i] = pg->regs[NV_PGRAPH_COMBINECOLORI0 + i * 4];
state.psh.rgb_outputs[i] = pg->regs[NV_PGRAPH_COMBINECOLORO0 + i * 4];
@ -3083,6 +3139,33 @@ static void pgraph_bind_shaders(PGRAPHState *pg)
glUseProgram(pg->shader_binding->gl_program);
/* Clipping regions */
for (i = 0; i < state.psh.window_clip_count; i++) {
if (pg->shader_binding->clip_region_loc[i] == -1) {
continue;
}
uint32_t x = pg->regs[NV_PGRAPH_WINDOWCLIPX0 + i * 4];
GLuint x_min = GET_MASK(x, NV_PGRAPH_WINDOWCLIPX0_XMIN);
GLuint x_max = GET_MASK(x, NV_PGRAPH_WINDOWCLIPX0_XMAX);
/* Adjust y-coordinates for the OpenGL viewport: translate coordinates
* to have the origin at the bottom-left of the surface (as opposed to
* top-left), and flip y-min and y-max accordingly.
*/
uint32_t y = pg->regs[NV_PGRAPH_WINDOWCLIPY0 + i * 4];
GLuint y_min = (pg->surface_shape.clip_height - 1) -
GET_MASK(y, NV_PGRAPH_WINDOWCLIPY0_YMAX);
GLuint y_max = (pg->surface_shape.clip_height - 1) -
GET_MASK(y, NV_PGRAPH_WINDOWCLIPY0_YMIN);
pgraph_apply_anti_aliasing_factor(pg, &x_min, &y_min);
pgraph_apply_anti_aliasing_factor(pg, &x_max, &y_max);
glUniform4i(pg->shader_binding->clip_region_loc[i],
x_min, y_min, x_max, y_max);
}
pgraph_shader_update_constants(pg, pg->shader_binding, binding_changed,
vertex_program, fixed_function);

View File

@ -539,6 +539,42 @@ static QString* psh_convert(struct PixelShader *ps)
qstring_append(preflight, "\n");
qstring_append(preflight, "uniform vec4 fogColor;\n");
/* Window Clipping */
QString *clip = qstring_new();
if (ps->state.window_clip_count != 0) {
qstring_append_fmt(preflight, "uniform ivec4 clipRegion[%d];\n",
ps->state.window_clip_count);
qstring_append_fmt(clip, "/* Window-clip (%s) */\n",
ps->state.window_clip_exclusive ?
"Exclusive" : "Inclusive");
if (!ps->state.window_clip_exclusive) {
qstring_append(clip, "bool clipContained = false;\n");
}
qstring_append_fmt(clip, "for (int i = 0; i < %d; i++) {\n",
ps->state.window_clip_count);
qstring_append(clip, " bvec4 clipTest = bvec4(lessThan(gl_FragCoord.xy, clipRegion[i].xy),\n"
" greaterThan(gl_FragCoord.xy, clipRegion[i].zw));\n"
" if (!any(clipTest)) {\n");
if (ps->state.window_clip_exclusive) {
/* Pixel in clip region = exclude by discarding */
qstring_append(clip, " discard;\n");
assert(false); /* Untested */
} else {
/* Pixel in clip region = mark pixel as contained and leave */
qstring_append(clip, " clipContained = true;\n"
" break;\n");
}
qstring_append(clip, " }\n"
"}\n");
/* Check for inclusive window clip */
if (!ps->state.window_clip_exclusive) {
qstring_append(clip, "if (!clipContained) { discard; }\n");
}
} else if (ps->state.window_clip_exclusive) {
/* Clip everything */
qstring_append(clip, "discard;\n");
}
/* calculate perspective-correct inputs */
QString *vars = qstring_new();
qstring_append(vars, "vec4 pD0 = vtx.D0 / vtx.inv_w;\n");
@ -746,6 +782,7 @@ static QString* psh_convert(struct PixelShader *ps)
qstring_append(final, "#version 330\n\n");
qstring_append(final, qstring_get_str(preflight));
qstring_append(final, "void main() {\n");
qstring_append(final, qstring_get_str(clip));
qstring_append(final, qstring_get_str(vars));
qstring_append(final, qstring_get_str(ps->code));
qstring_append(final, "fragColor = r0;\n");

View File

@ -52,6 +52,9 @@ typedef struct PshState {
bool alpha_test;
enum PshAlphaFunc alpha_func;
bool window_clip_exclusive;
unsigned int window_clip_count;
} PshState;
QString *psh_translate(const PshState state);

View File

@ -985,6 +985,10 @@ ShaderBinding* generate_shaders(const ShaderState state)
snprintf(tmp, sizeof(tmp), "lightLocalAttenuation%d", i);
ret->light_local_attenuation_loc[i] = glGetUniformLocation(program, tmp);
}
for (i = 0; i < 8; i++) {
snprintf(tmp, sizeof(tmp), "clipRegion[%d]", i);
ret->clip_region_loc[i] = glGetUniformLocation(program, tmp);
}
return ret;
}

View File

@ -108,6 +108,7 @@ typedef struct ShaderBinding {
GLint light_local_position_loc[NV2A_MAX_LIGHTS];
GLint light_local_attenuation_loc[NV2A_MAX_LIGHTS];
GLint clip_region_loc[8];
} ShaderBinding;
ShaderBinding* generate_shaders(const ShaderState state);