Merge pull request #38 from mstan/merge/oversized-hardware-primitive-rejection

Reject oversized hardware primitives
This commit is contained in:
Matt Stanley
2026-07-14 18:35:26 -07:00
committed by GitHub
4 changed files with 163 additions and 31 deletions
@@ -0,0 +1,33 @@
# PR #16 GPU primitive-size rejection provenance
This branch isolates Martin Penkava's hardware primitive-size rejection from
[`mstan/psxrecomp` PR #16](https://github.com/mstan/psxrecomp/pull/16), exact
source commit
[`cfc5d0eab94c7ddfd30ed23c4d3beecdc737c835`](https://github.com/mstan/psxrecomp/commit/cfc5d0eab94c7ddfd30ed23c4d3beecdc737c835).
Last evaluated: 2026-07-14
| Item | Value |
| --- | --- |
| Head repository/branch | `shaneomac1337/psxrecomp`, `smackdown2-fixes` |
| Pull request/source commit | `mstan/psxrecomp` PR #16, `cfc5d0eab94c7ddfd30ed23c4d3beecdc737c835` |
| Local base | `7085721afe338a03cb114321a3576cdff420b732` (`origin/master`) |
| Local branch | `fix/pr16-gpu-primitive-size-reject-mpenkava` |
The 1023-pixel horizontal and 511-pixel vertical limits are PS1 hardware rules
documented by No$PSX and implemented by independent emulators including Beetle
and DuckStation. The framework checks parsed coordinates before widescreen and
draw-offset transforms. Every flat, shaded, textured, and shaded-textured
polygon path is covered; quads reject each rendered triangle independently.
Mono/shaded lines and both polyline continuations use the same limits. Textured
commands still latch their texture-page state before a size rejection.
The source commit's game name and observed scenes are validation evidence only;
no title identifier, address, or configuration is included here.
Source authorship is retained with:
```text
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
```
+34
View File
@@ -0,0 +1,34 @@
#ifndef PSX_GPU_PRIMITIVE_REJECT_H
#define PSX_GPU_PRIMITIVE_REJECT_H
#include <stdint.h>
/* PS1 hardware primitive-size rejection. Parsed coordinates are checked before
* widescreen transforms and draw offsets; offsets do not change distances.
* Quads are tested as their two rendered triangles independently. */
static inline int psx_gpu_triangle_oversize(const int32_t* vx,
const int32_t* vy,
int a, int b, int c) {
int32_t minx = vx[a], maxx = vx[a];
if (vx[b] < minx) minx = vx[b];
if (vx[b] > maxx) maxx = vx[b];
if (vx[c] < minx) minx = vx[c];
if (vx[c] > maxx) maxx = vx[c];
if (maxx - minx > 1023) return 1;
int32_t miny = vy[a], maxy = vy[a];
if (vy[b] < miny) miny = vy[b];
if (vy[b] > maxy) maxy = vy[b];
if (vy[c] < miny) miny = vy[c];
if (vy[c] > maxy) maxy = vy[c];
return maxy - miny > 511;
}
static inline int psx_gpu_line_oversize(int32_t x0, int32_t y0,
int32_t x1, int32_t y1) {
int32_t dx = x0 > x1 ? x0 - x1 : x1 - x0;
int32_t dy = y0 > y1 ? y0 - y1 : y1 - y0;
return dx > 1023 || dy > 511;
}
#endif
+60 -31
View File
@@ -11,6 +11,7 @@
*/
#include "gpu.h"
#include "gpu_primitive_reject.h"
#include "gpu_sw_renderer.h"
#include "gpu_render.h"
#include "text_xlate.h"
@@ -2142,6 +2143,7 @@ static void gp0_exec_mono_tri(void) {
for (int i = 0; i < 3; i++) {
parse_vertex(gp0_cmd_buf[1 + i], &vx[i], &vy[i]);
}
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
ws_nw_hud_shift_vertices(vx, 3);
for (int i = 0; i < 3; i++) {
vx[i] += draw_offset_x;
@@ -2158,6 +2160,9 @@ static void gp0_exec_mono_quad(void) {
int32_t vx[4], vy[4];
for (int i = 0; i < 4; i++)
parse_vertex(gp0_cmd_buf[1 + i], &vx[i], &vy[i]);
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
/* Full-screen filters are commonly encoded as an axis-aligned quad. Drawing
* a semi-transparent quad as two independent triangles blends their shared
@@ -2187,8 +2192,10 @@ static void gp0_exec_mono_quad(void) {
vy[i] += draw_offset_y;
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
gr_draw_flat_triangle(vx[2], vy[2], vx[1], vy[1], vx[3], vy[3], color);
if (!rej_a)
gr_draw_flat_triangle(vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], color);
if (!rej_b)
gr_draw_flat_triangle(vx[2], vy[2], vx[1], vy[1], vx[3], vy[3], color);
}
/* Execute shaded triangle (GP0 0x30-0x33) — Gouraud shaded */
@@ -2201,6 +2208,7 @@ static void gp0_exec_shaded_tri(void) {
c[i] = rgb888_to_rgb555(gp0_cmd_buf[i * 2] & 0xFFFFFFu);
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
}
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
ws_nw_hud_shift_vertices(vx, 3);
for (int i = 0; i < 3; i++) {
vx[i] += draw_offset_x;
@@ -2229,6 +2237,9 @@ static void gp0_exec_shaded_quad(void) {
c[i] = rgb888_to_rgb555(gp0_cmd_buf[i * 2] & 0xFFFFFFu);
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
}
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
ws_nw_backdrop_stretch_quad(vx, vy); /* full-frame 2D backdrop stretch (sky gradient; no-op else) */
ws_nw_hud_shift_vertices(vx, 4);
for (int i = 0; i < 4; i++) {
@@ -2244,12 +2255,14 @@ static void gp0_exec_shaded_quad(void) {
}
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
gr_draw_gouraud_triangle(vx[2], vy[2], c[2],
vx[1], vy[1], c[1],
vx[3], vy[3], c[3]);
if (!rej_a)
gr_draw_gouraud_triangle(vx[0], vy[0], c[0],
vx[1], vy[1], c[1],
vx[2], vy[2], c[2]);
if (!rej_b)
gr_draw_gouraud_triangle(vx[2], vy[2], c[2],
vx[1], vy[1], c[1],
vx[3], vy[3], c[3]);
}
/* Helper: build texpage word from GPU state for SW renderer.
@@ -2305,7 +2318,8 @@ static void gp0_exec_textured_tri(void) {
/* Texpage from word 4 bits 16-31 */
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[4] >> 16);
uint16_t tpage = tpage_word & 0x1FF;
set_tpage_from_poly(tpage_word);
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
ws_nw_hud_shift_vertices(vx, 3);
for (int i = 0; i < 3; i++) {
@@ -2341,7 +2355,10 @@ static void gp0_exec_textured_quad(void) {
uint16_t clut_y = (clut >> 6) & 0x1FF;
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[4] >> 16);
uint16_t tpage = tpage_word & 0x1FF;
set_tpage_from_poly(tpage_word);
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
/* Widescreen: tagged billboard quads carry CPU-computed pixel offsets the
* GTE squash never saw — re-squash every X around the prim's anchor. */
@@ -2385,14 +2402,16 @@ static void gp0_exec_textured_quad(void) {
}
}
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
clut_x, clut_y, tpage);
gr_draw_textured_triangle(vx[2], vy[2], u[2], v[2],
vx[1], vy[1], u[1], v[1],
vx[3], vy[3], u[3], v[3],
clut_x, clut_y, tpage);
if (!rej_a)
gr_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
clut_x, clut_y, tpage);
if (!rej_b)
gr_draw_textured_triangle(vx[2], vy[2], u[2], v[2],
vx[1], vy[1], u[1], v[1],
vx[3], vy[3], u[3], v[3],
clut_x, clut_y, tpage);
}
/* Execute shaded textured triangle (GP0 0x34-0x37) */
@@ -2417,7 +2436,8 @@ static void gp0_exec_shaded_textured_tri(void) {
uint16_t clut_y = (clut >> 6) & 0x1FF;
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[5] >> 16);
uint16_t tpage = tpage_word & 0x1FF;
set_tpage_from_poly(tpage_word);
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
if (psx_gpu_triangle_oversize(vx, vy, 0, 1, 2)) return;
ws_nw_hud_shift_vertices(vx, 3);
for (int i = 0; i < 3; i++) {
@@ -2457,7 +2477,10 @@ static void gp0_exec_shaded_textured_quad(void) {
uint16_t clut_y = (clut >> 6) & 0x1FF;
uint16_t tpage_word = (uint16_t)(gp0_cmd_buf[5] >> 16);
uint16_t tpage = tpage_word & 0x1FF;
set_tpage_from_poly(tpage_word);
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
ws_nw_hud_shift_vertices(vx, 4);
for (int i = 0; i < 4; i++) {
@@ -2466,14 +2489,16 @@ static void gp0_exec_shaded_textured_quad(void) {
}
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
clut_x, clut_y, tpage, raw_texture);
gr_draw_shaded_textured_triangle(vx[2], vy[2], u[2], v[2], c[2],
vx[1], vy[1], u[1], v[1], c[1],
vx[3], vy[3], u[3], v[3], c[3],
clut_x, clut_y, tpage, raw_texture);
if (!rej_a)
gr_draw_shaded_textured_triangle(vx[0], vy[0], u[0], v[0], c[0],
vx[1], vy[1], u[1], v[1], c[1],
vx[2], vy[2], u[2], v[2], c[2],
clut_x, clut_y, tpage, raw_texture);
if (!rej_b)
gr_draw_shaded_textured_triangle(vx[2], vy[2], u[2], v[2], c[2],
vx[1], vy[1], u[1], v[1], c[1],
vx[3], vy[3], u[3], v[3], c[3],
clut_x, clut_y, tpage, raw_texture);
}
/* Execute mono line (GP0 0x40-0x47) — Bresenham */
@@ -2483,6 +2508,7 @@ static void gp0_exec_mono_line(void) {
int32_t x0, y0, x1, y1;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
parse_vertex(gp0_cmd_buf[2], &x1, &y1);
if (psx_gpu_line_oversize(x0, y0, x1, y1)) return;
int32_t vx[2] = { x0, x1 };
ws_nw_hud_shift_vertices(vx, 2);
x0 = vx[0]; x1 = vx[1];
@@ -2500,6 +2526,7 @@ static void gp0_exec_shaded_line(void) {
int32_t x0, y0, x1, y1;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
parse_vertex(gp0_cmd_buf[3], &x1, &y1);
if (psx_gpu_line_oversize(x0, y0, x1, y1)) return;
int32_t vx[2] = { x0, x1 };
ws_nw_hud_shift_vertices(vx, 2);
x0 = vx[0]; x1 = vx[1];
@@ -3574,7 +3601,8 @@ static void gpu_write_gp0_body(uint32_t val) {
int32_t x, y;
parse_vertex(val, &x, &y);
x += draw_offset_x; y += draw_offset_y;
if (polyline_has_prev) {
if (polyline_has_prev &&
!psx_gpu_line_oversize(polyline_prev_x, polyline_prev_y, x, y)) {
gr_draw_line(polyline_prev_x, polyline_prev_y, x, y, polyline_color);
}
polyline_prev_x = x; polyline_prev_y = y;
@@ -3616,8 +3644,9 @@ static void gpu_write_gp0_body(uint32_t val) {
int32_t x, y;
parse_vertex(val, &x, &y);
x += draw_offset_x; y += draw_offset_y;
gr_draw_shaded_line(polyline_prev_x, polyline_prev_y, polyline_prev_c,
x, y, polyline_color);
if (!psx_gpu_line_oversize(polyline_prev_x, polyline_prev_y, x, y))
gr_draw_shaded_line(polyline_prev_x, polyline_prev_y,
polyline_prev_c, x, y, polyline_color);
polyline_prev_x = x; polyline_prev_y = y;
polyline_prev_c = polyline_color;
polyline_has_prev = 1;
+36
View File
@@ -0,0 +1,36 @@
#include "gpu_primitive_reject.h"
#include <stdio.h>
static int failures;
static void check(int condition, const char* name) {
if (!condition) {
fprintf(stderr, "FAIL: %s\n", name);
failures++;
}
}
int main(void) {
int32_t x_ok[3] = { -512, 511, 0 };
int32_t y_ok[3] = { -255, 256, 0 };
int32_t x_wide[3] = { -512, 512, 0 };
int32_t y_tall[3] = { -256, 256, 0 };
check(!psx_gpu_triangle_oversize(x_ok, y_ok, 0, 1, 2),
"inclusive 1023x511 boundary is accepted");
check(psx_gpu_triangle_oversize(x_wide, y_ok, 0, 1, 2),
"triangle wider than 1023 is rejected");
check(psx_gpu_triangle_oversize(x_ok, y_tall, 0, 1, 2),
"triangle taller than 511 is rejected");
check(!psx_gpu_line_oversize(-512, -255, 511, 256),
"line boundary is accepted");
check(psx_gpu_line_oversize(-512, 0, 512, 0),
"oversize horizontal line is rejected");
check(psx_gpu_line_oversize(0, -256, 0, 256),
"oversize vertical line is rejected");
if (failures) return 1;
puts("PASS: PS1 primitive size rejection boundaries");
return 0;
}