mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Avoid clipped GL draws and redundant draw-state flushes
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Shared GL draw submission cost
|
||||
|
||||
The native GL raster path rejects primitives whose entire integer bounding box
|
||||
lies outside the current GPU drawing area. GPU decoding has already applied the
|
||||
draw offset. A one-pixel guard keeps boundary lines and triangles conservative.
|
||||
Wide backing targets, high-resolution wide mirrors, precision/depth coordinates
|
||||
and seam correction keep their existing path. Fills, transfers and VRAM copies
|
||||
are excluded because they do not obey primitive drawing-area clipping.
|
||||
|
||||
Repeated drawing-area and drawing-offset packets retain their pending batch
|
||||
when the values have not changed. Changed state still flushes in painter order;
|
||||
software state is forwarded in either case. These are host submission savings:
|
||||
GP0 decoding, simulation, interrupts, GPU timing and software raster calls keep
|
||||
their existing behavior. Both HLE and LLE textured batchers use this path. No
|
||||
CPU-specific instructions or new user control are needed.
|
||||
|
||||
`runtime/tests/test_gl_render_thread.c` includes a clipped workload, a visible
|
||||
texture control, a scissor-edge sliver and repeated state packets. It verifies
|
||||
the skipped batches, retained visible pixels, clipping and shared batching.
|
||||
The existing real-context stream runs at 1x/4x, with the worker disabled/enabled
|
||||
and with dynamic resolution changes. On Windows/NVIDIA the native readback,
|
||||
VRAM, hires and actual displayed composition hashes remain unchanged in all
|
||||
six configurations under both batchers. The 22 blend/mask oracle checks pass.
|
||||
|
||||
Mega Man X4's experimental four-view build is the measured caller. Its separate
|
||||
title config enables the existing audited PsyQ store-counter idle detector at
|
||||
USA address `0x800E4F6C`; that is not a new framework default. The detector
|
||||
retains device/IRQ boundaries and rejects unqualified loops. The title's
|
||||
source compatibility hash includes this config. See its heavy-gameplay QA
|
||||
record for performance and native multi-peer evidence. These checks do not
|
||||
qualify every game or guarantee full-speed four-view play.
|
||||
@@ -3228,6 +3228,27 @@ static void ensure_cpu(void) {
|
||||
|
||||
static uint64_t s_scene_prims = 0; /* frame_perf: scene primitives submitted (pre double-draw) */
|
||||
static uint64_t s_scene_prims_tex = 0; /* frame_perf: of which textured (vs flat geometry) */
|
||||
static uint64_t s_clipped_prims = 0;
|
||||
/* gpu.c has already applied the drawing offset to these integer vertices.
|
||||
* Reject only a wholly clipped native draw. Wide mirrors can reveal pixels
|
||||
* outside the native area; precision/seam/windowed paths keep their existing
|
||||
* clipping. A one-pixel guard also retains boundary lines/triangles. Fills
|
||||
* and VRAM copies do not use this test: their writes ignore the draw area. */
|
||||
static int prim_wholly_clipped(const int *xs, const int *ys, int n) {
|
||||
if (g_wide_cur || hiw_on() || s_pc_valid || s_pq_valid || s_pz_valid || s_pgxp_seam)
|
||||
return 0;
|
||||
int x0=xs[0],x1=xs[0],y0=ys[0],y1=ys[0];
|
||||
for (int i=1;i<n;i++) {
|
||||
if(xs[i]<x0)x0=xs[i];if(xs[i]>x1)x1=xs[i];
|
||||
if(ys[i]<y0)y0=ys[i];if(ys[i]>y1)y1=ys[i];
|
||||
}
|
||||
if (x1 < s_area_x1-1 || x0 > s_area_x2+1 ||
|
||||
y1 < s_area_y1-1 || y0 > s_area_y2+1) {
|
||||
s_clipped_prims++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static void mark_prim_dirty(const int *xs, const int *ys, int n, int textured) {
|
||||
s_scene_prims++;
|
||||
int x0 = xs[0], x1 = xs[0], y0 = ys[0], y1 = ys[0];
|
||||
@@ -4564,6 +4585,7 @@ static void seam_sz(const float B[3][3], float z[3]) {
|
||||
|
||||
static void gpu_geometry(GLenum mode, const int *xs, const int *ys,
|
||||
const uint16_t *cs, int n, int semi) {
|
||||
if (prim_wholly_clipped(xs,ys,n)) return;
|
||||
flush_tex_batch(); /* flat prim: drain textured draws first (order + program) */
|
||||
flush_cpu_upload(); /* also drains flat batch if an upload was pending */
|
||||
mark_prim_dirty(xs, ys, n, 0 /* flat */);
|
||||
@@ -4812,6 +4834,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
|
||||
const float *col, uint16_t texpage,
|
||||
uint16_t clut_x, uint16_t clut_y, int rawtex,
|
||||
int semi, const int *lim) {
|
||||
if (prim_wholly_clipped(xs,ys,3)) return;
|
||||
/* PS1 dithering: texture-modulated polygons only (never raw texels or
|
||||
* rectangles), while E1 bit 9 is set. */
|
||||
const int dither_tex = s_dither_mode && s_dither_bit && !rawtex && !s_prim_rect;
|
||||
@@ -5324,11 +5347,20 @@ static void glb_set_depth_triangle(int enabled, float z0, float z1, float z2) {
|
||||
s_pz[0] = z0; s_pz[1] = z1; s_pz[2] = z2;
|
||||
}
|
||||
static void glb_set_draw_area(int x1,int y1,int x2,int y2) {
|
||||
if (s_depth_used && (x1 != s_area_x1 || y1 != s_area_y1 || x2 != s_area_x2 || y2 != s_area_y2))
|
||||
DEPTH_REQUEST_CLEAR(DCLR_AREA); /* PGXP depth: a new drawing area starts clean */
|
||||
flush_flat_batch(); flush_tex_batch(); s_area_x1=x1; s_area_y1=y1; s_area_x2=x2; s_area_y2=y2; sw_set_draw_area(x1,y1,x2,y2); }
|
||||
if (x1 != s_area_x1 || y1 != s_area_y1 || x2 != s_area_x2 || y2 != s_area_y2) {
|
||||
if (s_depth_used) DEPTH_REQUEST_CLEAR(DCLR_AREA);
|
||||
flush_flat_batch(); flush_tex_batch();
|
||||
s_area_x1=x1; s_area_y1=y1; s_area_x2=x2; s_area_y2=y2;
|
||||
}
|
||||
sw_set_draw_area(x1,y1,x2,y2);
|
||||
}
|
||||
static void glb_get_draw_area(int *x1,int *y1,int *x2,int *y2) { sw_get_draw_area(x1,y1,x2,y2); }
|
||||
static void glb_set_draw_offset(int x,int y) { flush_flat_batch(); flush_tex_batch(); s_off_x=x; s_off_y=y; sw_set_draw_offset(x,y); }
|
||||
static void glb_set_draw_offset(int x,int y) {
|
||||
if (x != s_off_x || y != s_off_y) {
|
||||
flush_flat_batch(); flush_tex_batch(); s_off_x=x; s_off_y=y;
|
||||
}
|
||||
sw_set_draw_offset(x,y);
|
||||
}
|
||||
|
||||
/* Pre-context draws (s_raster_ok == 0) fall back to the software rasterizer
|
||||
* over CPU VRAM; the initial full-VRAM upload at context init folds them in.
|
||||
|
||||
@@ -210,6 +210,47 @@ static void textures(void) {
|
||||
cpu_upload(512, 256, 16, 1, clut);
|
||||
}
|
||||
|
||||
static void test_clipped_and_redundant_state(void) {
|
||||
gr_wide_configure(0,0);
|
||||
gr_wide_disable_target();
|
||||
gr_set_draw_area(64,64,127,127);
|
||||
gr_set_draw_offset(0,0);
|
||||
gr_set_semi_transparency(0,0);
|
||||
gr_set_color_modulation(128,128,128,0);
|
||||
mask_bits(0,0);
|
||||
gl_renderer_render_thread_sync("clipped_fixture_before");
|
||||
flush_flat_batch();flush_tex_batch();flush_cpu_upload();
|
||||
const uint64_t batches=s_batch_total,clipped=s_clipped_prims;
|
||||
for(int i=0;i<16;i++) {
|
||||
gr_draw_flat_rect(-100-i,-100,4,4,0x7fff);
|
||||
gr_draw_textured_rect(200+i,200,8,8,0,0,512,256,0x0008);
|
||||
gr_draw_line(-100,200+i,-80,220+i,0x7fff);
|
||||
}
|
||||
gl_renderer_render_thread_sync("clipped_fixture_after");
|
||||
flush_flat_batch();flush_tex_batch();flush_cpu_upload();
|
||||
check(s_batch_total==batches,"wholly clipped draws emit no GPU batches");
|
||||
check(s_clipped_prims>clipped,"clipped primitive counter observes rejected work");
|
||||
check(gr_vram_read(64,64)==0,"clipped draws preserve VRAM");
|
||||
uint16_t page[8*8],palette[16]={0};
|
||||
for(int i=0;i<8*8;i++)page[i]=0x1111;
|
||||
palette[1]=0x7fff;
|
||||
cpu_upload(512,0,8,8,page);cpu_upload(512,256,16,1,palette);
|
||||
gr_set_color_modulation(128,128,128,1);
|
||||
const uint64_t visible=s_batch_total;
|
||||
gr_draw_textured_rect(64,64,8,8,0,0,512,256,0x0008);
|
||||
gr_set_draw_area(64,64,127,127); /* repeated GP0 environment packets */
|
||||
gr_set_draw_offset(0,0);
|
||||
gr_draw_textured_rect(127,80,8,8,0,0,512,256,0x0008); /* retain the scissor-edge sliver */
|
||||
gl_renderer_render_thread_sync("redundant_state_fixture");
|
||||
flush_flat_batch();flush_tex_batch();flush_cpu_upload();
|
||||
check(s_batch_total==visible+1,"redundant draw state preserves one painter-ordered batch");
|
||||
check((gr_vram_read(64,64)&0x7fff)==0x7fff,"visible control draw survives");
|
||||
check((gr_vram_read(127,80)&0x7fff)==0x7fff,"scissor-edge sliver survives");
|
||||
check(gr_vram_read(128,80)==0,"draw area still clips the adjacent pixel");
|
||||
gr_set_draw_area(0,0,1023,511);
|
||||
textures();
|
||||
}
|
||||
|
||||
static void frame(int f, int buf_x) {
|
||||
gr_set_draw_area(buf_x, 0, buf_x + FRAME_W - 1, FRAME_H - 1);
|
||||
gr_set_draw_offset(buf_x, 0);
|
||||
@@ -328,6 +369,7 @@ int main(int argc, char **argv) {
|
||||
if (!gl_renderer_init_context(win)) { fprintf(stderr, "FAIL context\n"); return 2; }
|
||||
check(gr_backend() == GR_BACKEND_OPENGL, "OpenGL backend");
|
||||
textures();
|
||||
test_clipped_and_redundant_state();
|
||||
gr_wide_configure(426, 53);
|
||||
if (threaded) {
|
||||
check(gl_renderer_render_thread_start(2) == 1, "render thread started");
|
||||
|
||||
Reference in New Issue
Block a user