gpu: use exclusive PS1 software-raster edges

Adapt PR #14's biased 32.32 triangle edge DDA and half-open bottom/right coverage across the software raster paths, with a focused pure edge-math regression test.

Source: https://github.com/mstan/psxrecomp/pull/14

Source-commit: https://github.com/mstan/psxrecomp/commit/91a654f61dff18806f3eea43f72a30cb34430695

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-14 09:58:31 -07:00
parent 74890cb187
commit 928085d5fb
4 changed files with 101 additions and 47 deletions
@@ -0,0 +1,13 @@
# PR 14 software-raster triangle coverage
Evaluated and adapted on 2026-07-14 from Kareem Olim's
[PR #14](https://github.com/mstan/psxrecomp/pull/14), exact commit
[`91a654f61dff18806f3eea43f72a30cb34430695`](https://github.com/mstan/psxrecomp/commit/91a654f61dff18806f3eea43f72a30cb34430695).
Adapted scope: the software rasterizer's biased 32.32 edge DDA and half-open
bottom/right triangle coverage for flat, Gouraud, textured, and shaded-textured
triangles. The reusable edge math was moved to a small tested internal header.
Excluded: all title references, WebAssembly work, CD/SPU changes, GTE subpixel
and perspective experiments, presentation blending, and every other PR #14
change. This branch changes only software triangle coverage.
+39
View File
@@ -0,0 +1,39 @@
#ifndef PSXRECOMP_GPU_SW_EDGES_H
#define PSXRECOMP_GPU_SW_EDGES_H
#include <stdint.h>
/* PS1 polygon coverage uses a biased 32.32 edge DDA with exclusive bottom
* and right edges. Float-truncated inclusive spans make adjacent polygons
* disagree by a pixel and draw a shared diagonal twice. */
static inline int64_t psx_edge_fp(int x) {
return ((int64_t)x << 32) + ((1LL << 32) - (1 << 11));
}
static inline int64_t psx_edge_step(int dx, int dy) {
int64_t rounding = dx < 0 ? -(int64_t)(dy - 1)
: (dx > 0 ? (int64_t)(dy - 1) : 0);
return (((int64_t)dx << 32) + rounding) / dy;
}
static inline int psx_edge_unfp(int64_t fp) {
return (int)((uint64_t)fp >> 32);
}
/* Vertices must already be sorted by Y. y is in [y0,y2). */
static inline void psx_triangle_edges_at_y(int x0, int y0, int x1, int y1,
int x2, int y2, int y,
int *long_x, int *short_x) {
int64_t long_step = psx_edge_step(x2 - x0, y2 - y0);
*long_x = psx_edge_unfp(psx_edge_fp(x0) + long_step * (y - y0));
if (y < y1) {
int64_t short_step = psx_edge_step(x1 - x0, y1 - y0);
*short_x = psx_edge_unfp(psx_edge_fp(x0) + short_step * (y - y0));
} else {
int64_t short_step = psx_edge_step(x2 - x1, y2 - y1);
*short_x = psx_edge_unfp(psx_edge_fp(x1) + short_step * (y - y1));
}
}
#endif
+21 -47
View File
@@ -28,6 +28,7 @@
*/
#include "gpu_sw_renderer.h"
#include "gpu_sw_edges.h"
#include <string.h>
#include <stdlib.h>
#include <math.h>
@@ -662,33 +663,18 @@ static void raster_flat_triangle(const RTarget *t,
int dy_total = y2 - y0;
if (dy_total == 0) return;
for (int y = y0; y <= y2; y++) {
for (int y = y0; y < y2; y++) {
if (y < t->cy1 || y > t->cy2) continue;
int second_half = (y >= y1);
int seg_height = second_half ? (y2 - y1) : (y1 - y0);
if (seg_height == 0) seg_height = 1;
float alpha = (float)(y - y0) / (float)dy_total;
float beta;
if (second_half)
beta = (float)(y - y1) / (float)seg_height;
else
beta = (float)(y - y0) / (float)seg_height;
int xa = x0 + (int)((float)(x2 - x0) * alpha);
int xb;
if (second_half)
xb = x1 + (int)((float)(x2 - x1) * beta);
else
xb = x0 + (int)((float)(x1 - x0) * beta);
int xa, xb;
psx_triangle_edges_at_y(x0, y0, x1, y1, x2, y2, y, &xa, &xb);
if (xa > xb) { int tt = xa; xa = xb; xb = tt; }
int sx = max_i(xa, t->cx1);
int ex = min_i(xb, t->cx2);
int ex = min_i(xb, t->cx2 + 1);
for (int x = sx; x <= ex; x++) {
for (int x = sx; x < ex; x++) {
put_opaque(t, x, y, color);
}
}
@@ -741,7 +727,7 @@ static void raster_gouraud_triangle(const RTarget *t,
int dy_total = y2 - y0;
if (dy_total == 0) return;
for (int y = y0; y <= y2; y++) {
for (int y = y0; y < y2; y++) {
if (y < t->cy1 || y > t->cy2) continue;
int second_half = (y >= y1);
@@ -756,12 +742,8 @@ static void raster_gouraud_triangle(const RTarget *t,
beta = (float)(y - y0) / (float)seg_height;
/* Interpolate X along edges */
int xa = x0 + (int)((float)(x2 - x0) * alpha);
int xb;
if (second_half)
xb = x1 + (int)((float)(x2 - x1) * beta);
else
xb = x0 + (int)((float)(x1 - x0) * beta);
int xa, xb;
psx_triangle_edges_at_y(x0, y0, x1, y1, x2, y2, y, &xa, &xb);
/* Interpolate colors along edges (in 5-bit space) */
int ra = r0 + (int)((float)(r2 - r0) * alpha);
@@ -788,10 +770,10 @@ static void raster_gouraud_triangle(const RTarget *t,
}
int sx = max_i(xa, t->cx1);
int ex = min_i(xb, t->cx2);
int ex = min_i(xb, t->cx2 + 1);
int span = xb - xa;
for (int x = sx; x <= ex; x++) {
for (int x = sx; x < ex; x++) {
/* Interpolate color across the scanline */
uint16_t color;
if (span > 0) {
@@ -859,7 +841,7 @@ static void raster_textured_triangle(const RTarget *t,
int dy_total = y2 - y0;
if (dy_total == 0) return;
for (int y = y0; y <= y2; y++) {
for (int y = y0; y < y2; y++) {
if (y < t->cy1 || y > t->cy2) continue;
int second_half = (y >= y1);
@@ -873,12 +855,8 @@ static void raster_textured_triangle(const RTarget *t,
else
beta = (float)(y - y0) / (float)seg_height;
int xa = x0 + (int)((float)(x2 - x0) * alpha);
int xb;
if (second_half)
xb = x1 + (int)((float)(x2 - x1) * beta);
else
xb = x0 + (int)((float)(x1 - x0) * beta);
int xa, xb;
psx_triangle_edges_at_y(x0, y0, x1, y1, x2, y2, y, &xa, &xb);
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
@@ -902,9 +880,9 @@ static void raster_textured_triangle(const RTarget *t,
if (span == 0) span = 1;
int sx = max_i(xa, t->cx1);
int ex = min_i(xb, t->cx2);
int ex = min_i(xb, t->cx2 + 1);
for (int x = sx; x <= ex; x++) {
for (int x = sx; x < ex; x++) {
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
@@ -990,7 +968,7 @@ static void raster_shaded_textured_triangle(const RTarget *t,
int dy_total = y2 - y0;
if (dy_total == 0) return;
for (int y = y0; y <= y2; y++) {
for (int y = y0; y < y2; y++) {
if (y < t->cy1 || y > t->cy2) continue;
int second_half = (y >= y1);
@@ -1004,12 +982,8 @@ static void raster_shaded_textured_triangle(const RTarget *t,
else
beta = (float)(y - y0) / (float)seg_height;
int xa = x0 + (int)((float)(x2 - x0) * alpha);
int xb;
if (second_half)
xb = x1 + (int)((float)(x2 - x1) * beta);
else
xb = x0 + (int)((float)(x1 - x0) * beta);
int xa, xb;
psx_triangle_edges_at_y(x0, y0, x1, y1, x2, y2, y, &xa, &xb);
float ua = u0 + (float)(u2 - u0) * alpha;
float va = v0 + (float)(v2 - v0) * alpha;
@@ -1046,9 +1020,9 @@ static void raster_shaded_textured_triangle(const RTarget *t,
if (span == 0) span = 1;
int sx = max_i(xa, t->cx1);
int ex = min_i(xb, t->cx2);
int ex = min_i(xb, t->cx2 + 1);
for (int x = sx; x <= ex; x++) {
for (int x = sx; x < ex; x++) {
float t_val = (float)(x - xa) / (float)span;
float fu = ua + (ub - ua) * t_val;
float fv = va + (vb - va) * t_val;
+28
View File
@@ -0,0 +1,28 @@
#include "../src/gpu_sw_edges.h"
#include <stdio.h>
static int failures;
#define CHECK(condition, message) do { \
if (!(condition)) { fprintf(stderr, "FAIL: %s\n", message); failures++; } \
} while (0)
int main(void) {
int long_x = 0, short_x = 0;
/* A right triangle's final scanline is y2-1; y2 itself is excluded by the
* caller. Pin the DDA values used to produce the half-open X span. */
psx_triangle_edges_at_y(0, 0, 8, 0, 0, 8, 0, &long_x, &short_x);
CHECK(long_x == 0 && short_x == 8, "top scanline spans [0,8)");
psx_triangle_edges_at_y(0, 0, 8, 0, 0, 8, 7, &long_x, &short_x);
CHECK(long_x == 0 && short_x == 1, "last scanline spans one pixel");
CHECK(psx_edge_step(8, 8) == (1LL << 32),
"positive unit slope has exact 32.32 step");
CHECK(psx_edge_step(-8, 8) == -(1LL << 32),
"negative unit slope has exact 32.32 step");
if (failures) return 1;
puts("ALL PASS");
return 0;
}