diff --git a/runtime/include/gpu_gl_renderer.h b/runtime/include/gpu_gl_renderer.h index 122bdb89..4a27bdb2 100644 --- a/runtime/include/gpu_gl_renderer.h +++ b/runtime/include/gpu_gl_renderer.h @@ -136,6 +136,7 @@ uint32_t gl_renderer_stereo_unavailable(void); int gl_renderer_stereo_begin(int x, int y, int w, int h, int reuse_backup); int gl_renderer_stereo_end(uint32_t eye, int keep); void gl_renderer_stereo_stage_reset(void); +int gl_renderer_stereo_stage_readback(uint32_t eye,uint32_t width,uint32_t height,uint8_t *rgba); int gl_renderer_stereo_publish(uint64_t pair_id, uint64_t guest_cycle, const int32_t eye_view[2][3]); void gl_renderer_stereo_reset(void); diff --git a/runtime/include/mod_plugins.h b/runtime/include/mod_plugins.h index 3b1cc33c..8f48bfb0 100644 --- a/runtime/include/mod_plugins.h +++ b/runtime/include/mod_plugins.h @@ -600,6 +600,14 @@ uint32_t psx_mod_render_pass_plan(uint32_t period_vblanks, * partially updated plugin state) across guest code, or tolerate the abort. */ int psx_mod_render_pass(struct CPUState* cpu, const PSXModRenderPass* pass, PSXModRenderPassFn fn, void* user); +/* Isolated draw/readback using the stereo-capable rollback transaction. + * Never publishes an eye pair or changes presentation. Returns native-sized + * top-to-bottom RGBA8 pixels (alpha remains the PSX mask bit, not coverage). + * Call outside another pass. rgba_bytes must cover rect->w*rect->h*4. */ +int psx_mod_render_capture_rgba(struct CPUState *cpu,const PSXModRenderPass *rect, + PSXModRenderPassFn fn,void *user, + uint8_t *rgba,uint32_t rgba_bytes); +#define PSX_MOD_RENDER_CAPTURE_RGBA 1 /* * Why passes cannot run right now, the host-time budget aside (a plan that * returns 0 while this says READY was shed for time). A plugin that relies on diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index f038644e..97567806 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -610,6 +610,8 @@ static GLuint s_osd_tex = 0; static int s_osd_tw = 0, s_osd_th = 0; static GLuint s_present_prog = 0, s_present_vao = 0; static GLuint s_xr_color_prog = 0, s_xr_native_tex = 0; +static GLuint s_capture_read_tex,s_capture_read_fbo; +static int s_capture_read_w,s_capture_read_h; static void gl_swap_with_osd(void); static int s_post_aa = 0; /* GL_POST_AA_*; post-process AA (post_aa_apply) */ static int s_native_surface_enabled, s_native_surface_pending; @@ -8331,6 +8333,7 @@ static void pass_resources_release(void) { if(s_xr_native_tex) glDeleteTextures(1,&s_xr_native_tex); } s_xr_color_prog=s_xr_native_tex=0; + pass_free_color_fbo(&s_capture_read_tex,NULL,&s_capture_read_fbo,&s_capture_read_w,&s_capture_read_h); stereo_resources_release(); for(int gi=0;gi=2 || !rgba || !w || !h || w>1024 || h>512)return 0; + StereoPair *pair=&s_stereo_pair[1-s_stereo_current]; + if(!(pair->mask&(1u<fbo[eye]); + p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER,s_capture_read_fbo); + p_glBlitFramebuffer(0,0,pair->tw[eye],pair->th[eye],0,0,w,h,GL_COLOR_BUFFER_BIT,GL_NEAREST); + p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,s_capture_read_fbo); + p_glBindBuffer(0x88EB,0);glPixelStorei(GL_PACK_ALIGNMENT,1);glPixelStorei(GL_PACK_ROW_LENGTH,0); + glPixelStorei(GL_PACK_SKIP_ROWS,0);glPixelStorei(GL_PACK_SKIP_PIXELS,0); + glReadPixels(0,0,w,h,GL_RGBA,GL_UNSIGNED_BYTE,rgba); + } + p_glBindFramebuffer(PSXGL_READ_FRAMEBUFFER,read);p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER,draw); + glBindTexture(GL_TEXTURE_2D,texture);glPixelStorei(GL_PACK_ALIGNMENT,pack); + glPixelStorei(GL_PACK_ROW_LENGTH,row);glPixelStorei(GL_PACK_SKIP_ROWS,skipr);glPixelStorei(GL_PACK_SKIP_PIXELS,skipp); + p_glBindBuffer(0x88EB,pbo);if(scissor)glEnable(GL_SCISSOR_TEST);if(srgb)glEnable(0x8DB9); + return ok; +} static void stereo_invalidate(void) { s_stereo_valid = 0; s_stereo_pair[0].mask = s_stereo_pair[1].mask = 0; diff --git a/runtime/src/render_pass.c b/runtime/src/render_pass.c index fdeb5a6a..456779fa 100644 --- a/runtime/src/render_pass.c +++ b/runtime/src/render_pass.c @@ -979,3 +979,19 @@ int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame, s_pair_busy = 0; return ok; } + +int psx_mod_render_capture_rgba(CPUState *cpu,const PSXModRenderPass *rect, + PSXModRenderPassFn fn,void *user, + uint8_t *rgba,uint32_t bytes) { + if(s_pair_busy || !cpu || !rect || !fn || !rgba || rect->struct_sizew || !rect->h || rect->x+rect->w>1024 || rect->y+rect->h>512 || + (uint64_t)rect->w*rect->h*4>bytes || transaction_status(1)!=PSX_MOD_RENDER_PASS_READY)return 0; + if(g_overlay_flush_pending_cycles)g_overlay_flush_pending_cycles(); + s_pair_busy=1;s_plan_serial++; + gl_renderer_stereo_stage_reset(); + PSXModRenderPass capture=*rect;capture.alpha_q16=0; + int ok=render_transaction(cpu,&capture,fn,user,0); + if(ok)ok=gl_renderer_stereo_stage_readback(0,rect->w,rect->h,rgba); + gl_renderer_stereo_stage_reset();s_pair_busy=0; + return ok; +}