mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
0bdf338571
Completes the feature tex_pack.h has always described. Until now it identified
replacement textures and never swapped them: it opened files only for writing,
and its known-set was built by parsing filenames, so a pack could report
hundreds of "matched" entries while changing nothing on screen.
Decode a matched entry lazily via stb_image (declarations only; the
implementation is compiled in psx_window_icon.cpp, whose STBI_NO_STDIO must be
matched here, hence reading the file ourselves), cache it per key, and hand the
draw path the image plus the mapping from texture-page UVs into it:
s = (u - origin_u) / src_w t = (v - origin_v) / src_h
so the replacement's own resolution never appears at the call site — a 16x
atlas and a 1:1 image are addressed identically.
The GL side draws a replaced prim through its own program (REPL_FS, sharing
TEX_VS so positions, the perspective divide and the native-wide transforms stay
bit-identical) and its own draw, leaving the batch path untouched. Two things
that cost real debugging time and must not be re-derived:
- frag.a is the PS1 MASK BIT, not opacity, and blend_factor's alpha is the
dual-source destination factor. Writing either as coverage sets the mask
bit on every replaced pixel and makes later masked draws vanish.
- The two-pass mask/stencil protocol is NOT duplicated. tex_batch_draw_passes
is parameterised on the program's uniform locations and the mask flag, and
both paths call it. A hand-written second copy dropped the s_mask_check
fixup and the semi == 4 dual-source case.
RECTS AND SPRITES ARE TRACKED BUT NOT SUBSTITUTED. The backend consumes a
replacement in its textured-triangle path only; rect prims take their own route
and never sample it, so arming one produced primitives that drew nothing. On
WipEout 3 that is the whole 2D layer — HUD meters, boxes, pickup indicators,
and every glyph, since the font atlases are addressed by sprites — which is why
enabling a pack erased the interface while the 3D scene rendered correctly.
Identity tracking still runs, so the dumper, the match set and the UV census
are unaffected; only application is withheld until that path is wired.
Also adds the instrumentation that found this after four wrong guesses:
"tex_pack repl=0/1" toggles substitution live (an A/B needing a rebuild does
not get run), and "tex_pack sub=armed" reports WHICH textures were substituted
and where they landed on screen, which is what distinguished "the HUD is being
replaced by the wrong image" from "replacement corrupts state the HUD needs".
Measured in-race at 32:9 with princepeter64's Wip3out pack: 292 entries
indexed, 7 decoded, zero decode failures, 8006 replaced draws, full HUD intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
223 lines
11 KiB
C
223 lines
11 KiB
C
#ifndef PSX_GPU_RENDER_H
|
|
#define PSX_GPU_RENDER_H
|
|
|
|
/* Renderer facade. gpu.c (command processing) and main.cpp (present) call
|
|
* these gr_* entry points instead of a specific backend. The facade
|
|
* dispatches to a selected backend: the software rasterizer (gpu_sw_renderer.c,
|
|
* the default + fallback) or a hardware OpenGL backend (gpu_gl_renderer.c).
|
|
*
|
|
* The gr_* signatures mirror the software renderer's interface exactly, so a
|
|
* backend is just a table of the same functions. Select the backend with
|
|
* gr_set_backend() BEFORE gr_init(); gr_backend() reports the EFFECTIVE backend
|
|
* (a requested backend that fails to initialize falls back to software). */
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef enum {
|
|
GR_BACKEND_SOFTWARE = 0,
|
|
GR_BACKEND_OPENGL = 1,
|
|
GR_BACKEND_VULKAN = 2
|
|
} GrBackend;
|
|
|
|
void gr_set_backend(GrBackend backend); /* call before gr_init() */
|
|
GrBackend gr_backend(void); /* effective backend after init */
|
|
|
|
/* Lifecycle / global state */
|
|
void gr_init(uint16_t *vram);
|
|
void gr_set_scale(int scale);
|
|
int gr_scale(void);
|
|
void gr_set_texture_filter(int bilinear);
|
|
int gr_texture_filter(void);
|
|
|
|
/* Per-primitive draw state */
|
|
void gr_set_semi_transparency(int enabled, int mode);
|
|
void gr_set_mask_bits(int set_bit, int check_bit);
|
|
void gr_set_texture_window(uint32_t raw);
|
|
void gr_set_color_modulation(int r, int g, int b, int raw_texture);
|
|
|
|
/* Sub-pixel vertex override for the NEXT triangle ([video] geometry_correction).
|
|
* x/y are signed 16.16 screen coordinates carrying the projection fraction the
|
|
* GTE discarded; the integer positions still passed to gr_draw_*_triangle
|
|
* remain authoritative for anything that must stay faithful (dirty-rect
|
|
* tracking, the software path's native VRAM write). enabled == 0 clears the
|
|
* override. Backends that do not implement it ignore the call, so the integer
|
|
* path is what they draw. The override is consumed by the next triangle. */
|
|
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);
|
|
/* Perspective-correct UV weights for the NEXT triangle ([video]
|
|
* perspective_texturing). q[i] is the normalized 1/z homogeneous weight at
|
|
* vertex i. enabled == 0 restores the PS1's affine UV interpolation. */
|
|
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2);
|
|
|
|
/* Primitives */
|
|
void gr_fill_rect(int x, int y, int w, int h, uint16_t color);
|
|
void gr_copy_rect(int src_x, int src_y, int dst_x, int dst_y, int w, int h);
|
|
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
uint16_t color);
|
|
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);
|
|
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);
|
|
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
|
|
uint32_t color0,
|
|
int x1, int y1, int u1, int v1,
|
|
uint32_t color1,
|
|
int x2, int y2, int u2, int v2,
|
|
uint32_t color2,
|
|
uint16_t clut_x, uint16_t clut_y,
|
|
uint16_t texpage, int raw_texture);
|
|
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t color);
|
|
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);
|
|
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);
|
|
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t color);
|
|
void gr_draw_shaded_line(int x0, int y0, uint16_t c0,
|
|
int x1, int y1, uint16_t c1);
|
|
|
|
/* Display readout (present path) */
|
|
int gr_render_display(uint32_t *out_pixels, int out_pitch,
|
|
int disp_x, int disp_y, int disp_w, int disp_h);
|
|
int gr_render_display_hires(uint32_t *out_pixels, int out_pitch,
|
|
int disp_x, int disp_y, int disp_w, int disp_h);
|
|
|
|
/* VRAM transfers */
|
|
void gr_vram_write(int x, int y, uint16_t pixel);
|
|
uint16_t gr_vram_read(int x, int y);
|
|
void gr_vram_transfer_in(int x, int y, int w, int h, const uint16_t *data);
|
|
void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *data);
|
|
|
|
/* Draw area / offset */
|
|
void gr_set_draw_area(int x1, int y1, int x2, int y2);
|
|
void gr_get_draw_area(int *x1, int *y1, int *x2, int *y2);
|
|
void gr_set_draw_offset(int x, int y);
|
|
|
|
/* Native-wide compositor. gr_wide_supported() reports whether the active
|
|
* backend implements it; if not, the others are no-ops and the caller keeps
|
|
* the canonical present path. */
|
|
int gr_wide_supported(void);
|
|
void gr_wide_configure(int wide_w, int offset);
|
|
void gr_wide_set_target(int base_x);
|
|
void gr_wide_disable_target(void);
|
|
void gr_wide_clear(int base_x, int y, int h, uint16_t color);
|
|
/* sides: bit 0 = left synthetic margin, bit 1 = right. */
|
|
void gr_wide_clear_margins(int base_x, int y, int h, uint16_t color, int sides);
|
|
int gr_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x);
|
|
int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
|
|
int disp_y, int disp_h);
|
|
|
|
/* ---- Backend vtable -----------------------------------------------------
|
|
* A backend supplies the same set of functions. gpu_gl_renderer.c (Phase 2)
|
|
* provides gl_backend_get(); until it does, requesting OpenGL falls back to
|
|
* software. Members mirror the gr_* / sw_* signatures 1:1. */
|
|
typedef struct GpuRenderBackend {
|
|
const char *name;
|
|
void (*init)(uint16_t *vram);
|
|
void (*set_scale)(int scale);
|
|
int (*scale)(void);
|
|
void (*set_texture_filter)(int bilinear);
|
|
int (*texture_filter)(void);
|
|
void (*set_semi_transparency)(int enabled, int mode);
|
|
void (*set_mask_bits)(int set_bit, int check_bit);
|
|
void (*set_texture_window)(uint32_t raw);
|
|
void (*set_color_modulation)(int r, int g, int b, int raw_texture);
|
|
/* Optional (NULL = unsupported, facade no-ops): sub-pixel vertex override
|
|
* and perspective UV weights for the next triangle. See gr_* above. */
|
|
void (*set_precise_triangle)(int enabled,
|
|
int32_t x0, int32_t y0,
|
|
int32_t x1, int32_t y1,
|
|
int32_t x2, int32_t y2);
|
|
void (*set_perspective_triangle)(int enabled, float q0, float q1, float q2);
|
|
/* Optional: HD texture replacement for the NEXT textured primitive. `repl`
|
|
* is a const TexPackRepl* (void* to keep this header free of tex_pack.h),
|
|
* or NULL to clear. Set immediately before the draw and cleared after, the
|
|
* same one-shot contract as set_precise_triangle. */
|
|
void (*set_replacement)(const void *repl);
|
|
void (*fill_rect)(int x, int y, int w, int h, uint16_t color);
|
|
void (*copy_rect)(int src_x, int src_y, int dst_x, int dst_y, int w, int h);
|
|
void (*draw_flat_triangle)(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
uint16_t color);
|
|
void (*draw_gouraud_triangle)(int x0, int y0, uint16_t c0,
|
|
int x1, int y1, uint16_t c1,
|
|
int x2, int y2, uint16_t c2);
|
|
void (*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);
|
|
void (*draw_shaded_textured_triangle)(int x0, int y0, int u0, int v0,
|
|
uint32_t color0,
|
|
int x1, int y1, int u1, int v1,
|
|
uint32_t color1,
|
|
int x2, int y2, int u2, int v2,
|
|
uint32_t color2,
|
|
uint16_t clut_x, uint16_t clut_y,
|
|
uint16_t texpage, int raw_texture);
|
|
void (*draw_flat_rect)(int x, int y, int w, int h, uint16_t color);
|
|
void (*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);
|
|
void (*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);
|
|
void (*draw_line)(int x0, int y0, int x1, int y1, uint16_t color);
|
|
void (*draw_shaded_line)(int x0, int y0, uint16_t c0,
|
|
int x1, int y1, uint16_t c1);
|
|
int (*render_display)(uint32_t *out, int pitch,
|
|
int dx, int dy, int dw, int dh);
|
|
int (*render_display_hires)(uint32_t *out, int pitch,
|
|
int dx, int dy, int dw, int dh);
|
|
void (*vram_write)(int x, int y, uint16_t pixel);
|
|
uint16_t (*vram_read)(int x, int y);
|
|
void (*vram_transfer_in)(int x, int y, int w, int h, const uint16_t *data);
|
|
void (*vram_transfer_out)(int x, int y, int w, int h, uint16_t *data);
|
|
void (*set_draw_area)(int x1, int y1, int x2, int y2);
|
|
void (*get_draw_area)(int *x1, int *y1, int *x2, int *y2);
|
|
void (*set_draw_offset)(int x, int y);
|
|
/* Native-wide compositor (optional; NULL on backends without it — the
|
|
* facade then reports gr_wide_supported() == 0 and the caller keeps the
|
|
* canonical present). */
|
|
void (*wide_configure)(int wide_w, int offset);
|
|
void (*wide_set_target)(int base_x);
|
|
void (*wide_disable_target)(void);
|
|
void (*wide_clear)(int base_x, int y, int h, uint16_t color);
|
|
void (*wide_clear_margins)(int base_x, int y, int h, uint16_t color, int sides);
|
|
int (*render_wide_display)(uint32_t *out, int pitch, int base_x,
|
|
int disp_y, int disp_h);
|
|
/* Dump the ENTIRE wide compositor surface for base_x (all double-buffer
|
|
* bands + both margins), g_wide_w x VRAM_H. Debug/inspection only; returns
|
|
* pixel count, writes width/height to ow/oh. NULL if unsupported. */
|
|
int (*wide_dump_full)(uint32_t *out, int cap_pixels, int *ow, int *oh,
|
|
int base_x);
|
|
} GpuRenderBackend;
|
|
|
|
/* GL supersampling ceiling. Deliberately separate from SW_MAX_INTERNAL_SCALE:
|
|
* the software path allocates a VRAM-sized hi-res MIRROR costing 1 MB * scale^2
|
|
* (256 MB at 16x), but under GL that mirror stays at 1x and the cost is an FBO
|
|
* the GPU already has memory for. 16x is a 16384x8192 render target, the
|
|
* GL_MAX_TEXTURE_SIZE floor for desktop GL 3.3; the GL backend clamps again to
|
|
* the driver's real limit once a context exists. */
|
|
#define GL_MAX_INTERNAL_SCALE 16
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSX_GPU_RENDER_H */
|