Files
psxrecomp/runtime/shaders/geo_tex.vert
Matthew Stanley c8b692eda2 feat(video): opt-in sub-pixel vertex precision + perspective-correct textures
Makes the parked projection/perspective work reachable and gets it into the
renderers people actually use. Addresses issue #92 (PGXP-style geometry and
texture correction).

The GTE side cache, SWC2 RAM provenance, GP0 triangle preparation and the
software-renderer consumption path already landed in 2ceaf5a ("park projection
and perspective correction", from kem0x PR #14), but were unreachable: the
generic setters gte_geometry_correction_set() / gpu_texture_correction_set()
had no production caller, and the corrected vertices went straight to
sw_set_precise_triangle(), bypassing the renderer vtable — while the default
renderer is OpenGL.

This adds:

- [video] geometry_correction and [video] perspective_texturing, in game.toml
  and settings.toml, both defaulting false. Independent knobs; the launcher
  seed marks them present so a save round-trips a hand-edited key instead of
  dropping it. Startup names the active pair, and notes when geometry
  correction is on below supersampling 2 (where it cannot be seen).
- gr_set_precise_triangle() / gr_set_perspective_triangle() on the
  GpuRenderBackend vtable, so gpu.c hands the correction to whichever backend
  is live rather than to the software rasterizer directly. NULL entries no-op.
- OpenGL and Vulkan consumption. Positions are already float end-to-end, so
  sub-pixel vertices drop in; the readback dirty rect widens by one pixel to
  cover them. Perspective UVs use a per-vertex a_q weight and clip coordinates
  pre-multiplied by w = 1/q, letting the hardware's own perspective divide
  interpolate a smooth UV varying beside the affine noperspective one. a_q == 0
  — the default, and what every prim gets with the feature off — makes w
  exactly 1.0 and selects the affine varying, so the faithful path is the
  pre-feature pipeline unchanged.

The PS1-visible GTE SXY FIFO stays integer either way: guest-side screen-bounds
culling and SXY readback are untouched, and this remains visual-only.

Also fixes a pre-existing concurrent-build race found while validating: every
runtime target runs embed_spirv.py over the same shaders, and each wrote
"<shader>.spv" next to the source, so under -j N one process deleted the file
another had just written (FileNotFoundError on a shader that compiled fine).
The intermediates now go to a per-invocation temp dir.

Parked-work provenance retained; see ENHANCEMENTS.md G1.

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:02:58 -07:00

57 lines
2.3 KiB
GLSL

#version 450
/* Textured primitives. Per-prim texture state (texpage, clut, depth, raw, uv
* limits) rides in FLAT vertex attributes so consecutive prims with the same
* blend/mask/texture-window/filter coalesce into one draw (see flush_tex_batch).
* The push-constant block carries the vertex transform (u_shift/u_xoff/u_xhalf)
* and the fragment batch keys (semipass/maskset/filter/twin). Ported from the
* GL backend's TEX_VS. */
layout(location = 0) in vec2 a_pos;
layout(location = 1) in vec2 a_uv;
layout(location = 2) in vec4 a_col;
layout(location = 3) in vec2 a_tpage;
layout(location = 4) in vec2 a_clut;
layout(location = 5) in float a_depth;
layout(location = 6) in float a_raw;
layout(location = 7) in vec4 a_limits;
/* Perspective weight ([video] perspective_texturing). 0 = affine, the
* PS1-faithful default: w below is then exactly 1.0 and the fragment shader
* reads the noperspective varying, i.e. the pre-feature pipeline unchanged. */
layout(location = 8) in float a_q;
layout(push_constant) uniform PC {
float u_shift;
float u_xoff;
float u_xhalf;
int u_semipass;
int u_maskset;
int u_filter;
ivec4 u_twin;
} pc;
layout(location = 0) noperspective out vec2 v_uv;
layout(location = 1) noperspective out vec4 v_col;
layout(location = 2) flat out ivec2 v_tpage;
layout(location = 3) flat out ivec2 v_clut;
layout(location = 4) flat out int v_depth;
layout(location = 5) flat out int v_raw;
layout(location = 6) flat out ivec4 v_limits;
layout(location = 7) smooth out vec2 v_uv_p; /* perspective-correct UV */
layout(location = 8) flat out int v_persp;
void main() {
v_uv = a_uv; v_uv_p = a_uv; v_col = a_col;
v_persp = (a_q > 0.0) ? 1 : 0;
v_tpage = ivec2(a_tpage + 0.5);
v_clut = ivec2(a_clut + 0.5);
v_depth = int(a_depth + 0.5);
v_raw = int(a_raw + 0.5);
v_limits = ivec4(floor(a_limits + 0.5));
/* Perspective-correct UV rides the standard w divide: emit clip coords
* pre-multiplied by w = 1/q so post-divide NDC is unchanged while the
* rasterizer interpolates v_uv_p hyperbolically. a_q == 0 => w == 1.0. */
float w = (a_q > 0.0) ? (1.0 / a_q) : 1.0;
vec2 ndc = vec2((a_pos.x + pc.u_shift + pc.u_xoff) / pc.u_xhalf - 1.0,
(a_pos.y + pc.u_shift) / 256.0 - 1.0);
gl_Position = vec4(ndc * w, 0.0, w);
}