Files
psxrecomp/runtime/src/gpu_render.c
T
tetrisgm ab04da86e3 pgxp: depth from the absolute GTE screen Z, not the normalised perspective weight
PGXP depth shattered the scene, and the reason was a category error rather than
a tuning problem. It derived depth from a_q, which is q/qmax -- a perspective
weight normalised WITHIN one triangle. Every triangle therefore got its own
private 0..1 range, so the value said nothing about where one triangle sat
relative to another, which is the only thing a depth buffer is for.

The real depth was already in hand and being thrown away. prepare_texture_triangle
reads a 16-bit GTE screen Z per vertex via gte_precision_load_word, then
normalises it away on the very next line because UV correction only needs
relative weights. That Z is a single global scale, so it is exactly what a depth
buffer wants.

Adds gr_set_depth_triangle alongside gr_set_perspective_triangle rather than
widening it: the two carry different quantities for different purposes, and a
null-safe hook leaves the software backend untouched. The GL backend takes it as
its own vertex attribute (a_z, location 10) and maps it straight to NDC.

Verified in 3D attract-mode gameplay: with pgxp_depth on the scene renders
intact -- track, perspective-correct texturing and effects all correct -- where
before it shattered. On the 2D title screen the on/off frames are bit-identical,
because no primitive there carries GTE provenance, so a_z is 0 and depth stays
off. Still default-off in config; this makes the feature viable, it does not
turn it on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:49:15 -07:00

283 lines
14 KiB
C

/* gpu_render.c — renderer facade / backend dispatch.
*
* gpu.c and main.cpp call the gr_* functions; this file forwards each to the
* selected backend's vtable. The software rasterizer is the default and the
* fallback. The OpenGL backend (gpu_gl_renderer.c) is selected via
* gr_set_backend(GR_BACKEND_OPENGL) and supplies its table through
* gl_backend_get(); if that returns NULL (GL unavailable / init failed) we
* stay on software so a misconfigured [video] renderer never bricks boot.
*
* The software backend table points straight at the existing sw_* functions,
* so the software path is byte-for-byte unchanged. */
#include "gpu_render.h"
#include "gpu_sw_renderer.h"
#include "gpu_uv.h"
#include "tex_pack.h"
#include <stdio.h>
static const GpuRenderBackend SW_BACKEND = {
.name = "software",
.init = sw_renderer_init,
.set_scale = sw_renderer_set_scale,
.scale = sw_renderer_scale,
.set_texture_filter = sw_set_texture_filter,
.texture_filter = sw_texture_filter,
.set_semi_transparency = sw_set_semi_transparency,
.set_mask_bits = sw_set_mask_bits,
.set_texture_window = sw_set_texture_window,
.set_color_modulation = sw_set_color_modulation,
.set_precise_triangle = sw_set_precise_triangle,
.set_perspective_triangle = sw_set_perspective_triangle,
.fill_rect = sw_fill_rect,
.copy_rect = sw_copy_rect,
.draw_flat_triangle = sw_draw_flat_triangle,
.draw_gouraud_triangle = sw_draw_gouraud_triangle,
.draw_textured_triangle = sw_draw_textured_triangle,
.draw_shaded_textured_triangle = sw_draw_shaded_textured_triangle,
.draw_flat_rect = sw_draw_flat_rect,
.draw_textured_rect = sw_draw_textured_rect,
.draw_textured_rect_scaled = sw_draw_textured_rect_scaled,
.draw_line = sw_draw_line,
.draw_shaded_line = sw_draw_shaded_line,
.render_display = sw_render_display,
.render_display_hires = sw_render_display_hires,
.vram_write = sw_vram_write,
.vram_read = sw_vram_read,
.vram_transfer_in = sw_vram_transfer_in,
.vram_transfer_out = sw_vram_transfer_out,
.set_draw_area = sw_set_draw_area,
.get_draw_area = sw_get_draw_area,
.set_draw_offset = sw_set_draw_offset,
.wide_configure = sw_wide_configure,
.wide_set_target = sw_wide_set_target,
.wide_disable_target = sw_wide_disable_target,
.wide_clear = sw_wide_clear,
.wide_clear_margins = sw_wide_clear_margins,
.render_wide_display = sw_render_wide_display,
.wide_dump_full = sw_wide_dump_full,
};
/* Supplied by gpu_gl_renderer.c; returns NULL until the GL backend is ready. */
extern const GpuRenderBackend *gl_backend_get(void);
/* Supplied by gpu_vk_renderer.c; returns NULL until the Vulkan backend is
* ready (init succeeded). When Vulkan isn't compiled in it returns NULL. */
extern const GpuRenderBackend *vk_backend_get(void);
static const GpuRenderBackend *g_b = &SW_BACKEND;
static GrBackend g_effective = GR_BACKEND_SOFTWARE;
void gr_set_backend(GrBackend backend) {
if (backend == GR_BACKEND_OPENGL) {
const GpuRenderBackend *gl = gl_backend_get();
if (gl) {
g_b = gl;
g_effective = GR_BACKEND_OPENGL;
fprintf(stdout, "psxrecomp: renderer = opengl (%s)\n", gl->name);
return;
}
fprintf(stdout, "psxrecomp: renderer = opengl requested but unavailable "
"— falling back to software\n");
} else if (backend == GR_BACKEND_VULKAN) {
const GpuRenderBackend *vk = vk_backend_get();
if (vk) {
g_b = vk;
g_effective = GR_BACKEND_VULKAN;
fprintf(stdout, "psxrecomp: renderer = vulkan (%s)\n", vk->name);
return;
}
fprintf(stdout, "psxrecomp: renderer = vulkan requested but unavailable "
"— falling back to software\n");
}
g_b = &SW_BACKEND;
g_effective = GR_BACKEND_SOFTWARE;
}
GrBackend gr_backend(void) { return g_effective; }
/* ---- Dispatch wrappers (one line each; forward to the active backend) ---- */
void gr_init(uint16_t *vram) { g_b->init(vram); }
void gr_set_scale(int scale) { g_b->set_scale(scale); }
int gr_scale(void) { return g_b->scale(); }
void gr_set_texture_filter(int bilinear) { g_b->set_texture_filter(bilinear); }
int gr_texture_filter(void) { return g_b->texture_filter(); }
void gr_set_semi_transparency(int e, int m) { g_b->set_semi_transparency(e, m); }
void gr_set_mask_bits(int s, int c) { g_b->set_mask_bits(s, c); }
void gr_set_texture_window(uint32_t raw) { tex_pack_set_texture_window(raw); g_b->set_texture_window(raw); }
void gr_set_color_modulation(int r, int g, int b, int raw) { g_b->set_color_modulation(r, g, b, raw); }
void gr_set_precise_triangle(int enabled, int32_t x0, int32_t y0, int32_t x1, int32_t y1,
int32_t x2, int32_t y2) {
if (g_b->set_precise_triangle)
g_b->set_precise_triangle(enabled, x0, y0, x1, y1, x2, y2);
}
void gr_set_depth_triangle(int enabled, float z0, float z1, float z2) {
if (g_b->set_depth_triangle)
g_b->set_depth_triangle(enabled, z0, z1, z2);
}
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
if (g_b->set_perspective_triangle)
g_b->set_perspective_triangle(enabled, q0, q1, q2);
}
void gr_fill_rect(int x, int y, int w, int h, uint16_t c) {
tex_pack_invalidate(x, y, w, h);
g_b->fill_rect(x, y, w, h, c);
}
void gr_copy_rect(int sx, int sy, int dx, int dy, int w, int h) {
tex_pack_invalidate(dx, dy, w, h);
g_b->copy_rect(sx, sy, dx, dy, w, h);
}
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint16_t c) {
g_b->draw_flat_triangle(x0, y0, x1, y1, x2, y2, c);
}
void gr_draw_gouraud_triangle(int x0, int y0, uint16_t c0, int x1, int y1, uint16_t c1,
int x2, int y2, uint16_t c2) {
g_b->draw_gouraud_triangle(x0, y0, c0, x1, y1, c1, x2, y2, c2);
}
/* HD texture identity. Hooked here rather than in a backend so software, GL and
* Vulkan all feed the same tracker, and so the uv-limit model stays the single
* one in gpu_uv.h — the backends derive their own `lim` from these same helpers
* and the same (pre-compensation) uv values, so the rect we match against is the
* rect they sample. No-op unless a pack or the dumper is enabled. */
/* Arm (or clear) the backend's replacement slot for the primitive about to be
* drawn. Same one-shot contract as set_precise_triangle: set before, cleared
* after, so a primitive without a replacement can never inherit the previous
* one's. Cheap no-op when no pack is loaded — the lookup exits on the first
* test — and entirely absent for backends that do not implement it. */
static int tex_pack_arm(const int lim[4], uint16_t clut_x, uint16_t clut_y,
uint16_t texpage,
int sx0, int sy0, int sx1, int sy1) {
if (!g_b->set_replacement) return 0;
TexPackRepl r;
if (!tex_pack_lookup_replacement(lim, clut_x, clut_y, texpage, &r)) return 0;
/* Record WHICH primitive got substituted and where it landed, not just how
* many. Counters alone could not distinguish "the HUD is being replaced by
* the wrong image" from "replacement damages state the HUD needs". */
tex_pack_note_armed(r.id, sx0, sy0, sx1, sy1);
g_b->set_replacement(&r);
return 1;
}
static void tex_pack_disarm(int armed) {
if (armed && g_b->set_replacement) g_b->set_replacement(NULL);
}
static int tex_pack_note_tri(int x0, int y0, int u0, int v0,
int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
if (!tex_pack_active()) return 0;
const int xs[3] = { x0, x1, x2 }, ys[3] = { y0, y1, y2 };
const int us[3] = { u0, u1, u2 }, vs[3] = { v0, v1, v2 };
int lim[4];
psx_uv_tri_limits(xs, ys, us, vs, lim);
tex_pack_on_textured_prim(lim, clut_x, clut_y, texpage);
int sx0 = xs[0], sx1 = xs[0], sy0 = ys[0], sy1 = ys[0];
for (int i = 1; i < 3; i++) {
if (xs[i] < sx0) sx0 = xs[i];
if (xs[i] > sx1) sx1 = xs[i];
if (ys[i] < sy0) sy0 = ys[i];
if (ys[i] > sy1) sy1 = ys[i];
}
return tex_pack_arm(lim, clut_x, clut_y, texpage, sx0, sy0, sx1, sy1);
}
void gr_draw_textured_triangle(int x0, int y0, int u0, int v0, int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
const int armed = tex_pack_note_tri(x0, y0, u0, v0, x1, y1, u1, v1,
x2, y2, u2, v2, clut_x, clut_y, texpage);
g_b->draw_textured_triangle(x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2,
clut_x, clut_y, texpage);
tex_pack_disarm(armed);
}
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0, uint32_t c0,
int x1, int y1, int u1, int v1, uint32_t c1,
int x2, int y2, int u2, int v2, uint32_t c2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage, int raw) {
const int armed = tex_pack_note_tri(x0, y0, u0, v0, x1, y1, u1, v1,
x2, y2, u2, v2, clut_x, clut_y, texpage);
g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1,
x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw);
tex_pack_disarm(armed);
}
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) { g_b->draw_flat_rect(x, y, w, h, c); }
/* Rect prims carry their uv corners directly; u1/v1 are the EXCLUSIVE corner,
* which for the unscaled form is u+w / v+h (matching glb_draw_textured_rect). */
static int tex_pack_note_rect(int u0, int v0, int u1, int v1,
uint16_t clut_x, uint16_t clut_y, uint16_t texpage,
int sx, int sy, int sw, int sh) {
if (!tex_pack_active()) return 0;
int lim[4];
psx_uv_rect_limits(u0, v0, u1, v1, lim);
tex_pack_on_textured_prim(lim, clut_x, clut_y, texpage);
return tex_pack_arm(lim, clut_x, clut_y, texpage, sx, sy, sx + sw, sy + sh);
}
void gr_draw_textured_rect(int x, int y, int w, int h, int u, int v,
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
const int armed = tex_pack_note_rect(u, v, u + w, v + h, clut_x, clut_y, texpage,
x, y, w, h);
g_b->draw_textured_rect(x, y, w, h, u, v, clut_x, clut_y, texpage);
tex_pack_disarm(armed);
}
void gr_draw_textured_rect_scaled(int x, int y, int w, int h, int u0, int v0, int u1, int v1,
uint16_t clut_x, uint16_t clut_y, uint16_t texpage) {
const int armed = tex_pack_note_rect(u0, v0, u1, v1, clut_x, clut_y, texpage,
x, y, w, h);
g_b->draw_textured_rect_scaled(x, y, w, h, u0, v0, u1, v1, clut_x, clut_y, texpage);
tex_pack_disarm(armed);
}
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t c) { g_b->draw_line(x0, y0, x1, y1, c); }
void gr_draw_shaded_line(int x0, int y0, uint16_t c0, int x1, int y1, uint16_t c1) {
g_b->draw_shaded_line(x0, y0, c0, x1, y1, c1);
}
int gr_render_display(uint32_t *o, int p, int dx, int dy, int dw, int dh) {
return g_b->render_display(o, p, dx, dy, dw, dh);
}
int gr_render_display_hires(uint32_t *o, int p, int dx, int dy, int dw, int dh) {
return g_b->render_display_hires(o, p, dx, dy, dw, dh);
}
void gr_vram_write(int x, int y, uint16_t pixel) { tex_pack_invalidate(x, y, 1, 1); g_b->vram_write(x, y, pixel); }
uint16_t gr_vram_read(int x, int y) { return g_b->vram_read(x, y); }
void gr_vram_transfer_in(int x, int y, int w, int h, const uint16_t *d) {
/* The staged rect `d` is the hash preimage: CRC32 over it is the texture
* hash a Beetle-format pack is keyed by. */
tex_pack_on_upload(x, y, w, h, d);
g_b->vram_transfer_in(x, y, w, h, d);
}
void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *d) { g_b->vram_transfer_out(x, y, w, h, d); }
void gr_set_draw_area(int x1, int y1, int x2, int y2){ g_b->set_draw_area(x1, y1, x2, y2); }
void gr_get_draw_area(int *x1, int *y1, int *x2, int *y2) { g_b->get_draw_area(x1, y1, x2, y2); }
void gr_set_draw_offset(int x, int y) { g_b->set_draw_offset(x, y); }
/* Native-wide compositor — present only on backends that supply it. */
int gr_wide_supported(void) { return g_b->render_wide_display != 0; }
void gr_wide_configure(int wide_w, int offset) {
if (g_b->wide_configure) g_b->wide_configure(wide_w, offset);
}
void gr_wide_set_target(int base_x) {
if (g_b->wide_set_target) g_b->wide_set_target(base_x);
}
void gr_wide_disable_target(void) {
if (g_b->wide_disable_target) g_b->wide_disable_target();
}
void gr_wide_clear(int base_x, int y, int h, uint16_t color) {
if (g_b->wide_clear) g_b->wide_clear(base_x, y, h, color);
}
void gr_wide_clear_margins(int base_x, int y, int h, uint16_t color, int sides) {
if (g_b->wide_clear_margins)
g_b->wide_clear_margins(base_x, y, h, color, sides);
}
int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
int disp_y, int disp_h) {
if (g_b->render_wide_display)
return g_b->render_wide_display(out, pitch, base_x, disp_y, disp_h);
return 0;
}
int gr_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x) {
if (g_b->wide_dump_full)
return g_b->wide_dump_full(out, cap_pixels, ow, oh, base_x);
return 0;
}