diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0cd92644..d63c6e7c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -168,6 +168,28 @@ test-parallel-rdp-linux-x64: paths: - parallel-rdp-artifacts +# Linux 64-bit: the OpenGL renderers on Mesa's llvmpipe through headless +# EGL, no GPU. tools/lrhost_gl runs krom's RDP tests through Glide64, +# GLideN64, Rice and glN64: each must run to the end and draw, and +# GLideN64 must draw the same frames whatever its colour buffer copy to +# RDRAM is set to. +test-gl-renderers-linux-x64: + extends: + - .core-defs + - .libretro-linux-x64-make-default + variables: + WITH_DYNAREC: x86_64 + HAVE_THR_AL: 1 + HAVE_PARALLEL: 1 + HAVE_PARALLEL_RSP: 1 + DEBIAN_FRONTEND: noninteractive + image: ubuntu:24.04 + script: + - apt-get update -qq && apt-get install -y -qq build-essential nasm git ca-certificates curl python3 libgl-dev libegl-dev libegl-mesa0 libgl1-mesa-dri + - make -j$(nproc) platform=unix WITH_DYNAREC=$WITH_DYNAREC HAVE_THR_AL=$HAVE_THR_AL HAVE_PARALLEL=$HAVE_PARALLEL HAVE_PARALLEL_RSP=$HAVE_PARALLEL_RSP + - make platform=unix lrhost_gl + - python3 tools/ci/gl_renderers.py --core ./parallel_n64_libretro.so --host ./lrhost_gl + # Linux 64-bit (ARM64) libretro-build-linux-aarch64: extends: diff --git a/Graphics/RDP/RDP_state.h b/Graphics/RDP/RDP_state.h index 89d12b73..bc85ad60 100644 --- a/Graphics/RDP/RDP_state.h +++ b/Graphics/RDP/RDP_state.h @@ -373,7 +373,9 @@ static INLINE uint32_t rdp_read_data(uint32_t address) { if ((*(uint32_t*)gfx_info.DPC_STATUS_REG) & 0x1) /* XBUS_DMEM_DMA enabled */ return ((uint32_t*)gfx_info.DMEM)[(address & 0xfff) >> 2]; - return ((uint32_t*)gfx_info.RDRAM)[address >> 2]; + /* The DP's address registers are 24-bit: a list given as a KSEG + * address (as homebrew does) reads RDRAM, as it does on hardware. */ + return ((uint32_t*)gfx_info.RDRAM)[(address & 0x007FFFFC) >> 2]; } bool RDP_Half1(uint32_t _c); diff --git a/Graphics/image_convert.h b/Graphics/image_convert.h index f2b8ec8c..a8768610 100644 --- a/Graphics/image_convert.h +++ b/Graphics/image_convert.h @@ -137,7 +137,7 @@ static INLINE uint32_t RGBA16toRGBA32(uint32_t _c) { union RGBA c; c.raw = _c; - return (c.r << 24) | (c.g << 16) | (c.b << 8) | c.a; + return ((uint32_t)c.r << 24) | ((uint32_t)c.g << 16) | ((uint32_t)c.b << 8) | c.a; } static INLINE void UnswapCopyWrap(const uint8_t *src, uint32_t srcIdx, uint8_t *dest, uint32_t destIdx, uint32_t destMask, uint32_t numBytes) diff --git a/Makefile b/Makefile index dc415a75..0a972ba4 100644 --- a/Makefile +++ b/Makefile @@ -1210,7 +1210,7 @@ clean: clean-tools rm -f $(OBJECTS) $(TARGET) $(OBJECTS:.o=.d) .build-flags clean-tools: - rm -f $(ANGRYLION_TOOLS) lrhost$(EXE_EXT) lrhost_vk$(EXE_EXT) angrylion_replay$(EXE_EXT) $(ANGRYLION_CHECKS) dd_ipl_hle_test$(EXE_EXT) \ + rm -f $(ANGRYLION_TOOLS) lrhost$(EXE_EXT) lrhost_vk$(EXE_EXT) lrhost_gl$(EXE_EXT) angrylion_replay$(EXE_EXT) $(ANGRYLION_CHECKS) dd_ipl_hle_test$(EXE_EXT) \ $(CORE_CHECKS) dynarec_long_block_test$(EXE_EXT) savestate_latch_test$(EXE_EXT) savestate_code_test$(EXE_EXT) cpu_fallback_test$(EXE_EXT) tools/test_prsp_dmem_wrap.o tools/test_command_ring_rthreads.o tools/test_command_ring_ec.o tools/test_granite_util_rthreads.o # Angrylion benchmark and bit-exactness harnesses (tools/angrylion_*.c), @@ -1242,6 +1242,11 @@ lrhost$(EXE_EXT): tools/lrhost.c $(LIBRETRO_COMM_DIR)/features/features_cpu.o lrhost_vk$(EXE_EXT): tools/lrhost.c tools/lrhost_vulkan.h $(LIBRETRO_COMM_DIR)/features/features_cpu.o $(CC) $(CPPFLAGS) $(CFLAGS) -DLRHOST_VULKAN -I$(VIDEODIR_PARALLEL)/parallel-rdp/vulkan-headers/include -w $< $(LIBRETRO_COMM_DIR)/features/features_cpu.o -o $@ $(LRHOST_LIBS) -lpthread -lm +# The same host with an OpenGL HW context (headless EGL; Mesa's llvmpipe +# with EGL_PLATFORM=surfaceless works), for the GL renderers: make lrhost_gl +lrhost_gl$(EXE_EXT): tools/lrhost.c tools/lrhost_gl.h $(LIBRETRO_COMM_DIR)/features/features_cpu.o + $(CC) $(CPPFLAGS) $(CFLAGS) -DLRHOST_GL -w $< $(LIBRETRO_COMM_DIR)/features/features_cpu.o -o $@ $(LRHOST_LIBS) -lEGL -lpthread -lm + angrylion_replay$(EXE_EXT): tools/angrylion_replay.c $(ANGRYLION_TOOL_OBJS) $(CC) $(CPPFLAGS) $(CFLAGS) -I$(VIDEODIR_ANGRYLION) -w $< $(ANGRYLION_TOOL_OBJS) -o $@ -lpthread -lm @@ -1665,13 +1670,22 @@ test_command_ring$(EXE_EXT): tools/test_command_ring.cpp \ test_gliden64_write_rdram$(EXE_EXT): tools/test_gliden64_write_rdram.cpp $(VIDEODIR_GLIDEN64)/src/BufferCopy/WriteToRDRAM.h $(CXX) -std=c++11 $(CHECK_CFLAGS) $(CHECK_SANITIZE) $< -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) +test_gliden64_deferred_readback$(EXE_EXT): tools/test_gliden64_deferred_readback.cpp $(VIDEODIR_GLIDEN64)/src/BufferCopy/DeferredReadback.h + $(CXX) -std=c++11 $(CHECK_CFLAGS) $(CHECK_SANITIZE) $< -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) + +test_glide64_fb_readback$(EXE_EXT): tools/test_glide64_fb_readback.c $(VIDEODIR_GLIDE)/Glide64/FBReadback.h Graphics/image_convert.h + $(CC) $(CHECK_CFLAGS) $(CHECK_SANITIZE) -I$(LIBRETRO_COMM_DIR)/include $< -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) + +test_gliden64_ring_segments$(EXE_EXT): tools/test_gliden64_ring_segments.cpp $(VIDEODIR_GLIDEN64)/src/Graphics/OpenGLContext/opengl_RingSegments.h + $(CXX) -std=c++11 $(CHECK_CFLAGS) $(CHECK_SANITIZE) -I$(VIDEODIR_GLIDEN64)/src $< -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) + CORE_CHECKS := test_rcp_bounds$(EXE_EXT) test_memory_map$(EXE_EXT) test_fb_write$(EXE_EXT) \ test_cp0_exceptions$(EXE_EXT) test_r4300_interp$(EXE_EXT) test_fpu_compare$(EXE_EXT) test_pi_status$(EXE_EXT) \ test_musyx_bounds$(EXE_EXT) test_musyx_aux$(EXE_EXT) test_alist_ops$(EXE_EXT) test_alist_abi1$(EXE_EXT) test_hle_dispatch$(EXE_EXT) angrylion_zboss_audio_test$(EXE_EXT) \ test_mouse_accum$(EXE_EXT) test_hle_dram_wrap$(EXE_EXT) test_pif_write$(EXE_EXT) \ test_re2_fill$(EXE_EXT) test_temporal_upscaler$(EXE_EXT) test_pipeline_records$(EXE_EXT) test_rdp_x_range$(EXE_EXT) \ test_command_ring$(EXE_EXT) test_gb_cart_banks$(EXE_EXT) test_dynarec_cache_pages$(EXE_EXT) \ - test_gliden64_write_rdram$(EXE_EXT) test_granite_util$(EXE_EXT) test_vk_alloc_fallback$(EXE_EXT) \ + test_gliden64_write_rdram$(EXE_EXT) test_gliden64_deferred_readback$(EXE_EXT) test_gliden64_ring_segments$(EXE_EXT) test_glide64_fb_readback$(EXE_EXT) test_granite_util$(EXE_EXT) test_vk_alloc_fallback$(EXE_EXT) \ test_vi_image_pool$(EXE_EXT) test_incoherent_pages$(EXE_EXT) ifeq ($(HAVE_PARALLEL_RSP), 1) diff --git a/gles2n64/src/GBI.h b/gles2n64/src/GBI.h index 342744dc..7beb8f5a 100644 --- a/gles2n64/src/GBI.h +++ b/gles2n64/src/GBI.h @@ -734,6 +734,9 @@ typedef struct MicrocodeInfo *current, *top, *bottom; } GBIInfo; +/* GLideN64 has a GBI of its own, a C++ object: under -fcommon this one + * would be merged into it and write over it */ +#define GBI gln64GBI extern GBIInfo GBI; uint32_t GBI_GetCurrentMicrocodeType(void); diff --git a/gles2rice/src/RSP_Parser.cpp b/gles2rice/src/RSP_Parser.cpp index 3ba8db47..b9988645 100644 --- a/gles2rice/src/RSP_Parser.cpp +++ b/gles2rice/src/RSP_Parser.cpp @@ -1271,8 +1271,9 @@ void RDP_DLParser_Process(void) status.gDlistCount++; - uint32_t start = *(gfx_info.DPC_START_REG); - uint32_t end = *(gfx_info.DPC_END_REG); + /* the DP's address registers are 24-bit, and RDRAM is 8 MB at most */ + uint32_t start = *(gfx_info.DPC_START_REG) & 0x007FFFF8; + uint32_t end = *(gfx_info.DPC_END_REG) & 0x007FFFF8; uint32_t *rdram_u32 = (uint32_t*)gfx_info.RDRAM; __RSP.PCi = 0; diff --git a/glide2gl/src/Glide64/FBReadback.h b/glide2gl/src/Glide64/FBReadback.h new file mode 100644 index 00000000..ececbdb1 --- /dev/null +++ b/glide2gl/src/Glide64/FBReadback.h @@ -0,0 +1,79 @@ +#ifndef GLIDE64_FB_READBACK_H +#define GLIDE64_FB_READBACK_H + +/* The colour image copied back to RDRAM from what the window shows: each + * pixel read back is turned into RDRAM's format as it is read, with no + * window-sized copy in between. The rows are RGBA8, stride bytes apart, + * the bottom row first, as glReadPixels leaves them. */ + +#include +#include +#include "../../../Graphics/image_convert.h" + +static INLINE void fb_store(uint8_t *dst, int size16, uint32_t width, + uint32_t x, uint32_t y, uint16_t c) +{ + if (size16) + ((uint16_t*)dst)[(x + y * width)^1] = c; + else + ((uint32_t*)dst)[x + y * width] = RGBA16toRGBA32(c); +} + +/* At 1:1: the w x h rows, as 565 and then the colour image's 5551, black + * kept transparent with read_alpha. */ +static INLINE void fb_readback_direct(const uint8_t *rows, uint32_t stride, + uint32_t w, uint32_t h, uint32_t width, int read_alpha, int size16, + uint8_t *dst) +{ + uint32_t y, x; + for (y = 0; y < h; y++) + { + const uint8_t *p = rows + (size_t)(h - 1 - y) * stride; + for (x = 0; x < w; x++, p += 4) + { + uint16_t c = (uint16_t)(((p[0] >> 3) << 11) | ((p[1] >> 2) << 5) | (p[2] >> 3)); + if (read_alpha && c == 0) {} + else + c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; + fb_store(dst, size16, width, x, y, c); + } + } +} + +/* Scaled: the window pixel each N64 pixel of [x_start, x_end) x [y_start, + * y_end) falls on, from the rect (rx, ry, rw, rh) read back. */ +static INLINE void fb_readback_sampled(const uint8_t *rows, uint32_t stride, + int32_t rx, int32_t ry, int32_t rw, int32_t rh, + float scale_x, float scale_y, float offset_x, float offset_y, + int x_start, int y_start, int x_end, int y_end, + uint32_t width, int read_alpha, int size16, uint8_t *dst) +{ + int y, x; + for (y = y_start; y < y_end; y++) + { + int sy = (int)(y * scale_y + offset_y) - ry; + const uint8_t *row; + if (sy < 0) + sy = 0; + else if (sy >= rh) + sy = rh - 1; + row = rows + (size_t)(rh - 1 - sy) * stride; + for (x = x_start; x < x_end; x++) + { + int sx = (int)(x*scale_x + offset_x) - rx; + const uint8_t *p; + uint16_t c; + if (sx < 0) + sx = 0; + else if (sx >= rw) + sx = rw - 1; + p = row + (size_t)sx * 4; + c = (uint16_t)(((p[0] >> 3) << 11) | ((p[1] >> 3) << 6) | ((p[2] >> 3) << 1) | 1); + if (read_alpha && c == 1) + c = 0; + fb_store(dst, size16, width, (uint32_t)x, (uint32_t)y, c); + } + } +} + +#endif diff --git a/glide2gl/src/Glide64/Framebuffer_glide64.c b/glide2gl/src/Glide64/Framebuffer_glide64.c index c2053c84..8183b9c8 100644 --- a/glide2gl/src/Glide64/Framebuffer_glide64.c +++ b/glide2gl/src/Glide64/Framebuffer_glide64.c @@ -51,6 +51,7 @@ #include "../../../Graphics/RDP/gDP_state.h" #include "Framebuffer_glide64.h" +#include "FBReadback.h" #include "TexCache.h" #include "../Glitch64/glide.h" #include "GlideExtensions.h" @@ -1030,124 +1031,67 @@ void CopyFrameBuffer(int32_t buffer) if (rdp.scale_x < 1.1f) { - uint16_t * ptr_src = (uint16_t*)glide64_frameBuffer; - if (grLfbReadRegion(buffer, - (uint32_t)rdp.offset_x, - (uint32_t)rdp.offset_y,//rdp.ci_upper_bound, - width, - height, - width<<1, - ptr_src)) - { - uint32_t y, x; + /* the rect read back, each pixel turned into RDRAM's format as it + * is read: no window-sized copy in between */ + int32_t rx = (int32_t)(uint32_t)rdp.offset_x; + int32_t ry = (int32_t)(uint32_t)rdp.offset_y; + int32_t rw = (int32_t)width; + int32_t rh = (int32_t)height; + uint32_t stride; + const uint8_t *rows = grLfbReadRect(&rx, &ry, &rw, &rh, &stride); - if (g_gdp.fb_size == G_IM_SIZ_16b) - { - uint16_t *ptr_dst = (uint16_t*)(gfx_info.RDRAM + gDP.colorImage.address); - - for (y = 0; y < height; y++) - { - for (x = 0; x < width; x++) - { - uint16_t c = ptr_src[x + y * width]; - if ((settings.frame_buffer & fb_read_alpha) && c <= 0) {} - else - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - ptr_dst[(x + y * width)^1] = c; - } - } - } - else - { - uint32_t *ptr_dst = (uint32_t*)(gfx_info.RDRAM + gDP.colorImage.address); - for (y = 0; y < height; y++) - { - for (x = 0; x < width; x++) - { - uint16_t c = ptr_src[x + y * width]; - if ((settings.frame_buffer & fb_read_alpha) && c <= 0) {} - else - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - ptr_dst[x + y * width] = RGBA16toRGBA32(c); - } - } - } - } + if (rows) + fb_readback_direct(rows, stride, + (uint32_t)rw < width ? (uint32_t)rw : width, + (uint32_t)rh < height ? (uint32_t)rh : height, + width, settings.frame_buffer & fb_read_alpha, + g_gdp.fb_size == G_IM_SIZ_16b, + gfx_info.RDRAM + gDP.colorImage.address); } else { - GrLfbInfo_t info; float scale_x = (settings.scr_res_x - rdp.offset_x*2.0f) / MAX(width, rdp.vi_width); float scale_y = (settings.scr_res_y - rdp.offset_y*2.0f) / MAX(height, rdp.vi_height); + int x_start = 0; + int y_start = 0; + int x_end = width; + int y_end = height; + int read_alpha = settings.frame_buffer & fb_read_alpha; FRDP("width: %d, height: %d, ul_y: %d, lr_y: %d, scale_x: %f, scale_y: %f, ci_width: %d, ci_height: %d\n",width, height, rdp.ci_upper_bound, rdp.ci_lower_bound, scale_x, scale_y, gDP.colorImage.width, gDP.colorImage.height); - info.size = sizeof(GrLfbInfo_t); - if (grLfbLock (GR_LFB_READ_ONLY, - buffer, - GR_LFBWRITEMODE_565, - GR_ORIGIN_UPPER_LEFT, - FXFALSE, - &info)) + if ((settings.hacks&hack_PMario) && rdp.ci_count > 0 && rdp.frame_buffers[rdp.ci_count-1].status != CI_AUX) + read_alpha = false; + + if (settings.hacks&hack_BAR) { - int x_start = 0; - int y_start = 0; - int x_end = width; - int y_end = height; - uint32_t stride = info.strideInBytes>>1; - int read_alpha = settings.frame_buffer & fb_read_alpha; + x_start = 80; + y_start = 24; + x_end = 240; + y_end = 86; + } - if ((settings.hacks&hack_PMario) && rdp.ci_count > 0 && rdp.frame_buffers[rdp.ci_count-1].status != CI_AUX) - read_alpha = false; + if (x_end > x_start && y_end > y_start) + { + /* Only the window rows and columns the N64 pixels sample are + * read back, and only those pixels converted. */ + int32_t sx0 = (int)(x_start*scale_x + rdp.offset_x); + int32_t sy0 = (int)(y_start * scale_y + rdp.offset_y); + int32_t rx = sx0, ry = sy0; + int32_t rw = (int)((x_end - 1)*scale_x + rdp.offset_x) - sx0 + 1; + int32_t rh = (int)((y_end - 1) * scale_y + rdp.offset_y) - sy0 + 1; + uint32_t stride; + const uint8_t *rows = grLfbReadRect(&rx, &ry, &rw, &rh, &stride); - if (settings.hacks&hack_BAR) + if (rows) { - x_start = 80; - y_start = 24; - x_end = 240; - y_end = 86; + fb_readback_sampled(rows, stride, rx, ry, rw, rh, + scale_x, scale_y, rdp.offset_x, rdp.offset_y, + x_start, y_start, x_end, y_end, width, read_alpha, + g_gdp.fb_size <= G_IM_SIZ_16b, + gfx_info.RDRAM + gDP.colorImage.address); + LRDP("Framebuffer copy complete.\n"); } - - if (g_gdp.fb_size <= G_IM_SIZ_16b) - { - int y, x; - uint16_t *ptr_src = (uint16_t*)info.lfbPtr; - uint16_t *ptr_dst = (uint16_t*)(gfx_info.RDRAM + gDP.colorImage.address); - - for (y = y_start; y < y_end; y++) - { - for (x = x_start; x < x_end; x++) - { - uint16_t c = ptr_src[(int)(x*scale_x + rdp.offset_x) + (int)(y * scale_y + rdp.offset_y) * stride]; - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - if (read_alpha && c == 1) - c = 0; - ptr_dst[(x + y * width)^1] = c; - } - } - } - else - { - int y, x; - uint16_t *ptr_src = (uint16_t*)info.lfbPtr; - uint32_t *ptr_dst = (uint32_t*)(gfx_info.RDRAM + gDP.colorImage.address); - - for (y = y_start; y < y_end; y++) - { - for (x = x_start; x < x_end; x++) - { - uint16_t c = ptr_src[(int)(x*scale_x + rdp.offset_x) + (int)(y * scale_y + rdp.offset_y) * stride]; - c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; - if (read_alpha && c == 1) - c = 0; - ptr_dst[x + y * width] = RGBA16toRGBA32(c); - } - } - } - - // Unlock the backbuffer - grLfbUnlock (GR_LFB_READ_ONLY, buffer); - LRDP("LfbLock. Framebuffer copy complete.\n"); } } } diff --git a/glide2gl/src/Glide64/ucode06.h b/glide2gl/src/Glide64/ucode06.h index c3f86ae3..9d12cfcf 100644 --- a/glide2gl/src/Glide64/ucode06.h +++ b/glide2gl/src/Glide64/ucode06.h @@ -61,41 +61,21 @@ typedef struct DRAWIMAGE_t float scaleY; } DRAWIMAGE; +/* The depth image is not drawn, only the depth buffer cleared for it. */ static void DrawDepthImage (const DRAWIMAGE *d) { - float scale_x_src, scale_y_src, scale_x_dst, scale_y_dst; - uint16_t *src, *dst; - int32_t x, y, src_width, src_height, dst_width, dst_height; - if (!fb_depth_render_enabled || d->imageH > d->imageW) return; - scale_x_dst = rdp.scale_x; - scale_y_dst = rdp.scale_y; - scale_x_src = 1.0f/rdp.scale_x; - scale_y_src = 1.0f/rdp.scale_y; - src_width = d->imageW; - src_height = d->imageH; - dst_width = MIN((int)(src_width*scale_x_dst), (int)settings.scr_res_x); - dst_height = MIN((int)(src_height*scale_y_dst), (int)settings.scr_res_y); - src = (uint16_t*)(gfx_info.RDRAM+d->imagePtr); - dst = (uint16_t*)malloc(dst_width * dst_height * sizeof(uint16_t)); - - for (y = 0; y < dst_height; y++) - { - for (x = 0; x < dst_width; x++) - dst[x + y * dst_width] = src[((int)(x * scale_x_src) + (int)(y*scale_y_src) * src_width)^1]; - } grLfbWriteRegion(GR_BUFFER_AUXBUFFER, 0, 0, GR_LFB_SRC_FMT_ZA16, - dst_width, - dst_height, + 0, + 0, FXFALSE, - dst_width<<1, - dst); - free(dst); + 0, + NULL); } static void DrawImage (DRAWIMAGE *d) diff --git a/glide2gl/src/Glitch64/glide.h b/glide2gl/src/Glitch64/glide.h index 1eb89bb1..1b6d084e 100644 --- a/glide2gl/src/Glitch64/glide.h +++ b/glide2gl/src/Glitch64/glide.h @@ -520,11 +520,11 @@ grLfbWriteRegion( int32_t dst_buffer, int32_t pixelPipeline, int32_t src_stride, void *src_data ); - int32_t -grLfbReadRegion( int32_t src_buffer, - uint32_t src_x, uint32_t src_y, - uint32_t src_width, uint32_t src_height, - uint32_t dst_stride, void *dst_data ); +/* The window's pixels in [x, x + w) x [y, y + h), upper-left origin, + * as RGBA8 rows *stride bytes apart, the bottom row first; the rect is + * clipped to the window (*w, *h). Valid until the next read. */ +const uint8_t *grLfbReadRect(int32_t *x, int32_t *y, int32_t *w, int32_t *h, uint32_t *stride); + #define guFogGenerateLinear(nearZ, farZ) \ { \ diff --git a/glide2gl/src/Glitch64/glitchmain.c b/glide2gl/src/Glitch64/glitchmain.c index e60fa383..39b5910d 100644 --- a/glide2gl/src/Glitch64/glitchmain.c +++ b/glide2gl/src/Glitch64/glitchmain.c @@ -163,27 +163,22 @@ int32_t grLfbLock( int32_t type, int32_t buffer, int32_t writeMode, return FXTRUE; } -int32_t grLfbReadRegion( int32_t src_buffer, - uint32_t src_x, uint32_t src_y, - uint32_t src_width, uint32_t src_height, - uint32_t dst_stride, void *dst_data ) +const uint8_t *grLfbReadRect(int32_t *x, int32_t *y, int32_t *w, int32_t *h, uint32_t *stride) { - unsigned int i,j; + int32_t x0 = *x < 0 ? 0 : *x; + int32_t y0 = *y < 0 ? 0 : *y; + int32_t x1 = *x + *w > width ? width : *x + *w; + int32_t y1 = *y + *h > height ? height : *y + *h; - glReadPixels(src_x, height-src_y-src_height, src_width, src_height, GL_RGBA, GL_UNSIGNED_BYTE, buf); - - for (j=0; j> 3) << 11) | - ((buf[(src_height-j-1)*src_width*4+i*4+1] >> 2) << 5) | - (buf[(src_height-j-1)*src_width*4+i*4+2] >> 3); - } - } - - return FXTRUE; + if (x1 <= x0 || y1 <= y0) + return NULL; + *x = x0; + *y = y0; + *w = x1 - x0; + *h = y1 - y0; + *stride = (uint32_t)*w * 4; + glReadPixels(x0, height - y1, *w, *h, GL_RGBA, GL_UNSIGNED_BYTE, buf); + return buf; } int32_t @@ -199,12 +194,7 @@ grLfbWriteRegion( int32_t dst_buffer, if(dst_buffer == GR_BUFFER_AUXBUFFER) { - for (j=0; j -#include - -#include "ColorBufferToRDRAM.h" -#include "WriteToRDRAM.h" - -#include -#include -#include -#include -#include -#include "Log.h" -#include "MemoryStatus.h" - -/* -#include "ColorBufferToRDRAM_GL.h" -#include "ColorBufferToRDRAM_BufferStorageExt.h" -#elif defined(OS_ANDROID) && defined (GLES2) -#include "ColorBufferToRDRAM_GL.h" -#include "ColorBufferToRDRAM_GLES.h" -#else -#include "ColorBufferToRDRAMStub.h" -#endif -*/ - -#include -#include -#include -#include - -using namespace graphics; - -ColorBufferToRDRAM::ColorBufferToRDRAM() - : m_FBO(0) - , m_pTexture(nullptr) - , m_pCurFrameBuffer(nullptr) - , m_frameCount(-1) - , m_startAddress(-1) - , m_lastBufferWidth(-1) -{ - m_allowedRealWidths[0] = 320; - m_allowedRealWidths[1] = 480; - m_allowedRealWidths[2] = 640; -} - -ColorBufferToRDRAM::~ColorBufferToRDRAM() -{ -} - -void ColorBufferToRDRAM::init() -{ - m_FBO = gfxContext.createFramebuffer(); -} - -void ColorBufferToRDRAM::destroy() { - _destroyFBTexure(); - - if (m_FBO.isNotNull()) { - gfxContext.deleteFramebuffer(m_FBO); - m_FBO.reset(); - } -} - -void ColorBufferToRDRAM::_initFBTexture(void) -{ - const FramebufferTextureFormats & fbTexFormat = gfxContext.getFramebufferTextureFormats(); - - m_pTexture = textureCache().addFrameBufferTexture(textureTarget::TEXTURE_2D); - m_pTexture->format = G_IM_FMT_RGBA; - m_pTexture->size = 2; - m_pTexture->clampS = 1; - m_pTexture->clampT = 1; - m_pTexture->frameBufferTexture = CachedTexture::fbOneSample; - m_pTexture->maskS = 0; - m_pTexture->maskT = 0; - m_pTexture->mirrorS = 0; - m_pTexture->mirrorT = 0; - //The actual VI width is not used for texture width because most texture widths - //cause slowdowns in the glReadPixels call, at least on Android - m_pTexture->realWidth = m_lastBufferWidth; - m_pTexture->realHeight = VI_GetMaxBufferHeight(m_lastBufferWidth); +#include +#include + +#include "ColorBufferToRDRAM.h" +#include "WriteToRDRAM.h" + +#include +#include +#include +#include +#include +#include "Log.h" +#include "MemoryStatus.h" + +/* +#include "ColorBufferToRDRAM_GL.h" +#include "ColorBufferToRDRAM_BufferStorageExt.h" +#elif defined(OS_ANDROID) && defined (GLES2) +#include "ColorBufferToRDRAM_GL.h" +#include "ColorBufferToRDRAM_GLES.h" +#else +#include "ColorBufferToRDRAMStub.h" +#endif +*/ + +#include +#include +#include +#include + +using namespace graphics; + +ColorBufferToRDRAM::ColorBufferToRDRAM() + : m_FBO(0) + , m_pTexture(nullptr) + , m_pCurFrameBuffer(nullptr) + , m_frameCount(-1) + , m_startAddress(-1) + , m_lastBufferWidth(-1) +{ + m_allowedRealWidths[0] = 320; + m_allowedRealWidths[1] = 480; + m_allowedRealWidths[2] = 640; +} + +ColorBufferToRDRAM::~ColorBufferToRDRAM() +{ +} + +void ColorBufferToRDRAM::init() +{ + m_FBO = gfxContext.createFramebuffer(); +} + +void ColorBufferToRDRAM::destroy() { + _destroyFBTexure(); + + if (m_FBO.isNotNull()) { + gfxContext.deleteFramebuffer(m_FBO); + m_FBO.reset(); + } +} + +void ColorBufferToRDRAM::_initFBTexture(void) +{ + const FramebufferTextureFormats & fbTexFormat = gfxContext.getFramebufferTextureFormats(); + + m_pTexture = textureCache().addFrameBufferTexture(textureTarget::TEXTURE_2D); + m_pTexture->format = G_IM_FMT_RGBA; + m_pTexture->size = 2; + m_pTexture->clampS = 1; + m_pTexture->clampT = 1; + m_pTexture->frameBufferTexture = CachedTexture::fbOneSample; + m_pTexture->maskS = 0; + m_pTexture->maskT = 0; + m_pTexture->mirrorS = 0; + m_pTexture->mirrorT = 0; + //The actual VI width is not used for texture width because most texture widths + //cause slowdowns in the glReadPixels call, at least on Android + m_pTexture->realWidth = m_lastBufferWidth; + m_pTexture->realHeight = VI_GetMaxBufferHeight(m_lastBufferWidth); /* The color-buffer readers size their glReadPixels rows and source * stride from width, not realWidth. addFrameBufferTexture recycles * CachedTexture objects, so without this the readback runs with @@ -88,297 +88,341 @@ void ColorBufferToRDRAM::_initFBTexture(void) * DepthBufferToRDRAM already initializes its textures this way. */ m_pTexture->width = m_pTexture->realWidth; m_pTexture->height = m_pTexture->realHeight; - m_pTexture->textureBytes = m_pTexture->realWidth * m_pTexture->realHeight * fbTexFormat.colorFormatBytes; - - { - Context::InitTextureParams params; - params.handle = m_pTexture->name; - params.width = m_pTexture->realWidth; - params.height = m_pTexture->realHeight; - params.internalFormat = fbTexFormat.colorInternalFormat; - params.format = fbTexFormat.colorFormat; - params.dataType = fbTexFormat.colorType; - gfxContext.init2DTexture(params); - } - { - Context::TexParameters params; - params.handle = m_pTexture->name; - params.target = textureTarget::TEXTURE_2D; - params.textureUnitIndex = textureIndices::Tex[0]; - params.minFilter = textureParameters::FILTER_LINEAR; - params.magFilter = textureParameters::FILTER_LINEAR; - gfxContext.setTextureParameters(params); - } - { - Context::FrameBufferRenderTarget bufTarget; - bufTarget.bufferHandle = ObjectHandle(m_FBO); - bufTarget.bufferTarget = bufferTarget::DRAW_FRAMEBUFFER; - bufTarget.attachment = bufferAttachment::COLOR_ATTACHMENT0; - bufTarget.textureTarget = textureTarget::TEXTURE_2D; - bufTarget.textureHandle = m_pTexture->name; - gfxContext.addFrameBufferRenderTarget(bufTarget); - } - - // check if everything is OK - assert(!gfxContext.isFramebufferError()); - - gfxContext.bindFramebuffer(graphics::bufferTarget::DRAW_FRAMEBUFFER, graphics::ObjectHandle::defaultFramebuffer); - - m_bufferReader.reset(gfxContext.createColorBufferReader(m_pTexture)); -} - -void ColorBufferToRDRAM::_destroyFBTexure(void) -{ - m_bufferReader.reset(); - - if (m_pTexture != nullptr) { - textureCache().removeFrameBufferTexture(m_pTexture); - m_pTexture = nullptr; - } -} - -bool ColorBufferToRDRAM::_prepareCopy(u32& _startAddress) -{ - if (VI.width == 0 || frameBufferList().getCurrent() == nullptr) - return false; - - FrameBuffer * pBuffer = frameBufferList().findBuffer(_startAddress); - if (pBuffer == nullptr || pBuffer->m_isOBScreen) - return false; - - DisplayWindow & wnd = dwnd(); - const u32 curFrame = wnd.getBuffersSwapCount(); - - _startAddress &= ~0xfff; - if (_startAddress < pBuffer->m_startAddress) - _startAddress = pBuffer->m_startAddress; - - if (m_frameCount == curFrame && pBuffer == m_pCurFrameBuffer && m_startAddress != _startAddress) - return true; - - const u32 numPixels = pBuffer->m_width * pBuffer->m_height; - if (numPixels == 0) - return false; - - const u32 stride = pBuffer->m_width << pBuffer->m_size >> 1; - const u32 bufferHeight = cutHeight(_startAddress, pBuffer->m_height, stride); - if (bufferHeight == 0) - return false; - - if(m_pTexture == nullptr || - m_pTexture->realWidth != _getRealWidth(pBuffer->m_width) || - m_pTexture->realHeight != VI_GetMaxBufferHeight(_getRealWidth(pBuffer->m_width))) - { - _destroyFBTexure(); - - m_lastBufferWidth = _getRealWidth(pBuffer->m_width); - _initFBTexture(); - } - - m_pCurFrameBuffer = pBuffer; - - if ((config.generalEmulation.hacks & hack_subscreen) != 0 && m_pCurFrameBuffer->m_width == VI.width) { - copyWhiteToRDRAM(m_pCurFrameBuffer); - return false; - } - - ObjectHandle readBuffer; - - if (config.video.multisampling != 0) { - m_pCurFrameBuffer->resolveMultisampledTexture(); - readBuffer = m_pCurFrameBuffer->m_resolveFBO; - } else { - readBuffer = m_pCurFrameBuffer->m_FBO; - } - - if (m_pCurFrameBuffer->m_scale != 1.0f) { - u32 x0 = 0; - u32 width; - if (config.frameBufferEmulation.nativeResFactor == 0) { - const u32 screenWidth = wnd.getWidth(); - width = screenWidth; - if (wnd.isAdjustScreen()) { - width = static_cast(screenWidth*wnd.getAdjustScale()); - x0 = (screenWidth - width) / 2; - } - } else { - width = m_pCurFrameBuffer->m_pTexture->realWidth; - } - u32 height = (u32)(bufferHeight * m_pCurFrameBuffer->m_scale); - - CachedTexture * pInputTexture = m_pCurFrameBuffer->m_pTexture; - GraphicsDrawer::BlitOrCopyRectParams blitParams; - blitParams.srcX0 = x0; - blitParams.srcY0 = 0; - blitParams.srcX1 = x0 + width; - blitParams.srcY1 = height; - blitParams.srcWidth = pInputTexture->realWidth; - blitParams.srcHeight = pInputTexture->realHeight; - blitParams.dstX0 = 0; - blitParams.dstY0 = 0; - blitParams.dstX1 = m_pCurFrameBuffer->m_width; - blitParams.dstY1 = bufferHeight; - blitParams.dstWidth = m_pTexture->realWidth; - blitParams.dstHeight = m_pTexture->realHeight; - blitParams.filter = textureParameters::FILTER_NEAREST; - blitParams.tex[0] = pInputTexture; - blitParams.combiner = CombinerInfo::get().getTexrectCopyProgram(); - blitParams.readBuffer = readBuffer; - blitParams.drawBuffer = m_FBO; - blitParams.mask = blitMask::COLOR_BUFFER; - wnd.getDrawer().blitOrCopyTexturedRect(blitParams); - - gfxContext.bindFramebuffer(bufferTarget::READ_FRAMEBUFFER, m_FBO); - } else { - gfxContext.bindFramebuffer(bufferTarget::READ_FRAMEBUFFER, readBuffer); - } - - m_frameCount = curFrame; - m_startAddress = _startAddress; - return true; -} - -u8 ColorBufferToRDRAM::_RGBAtoR8(u8 _c) { - return _c; -} - -u16 ColorBufferToRDRAM::_RGBAtoRGBA16(u32 _c) { - RGBA c; - c.raw = _c; - return ((c.r >> 3) << 11) | ((c.g >> 3) << 6) | ((c.b >> 3) << 1) | (c.a == 0 ? 0 : 1); -} - -u32 ColorBufferToRDRAM::_RGBAtoRGBA32(u32 _c) { - RGBA c; - c.raw = _c; - return (c.r << 24) | (c.g << 16) | (c.b << 8) | c.a; -} - -void ColorBufferToRDRAM::_copy(u32 _startAddress, u32 _endAddress, bool _sync) -{ - const u32 stride = m_pCurFrameBuffer->m_width << m_pCurFrameBuffer->m_size >> 1; - const u32 max_height = std::min((u32)VI_GetMaxBufferHeight(m_pCurFrameBuffer->m_width), cutHeight(_startAddress, m_pCurFrameBuffer->m_height, stride)); - - u32 numPixels = (_endAddress - _startAddress) >> (m_pCurFrameBuffer->m_size - 1); - if (numPixels / m_pCurFrameBuffer->m_width > max_height) { - _endAddress = _startAddress + (max_height * stride); - numPixels = (_endAddress - _startAddress) >> (m_pCurFrameBuffer->m_size - 1); - } - - const u32 width = m_pCurFrameBuffer->m_width; - const s32 x0 = 0; - const s32 y0 = (_startAddress - m_pCurFrameBuffer->m_startAddress) / stride; - const u32 y1 = (_endAddress - m_pCurFrameBuffer->m_startAddress) / stride; - u32 height = std::min(max_height, 1u + y1 - y0); - u32 srcStride = width; - - const u8* pPixels = m_bufferReader->readPixels(x0, y0, width, height, m_pCurFrameBuffer->m_size, _sync, srcStride); - frameBufferList().setCurrentDrawBuffer(); - if (pPixels == nullptr) - return; - if (height == 0) { - m_bufferReader->cleanUp(); - return; - } - - if (m_pCurFrameBuffer->m_size == G_IM_SIZ_32b) { - const u32 *ptr_src = (const u32*)pPixels; - u32 *ptr_dst = (u32*)(RDRAM + _startAddress); - - if (!FBInfo::fbInfo.isSupported() && config.frameBufferEmulation.copyFromRDRAM != 0) { - memset(ptr_dst, 0, numPixels * 4); - } - - writeToRdram(ptr_src, srcStride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoRGBA32, 0, 0, width, height, numPixels, _startAddress, m_pCurFrameBuffer->m_startAddress, m_pCurFrameBuffer->m_size); - } else if (m_pCurFrameBuffer->m_size == G_IM_SIZ_16b) { - const u32 *ptr_src = (const u32*)pPixels; - u16 *ptr_dst = (u16*)(RDRAM + _startAddress); - - if (!FBInfo::fbInfo.isSupported() && config.frameBufferEmulation.copyFromRDRAM != 0) { - memset(ptr_dst, 0, numPixels * 2); - } - - writeToRdram(ptr_src, srcStride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoRGBA16, 0, 1, width, height, numPixels, _startAddress, m_pCurFrameBuffer->m_startAddress, m_pCurFrameBuffer->m_size); - } else if (m_pCurFrameBuffer->m_size == G_IM_SIZ_8b) { - const u8 *ptr_src = (const u8*)pPixels; - u8 *ptr_dst = RDRAM + _startAddress; - - if (!FBInfo::fbInfo.isSupported() && config.frameBufferEmulation.copyFromRDRAM != 0) { - memset(ptr_dst, 0, numPixels); - } - - writeToRdram(ptr_src, srcStride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoR8, 0, 3, width, height, numPixels, _startAddress, m_pCurFrameBuffer->m_startAddress, m_pCurFrameBuffer->m_size); - } - - m_pCurFrameBuffer->m_copiedToRdram = true; - m_pCurFrameBuffer->copyRdram(); - m_pCurFrameBuffer->m_cleared = false; - - m_bufferReader->cleanUp(); - - gDP.changed |= CHANGED_SCISSOR; -} - -u32 ColorBufferToRDRAM::_getRealWidth(u32 _viWidth) -{ - u32 index = 0; - const u32 maxIndex = static_cast(m_allowedRealWidths.size()) - 1; - while (index < maxIndex && _viWidth > m_allowedRealWidths[index]) - { - ++index; - } - - return m_allowedRealWidths[index]; -} - -void ColorBufferToRDRAM::copyToRDRAM(u32 _address, bool _sync) -{ - if (!isMemoryWritable(RDRAM + _address, gDP.colorImage.width << gDP.colorImage.size >> 1)) - return; - if (!_prepareCopy(_address)) - return; - const u32 numBytes = (m_pCurFrameBuffer->m_width*m_pCurFrameBuffer->m_height) << m_pCurFrameBuffer->m_size >> 1; - _copy(m_pCurFrameBuffer->m_startAddress, m_pCurFrameBuffer->m_startAddress + numBytes, _sync); -} - -void ColorBufferToRDRAM::copyChunkToRDRAM(u32 _startAddress) -{ - const u32 endAddress = (_startAddress & ~0xfff) + 0x1000; - - if (!isMemoryWritable(RDRAM + _startAddress, endAddress - _startAddress)) - return; - if (!_prepareCopy(_startAddress)) - return; - _copy(_startAddress, endAddress, true); -} - - -ColorBufferToRDRAM & ColorBufferToRDRAM::get() -{ - static ColorBufferToRDRAM cbCopy; - return cbCopy; -} - -void copyWhiteToRDRAM(FrameBuffer * _pBuffer) -{ - if (_pBuffer->m_size == G_IM_SIZ_32b) { - u32 *ptr_dst = (u32*)(RDRAM + _pBuffer->m_startAddress); - - for (u32 y = 0; y < VI.height; ++y) { - for (u32 x = 0; x < VI.width; ++x) - ptr_dst[x + y*VI.width] = 0xFFFFFFFF; - } - } else { - u16 *ptr_dst = (u16*)(RDRAM + _pBuffer->m_startAddress); - - for (u32 y = 0; y < VI.height; ++y) { - for (u32 x = 0; x < VI.width; ++x) { - ptr_dst[(x + y*VI.width) ^ 1] = 0xFFFF; - } - } - } - _pBuffer->m_copiedToRdram = true; - _pBuffer->copyRdram(); - - _pBuffer->m_cleared = false; -} + m_pTexture->textureBytes = m_pTexture->realWidth * m_pTexture->realHeight * fbTexFormat.colorFormatBytes; + + { + Context::InitTextureParams params; + params.handle = m_pTexture->name; + params.width = m_pTexture->realWidth; + params.height = m_pTexture->realHeight; + params.internalFormat = fbTexFormat.colorInternalFormat; + params.format = fbTexFormat.colorFormat; + params.dataType = fbTexFormat.colorType; + gfxContext.init2DTexture(params); + } + { + Context::TexParameters params; + params.handle = m_pTexture->name; + params.target = textureTarget::TEXTURE_2D; + params.textureUnitIndex = textureIndices::Tex[0]; + params.minFilter = textureParameters::FILTER_LINEAR; + params.magFilter = textureParameters::FILTER_LINEAR; + gfxContext.setTextureParameters(params); + } + { + Context::FrameBufferRenderTarget bufTarget; + bufTarget.bufferHandle = ObjectHandle(m_FBO); + bufTarget.bufferTarget = bufferTarget::DRAW_FRAMEBUFFER; + bufTarget.attachment = bufferAttachment::COLOR_ATTACHMENT0; + bufTarget.textureTarget = textureTarget::TEXTURE_2D; + bufTarget.textureHandle = m_pTexture->name; + gfxContext.addFrameBufferRenderTarget(bufTarget); + } + + // check if everything is OK + assert(!gfxContext.isFramebufferError()); + + gfxContext.bindFramebuffer(graphics::bufferTarget::DRAW_FRAMEBUFFER, graphics::ObjectHandle::defaultFramebuffer); + + m_bufferReader.reset(gfxContext.createColorBufferReader(m_pTexture)); + m_readback.reset(m_bufferReader->numSlots()); +} + +void ColorBufferToRDRAM::_destroyFBTexure(void) +{ + /* reads still in flight die with the reader's buffers */ + m_bufferReader.reset(); + m_readback.reset(1); + + if (m_pTexture != nullptr) { + textureCache().removeFrameBufferTexture(m_pTexture); + m_pTexture = nullptr; + } +} + +bool ColorBufferToRDRAM::_prepareCopy(u32& _startAddress) +{ + if (VI.width == 0 || frameBufferList().getCurrent() == nullptr) + return false; + + FrameBuffer * pBuffer = frameBufferList().findBuffer(_startAddress); + if (pBuffer == nullptr || pBuffer->m_isOBScreen) + return false; + + DisplayWindow & wnd = dwnd(); + const u32 curFrame = wnd.getBuffersSwapCount(); + + _startAddress &= ~0xfff; + if (_startAddress < pBuffer->m_startAddress) + _startAddress = pBuffer->m_startAddress; + + if (m_frameCount == curFrame && pBuffer == m_pCurFrameBuffer && m_startAddress != _startAddress) + return true; + + const u32 numPixels = pBuffer->m_width * pBuffer->m_height; + if (numPixels == 0) + return false; + + const u32 stride = pBuffer->m_width << pBuffer->m_size >> 1; + const u32 bufferHeight = cutHeight(_startAddress, pBuffer->m_height, stride); + if (bufferHeight == 0) + return false; + + if(m_pTexture == nullptr || + m_pTexture->realWidth != _getRealWidth(pBuffer->m_width) || + m_pTexture->realHeight != VI_GetMaxBufferHeight(_getRealWidth(pBuffer->m_width))) + { + _destroyFBTexure(); + + m_lastBufferWidth = _getRealWidth(pBuffer->m_width); + _initFBTexture(); + } + + m_pCurFrameBuffer = pBuffer; + + if ((config.generalEmulation.hacks & hack_subscreen) != 0 && m_pCurFrameBuffer->m_width == VI.width) { + copyWhiteToRDRAM(m_pCurFrameBuffer); + return false; + } + + ObjectHandle readBuffer; + + if (config.video.multisampling != 0) { + m_pCurFrameBuffer->resolveMultisampledTexture(); + readBuffer = m_pCurFrameBuffer->m_resolveFBO; + } else { + readBuffer = m_pCurFrameBuffer->m_FBO; + } + + if (m_pCurFrameBuffer->m_scale != 1.0f) { + u32 x0 = 0; + u32 width; + if (config.frameBufferEmulation.nativeResFactor == 0) { + const u32 screenWidth = wnd.getWidth(); + width = screenWidth; + if (wnd.isAdjustScreen()) { + width = static_cast(screenWidth*wnd.getAdjustScale()); + x0 = (screenWidth - width) / 2; + } + } else { + width = m_pCurFrameBuffer->m_pTexture->realWidth; + } + u32 height = (u32)(bufferHeight * m_pCurFrameBuffer->m_scale); + + CachedTexture * pInputTexture = m_pCurFrameBuffer->m_pTexture; + GraphicsDrawer::BlitOrCopyRectParams blitParams; + blitParams.srcX0 = x0; + blitParams.srcY0 = 0; + blitParams.srcX1 = x0 + width; + blitParams.srcY1 = height; + blitParams.srcWidth = pInputTexture->realWidth; + blitParams.srcHeight = pInputTexture->realHeight; + blitParams.dstX0 = 0; + blitParams.dstY0 = 0; + blitParams.dstX1 = m_pCurFrameBuffer->m_width; + blitParams.dstY1 = bufferHeight; + blitParams.dstWidth = m_pTexture->realWidth; + blitParams.dstHeight = m_pTexture->realHeight; + blitParams.filter = textureParameters::FILTER_NEAREST; + blitParams.tex[0] = pInputTexture; + blitParams.combiner = CombinerInfo::get().getTexrectCopyProgram(); + blitParams.readBuffer = readBuffer; + blitParams.drawBuffer = m_FBO; + blitParams.mask = blitMask::COLOR_BUFFER; + wnd.getDrawer().blitOrCopyTexturedRect(blitParams); + + gfxContext.bindFramebuffer(bufferTarget::READ_FRAMEBUFFER, m_FBO); + } else { + gfxContext.bindFramebuffer(bufferTarget::READ_FRAMEBUFFER, readBuffer); + } + + m_frameCount = curFrame; + m_startAddress = _startAddress; + return true; +} + +u8 ColorBufferToRDRAM::_RGBAtoR8(u8 _c) { + return _c; +} + +u16 ColorBufferToRDRAM::_RGBAtoRGBA16(u32 _c) { + RGBA c; + c.raw = _c; + return ((c.r >> 3) << 11) | ((c.g >> 3) << 6) | ((c.b >> 3) << 1) | (c.a == 0 ? 0 : 1); +} + +u32 ColorBufferToRDRAM::_RGBAtoRGBA32(u32 _c) { + RGBA c; + c.raw = _c; + return (c.r << 24) | (c.g << 16) | (c.b << 8) | c.a; +} + +void ColorBufferToRDRAM::_copy(u32 _startAddress, u32 _endAddress, bool _sync) +{ + const u32 stride = m_pCurFrameBuffer->m_width << m_pCurFrameBuffer->m_size >> 1; + const u32 max_height = std::min((u32)VI_GetMaxBufferHeight(m_pCurFrameBuffer->m_width), cutHeight(_startAddress, m_pCurFrameBuffer->m_height, stride)); + + u32 numPixels = (_endAddress - _startAddress) >> (m_pCurFrameBuffer->m_size - 1); + if (numPixels / m_pCurFrameBuffer->m_width > max_height) { + _endAddress = _startAddress + (max_height * stride); + numPixels = (_endAddress - _startAddress) >> (m_pCurFrameBuffer->m_size - 1); + } + + const u32 width = m_pCurFrameBuffer->m_width; + const s32 x0 = 0; + const s32 y0 = (_startAddress - m_pCurFrameBuffer->m_startAddress) / stride; + const u32 y1 = (_endAddress - m_pCurFrameBuffer->m_startAddress) / stride; + u32 height = std::min(max_height, 1u + y1 - y0); + + ColorCopyGeometry g; + g.startAddress = _startAddress; + g.numPixels = numPixels; + g.bufferAddress = m_pCurFrameBuffer->m_startAddress; + g.width = width; + g.size = m_pCurFrameBuffer->m_size; + + if (m_bufferReader->numSlots() == 0) { + u32 srcStride = width; + const u8* pPixels = m_bufferReader->readPixels(x0, y0, width, height, g.size, _sync, srcStride); + frameBufferList().setCurrentDrawBuffer(); + if (pPixels == nullptr) + return; + g.height = height; + _write(pPixels, srcStride, g); + m_bufferReader->cleanUp(); + gDP.changed |= CHANGED_SCISSOR; + return; + } + + g.height = std::min(height, (u32)m_pTexture->realHeight); + + /* An asynchronous copy writes the reads the GPU has finished and + * leaves this one in flight; a synchronous one writes those first, + * waiting for them, then its own. */ + const u32 slot = _sync ? m_readback.drain(*this) : m_readback.begin(*this); + m_bufferReader->readToSlot(slot, x0, y0, g.height, g.size); + frameBufferList().setCurrentDrawBuffer(); + if (_sync) { + m_bufferReader->slotReady(slot, true); + write(slot, g); + } else { + m_readback.issued(g); + } + + gDP.changed |= CHANGED_SCISSOR; +} + +bool ColorBufferToRDRAM::ready(u32 _slot, bool _wait) +{ + return m_bufferReader->slotReady(_slot, _wait); +} + +void ColorBufferToRDRAM::write(u32 _slot, const ColorCopyGeometry & _g) +{ + u32 srcStride = 0; + ColorCopyGeometry g = _g; + const u8 * pPixels = m_bufferReader->slotPixels(_slot, g.width, g.height, g.size, srcStride); + if (pPixels != nullptr) + _write(pPixels, srcStride, g); + m_bufferReader->cleanUp(); +} + +void ColorBufferToRDRAM::_write(const u8 * _pixels, u32 _stride, const ColorCopyGeometry & _g) +{ + if (_g.height == 0) + return; + /* the RDRAM it was read for may have changed hands since */ + if (!isMemoryWritable(RDRAM + _g.startAddress, _g.numPixels << _g.size >> 1)) + return; + + const bool clear = !FBInfo::fbInfo.isSupported() && config.frameBufferEmulation.copyFromRDRAM != 0; + if (_g.size == G_IM_SIZ_32b) { + u32 *ptr_dst = (u32*)(RDRAM + _g.startAddress); + if (clear) + memset(ptr_dst, 0, _g.numPixels * 4); + writeToRdram((const u32*)_pixels, _stride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoRGBA32, 0, 0, _g.width, _g.height, _g.numPixels, _g.startAddress, _g.bufferAddress, _g.size); + } else if (_g.size == G_IM_SIZ_16b) { + u16 *ptr_dst = (u16*)(RDRAM + _g.startAddress); + if (clear) + memset(ptr_dst, 0, _g.numPixels * 2); + writeToRdram((const u32*)_pixels, _stride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoRGBA16, 0, 1, _g.width, _g.height, _g.numPixels, _g.startAddress, _g.bufferAddress, _g.size); + } else if (_g.size == G_IM_SIZ_8b) { + u8 *ptr_dst = RDRAM + _g.startAddress; + if (clear) + memset(ptr_dst, 0, _g.numPixels); + writeToRdram(_pixels, _stride, ptr_dst, &ColorBufferToRDRAM::_RGBAtoR8, 0, 3, _g.width, _g.height, _g.numPixels, _g.startAddress, _g.bufferAddress, _g.size); + } + + /* the buffer it was read from, if it is still there as it was */ + FrameBuffer * pBuffer = frameBufferList().findBuffer(_g.bufferAddress); + if (pBuffer != nullptr && pBuffer->m_startAddress == _g.bufferAddress && + pBuffer->m_width == _g.width && pBuffer->m_size == _g.size) { + pBuffer->m_copiedToRdram = true; + pBuffer->copyRdram(); + pBuffer->m_cleared = false; + } +} + +u32 ColorBufferToRDRAM::_getRealWidth(u32 _viWidth) +{ + u32 index = 0; + const u32 maxIndex = static_cast(m_allowedRealWidths.size()) - 1; + while (index < maxIndex && _viWidth > m_allowedRealWidths[index]) + { + ++index; + } + + return m_allowedRealWidths[index]; +} + +void ColorBufferToRDRAM::copyToRDRAM(u32 _address, bool _sync) +{ + if (!isMemoryWritable(RDRAM + _address, gDP.colorImage.width << gDP.colorImage.size >> 1)) + return; + if (!_prepareCopy(_address)) + return; + const u32 numBytes = (m_pCurFrameBuffer->m_width*m_pCurFrameBuffer->m_height) << m_pCurFrameBuffer->m_size >> 1; + _copy(m_pCurFrameBuffer->m_startAddress, m_pCurFrameBuffer->m_startAddress + numBytes, _sync); +} + +void ColorBufferToRDRAM::copyChunkToRDRAM(u32 _startAddress) +{ + const u32 endAddress = (_startAddress & ~0xfff) + 0x1000; + + if (!isMemoryWritable(RDRAM + _startAddress, endAddress - _startAddress)) + return; + if (!_prepareCopy(_startAddress)) + return; + _copy(_startAddress, endAddress, true); +} + + +ColorBufferToRDRAM & ColorBufferToRDRAM::get() +{ + static ColorBufferToRDRAM cbCopy; + return cbCopy; +} + +void copyWhiteToRDRAM(FrameBuffer * _pBuffer) +{ + if (_pBuffer->m_size == G_IM_SIZ_32b) { + u32 *ptr_dst = (u32*)(RDRAM + _pBuffer->m_startAddress); + + for (u32 y = 0; y < VI.height; ++y) { + for (u32 x = 0; x < VI.width; ++x) + ptr_dst[x + y*VI.width] = 0xFFFFFFFF; + } + } else { + u16 *ptr_dst = (u16*)(RDRAM + _pBuffer->m_startAddress); + + for (u32 y = 0; y < VI.height; ++y) { + for (u32 x = 0; x < VI.width; ++x) { + ptr_dst[(x + y*VI.width) ^ 1] = 0xFFFF; + } + } + } + _pBuffer->m_copiedToRdram = true; + _pBuffer->copyRdram(); + + _pBuffer->m_cleared = false; +} diff --git a/mupen64plus-video-gliden64/src/BufferCopy/ColorBufferToRDRAM.h b/mupen64plus-video-gliden64/src/BufferCopy/ColorBufferToRDRAM.h index 4f5f8733..03008c36 100644 --- a/mupen64plus-video-gliden64/src/BufferCopy/ColorBufferToRDRAM.h +++ b/mupen64plus-video-gliden64/src/BufferCopy/ColorBufferToRDRAM.h @@ -1,69 +1,86 @@ -#ifndef ColorBufferToRDRAM_H -#define ColorBufferToRDRAM_H - -#include -#include -#include -#include - -namespace graphics { - class ColorBufferReader; -} - -struct CachedTexture; -struct FrameBuffer; - -class ColorBufferToRDRAM -{ -public: - void init(); - void destroy(); - - void copyToRDRAM(u32 _address, bool _sync); - void copyChunkToRDRAM(u32 _startAddress); - - static ColorBufferToRDRAM & get(); - -private: - ColorBufferToRDRAM(); - ColorBufferToRDRAM(const ColorBufferToRDRAM &) = delete; - virtual ~ColorBufferToRDRAM(); - - CachedTexture * m_pTexture; - - union RGBA { - struct { - u8 r, g, b, a; - }; - u32 raw; - }; - - void _initFBTexture(void); - - void _destroyFBTexure(void); - - bool _prepareCopy(u32& _startAddress); - - void _copy(u32 _startAddress, u32 _endAddress, bool _sync); - - u32 _getRealWidth(u32 _viWidth); - - // Convert pixel from video memory to N64 buffer format. - static u8 _RGBAtoR8(u8 _c); - static u16 _RGBAtoRGBA16(u32 _c); - static u32 _RGBAtoRGBA32(u32 _c); - - graphics::ObjectHandle m_FBO; - FrameBuffer * m_pCurFrameBuffer; - u32 m_frameCount; - u32 m_startAddress; - - u32 m_lastBufferWidth; - - std::array m_allowedRealWidths; - std::unique_ptr m_bufferReader; -}; - -void copyWhiteToRDRAM(FrameBuffer * _pBuffer); - -#endif // ColorBufferToRDRAM +#ifndef ColorBufferToRDRAM_H +#define ColorBufferToRDRAM_H + +#include +#include +#include +#include +#include "DeferredReadback.h" + +namespace graphics { + class ColorBufferReader; +} + +struct CachedTexture; +struct FrameBuffer; + +/* What a colour copy to RDRAM was read for. */ +struct ColorCopyGeometry +{ + u32 startAddress; + u32 numPixels; + u32 bufferAddress; + u32 width; + u32 height; + u32 size; +}; + +class ColorBufferToRDRAM : private DeferredReadback::Ops +{ +public: + void init(); + void destroy(); + + void copyToRDRAM(u32 _address, bool _sync); + void copyChunkToRDRAM(u32 _startAddress); + + static ColorBufferToRDRAM & get(); + +private: + ColorBufferToRDRAM(); + ColorBufferToRDRAM(const ColorBufferToRDRAM &) = delete; + virtual ~ColorBufferToRDRAM(); + + CachedTexture * m_pTexture; + + union RGBA { + struct { + u8 r, g, b, a; + }; + u32 raw; + }; + + void _initFBTexture(void); + + void _destroyFBTexure(void); + + bool _prepareCopy(u32& _startAddress); + + void _copy(u32 _startAddress, u32 _endAddress, bool _sync); + void _write(const u8 * _pixels, u32 _stride, const ColorCopyGeometry & _g); + + bool ready(u32 _slot, bool _wait) override; + void write(u32 _slot, const ColorCopyGeometry & _g) override; + + u32 _getRealWidth(u32 _viWidth); + + // Convert pixel from video memory to N64 buffer format. + static u8 _RGBAtoR8(u8 _c); + static u16 _RGBAtoRGBA16(u32 _c); + static u32 _RGBAtoRGBA32(u32 _c); + + graphics::ObjectHandle m_FBO; + FrameBuffer * m_pCurFrameBuffer; + u32 m_frameCount; + u32 m_startAddress; + + u32 m_lastBufferWidth; + + std::array m_allowedRealWidths; + std::unique_ptr m_bufferReader; + DeferredReadback m_readback; +}; + +void copyWhiteToRDRAM(FrameBuffer * _pBuffer); + +#endif // ColorBufferToRDRAM diff --git a/mupen64plus-video-gliden64/src/BufferCopy/DeferredReadback.h b/mupen64plus-video-gliden64/src/BufferCopy/DeferredReadback.h new file mode 100644 index 00000000..7947274d --- /dev/null +++ b/mupen64plus-video-gliden64/src/BufferCopy/DeferredReadback.h @@ -0,0 +1,89 @@ +#ifndef DeferredReadback_H +#define DeferredReadback_H + +#include "../Types.h" + +/* The order of the asynchronous colour copies to RDRAM. Each read goes + * into the next of a ring of slots and keeps what it was read for, so it + * lands where it was meant to go. A read is written to RDRAM at a later + * copy once the GPU has finished it, oldest first. The copy waits only + * when the slot it is about to reuse is still in flight, i.e. when the GPU + * is a whole ring of copies behind. */ +template +class DeferredReadback +{ +public: + struct Ops + { + virtual bool ready(u32 _slot, bool _wait) = 0; + virtual void write(u32 _slot, const Geometry & _geometry) = 0; + protected: + ~Ops() {} + }; + + DeferredReadback() { reset(1); } + + void reset(u32 _slots) + { + m_slots = _slots == 0 ? 1 : (_slots > MaxSlots ? MaxSlots : _slots); + m_next = 0; + for (u32 i = 0; i < MaxSlots; ++i) + m_pending[i] = false; + } + + /* Writes what has finished and hands back the slot to read into. */ + u32 begin(Ops & _ops) + { + for (u32 i = 0; i < m_slots; ++i) { + const u32 slot = (m_next + i) % m_slots; + if (!m_pending[slot]) + continue; + if (!_ops.ready(slot, false)) + break; + _retire(_ops, slot); + } + if (m_pending[m_next]) { + _ops.ready(m_next, true); + _retire(_ops, m_next); + } + return m_next; + } + + /* The read begin() handed out is on its way. */ + void issued(const Geometry & _geometry) + { + m_geometry[m_next] = _geometry; + m_pending[m_next] = true; + m_next = (m_next + 1) % m_slots; + } + + /* Writes every read in flight, waiting for each; the next slot is + * free afterwards, for a read that is waited for at once. */ + u32 drain(Ops & _ops) + { + for (u32 i = 0; i < m_slots; ++i) { + const u32 slot = (m_next + i) % m_slots; + if (m_pending[slot]) { + _ops.ready(slot, true); + _retire(_ops, slot); + } + } + return m_next; + } + + bool pending(u32 _slot) const { return m_pending[_slot]; } + +private: + void _retire(Ops & _ops, u32 _slot) + { + m_pending[_slot] = false; + _ops.write(_slot, m_geometry[_slot]); + } + + u32 m_slots; + u32 m_next; + bool m_pending[MaxSlots]; + Geometry m_geometry[MaxSlots]; +}; + +#endif // DeferredReadback_H diff --git a/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.cpp b/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.cpp index dfc35a7f..3b980a6b 100644 --- a/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.cpp +++ b/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.cpp @@ -1,83 +1,116 @@ - -#include "ColorBufferReader.h" -#include "FramebufferTextureFormats.h" -#include "Context.h" -#include "Parameters.h" -#include -#include - -namespace graphics { - - ColorBufferReader::ColorBufferReader(CachedTexture * _pTexture) - : m_pTexture(_pTexture) - { - m_pixelData.resize(m_pTexture->textureBytes); - } - - const u8* ColorBufferReader::_convertFloatTextureBuffer(const u8* _gpuData, u32 _width, u32 & _height, - u32 _heightOffset, u32 _stride) - { - u8* pixelDataAlloc = m_pixelData.data(); - const float* pixelData = reinterpret_cast(_gpuData); - const u32 colorsPerPixel = 4; - const u32 widthPixels = _width * colorsPerPixel; - const u32 stridePixels = _stride * colorsPerPixel; - - if (_height * widthPixels > m_pixelData.size()) - _height = static_cast(m_pixelData.size()) / widthPixels; - - for (u32 heightIndex = 0; heightIndex < _height; ++heightIndex) { - for (u32 widthIndex = 0; widthIndex < widthPixels; ++widthIndex) { - u8& dest = *(pixelDataAlloc + heightIndex*widthPixels + widthIndex); - const float& src = *(pixelData + (heightIndex+_heightOffset)*stridePixels + widthIndex); - dest = static_cast(src*255.0); - } - } - - return pixelDataAlloc; - } - - - const u8 * ColorBufferReader::readPixels(s32 _x0, s32 _y0, u32 _width, u32 & _height, u32 _size, bool _sync, - u32 & _stride) - { - const FramebufferTextureFormats & fbTexFormat = gfxContext.getFramebufferTextureFormats(); - - ReadColorBufferParams params; - params.x0 = _x0; - params.y0 = _y0; - params.width = _width; - params.height = _height; - params.sync = _sync; - - if (_size > G_IM_SIZ_8b) { - params.colorFormat = fbTexFormat.colorFormat; - params.colorType = fbTexFormat.colorType; - params.colorFormatBytes = fbTexFormat.colorFormatBytes; - } else { - params.colorFormat = fbTexFormat.monochromeFormat; - params.colorType = fbTexFormat.monochromeType; - params.colorFormatBytes = fbTexFormat.monochromeFormatBytes; - } - - u32 heightOffset = 0; - u32 stride = 0; - const u8* pixelData = _readPixels(params, heightOffset, stride); - - if (pixelData == nullptr) - return nullptr; - - if(params.colorType == datatype::FLOAT && _size > G_IM_SIZ_8b) { - _stride = _width; - return _convertFloatTextureBuffer(pixelData, params.width, _height, heightOffset, stride); - } - - /* no compacting copy: the consumer steps by the stride */ - const u32 strideBytes = stride * params.colorFormatBytes; - const u32 rows = strideBytes != 0 ? m_pTexture->textureBytes / strideBytes : 0; - _height = rows > heightOffset ? std::min(_height, rows - heightOffset) : 0; - _stride = stride; - return pixelData + heightOffset * strideBytes; - } - + +#include "ColorBufferReader.h" +#include "FramebufferTextureFormats.h" +#include "Context.h" +#include "Parameters.h" +#include +#include + +namespace graphics { + + ColorBufferReader::ColorBufferReader(CachedTexture * _pTexture) + : m_pTexture(_pTexture) + { + m_pixelData.resize(m_pTexture->textureBytes); + } + + const u8* ColorBufferReader::_convertFloatTextureBuffer(const u8* _gpuData, u32 _width, u32 & _height, + u32 _heightOffset, u32 _stride) + { + u8* pixelDataAlloc = m_pixelData.data(); + const float* pixelData = reinterpret_cast(_gpuData); + const u32 colorsPerPixel = 4; + const u32 widthPixels = _width * colorsPerPixel; + const u32 stridePixels = _stride * colorsPerPixel; + + if (_height * widthPixels > m_pixelData.size()) + _height = static_cast(m_pixelData.size()) / widthPixels; + + for (u32 heightIndex = 0; heightIndex < _height; ++heightIndex) { + for (u32 widthIndex = 0; widthIndex < widthPixels; ++widthIndex) { + u8& dest = *(pixelDataAlloc + heightIndex*widthPixels + widthIndex); + const float& src = *(pixelData + (heightIndex+_heightOffset)*stridePixels + widthIndex); + dest = static_cast(src*255.0); + } + } + + return pixelDataAlloc; + } + + + void ColorBufferReader::_slotFormat(u32 _size, ColorFormatParam & _format, DatatypeParam & _type, u32 & _bytes) const + { + const FramebufferTextureFormats & fbTexFormat = gfxContext.getFramebufferTextureFormats(); + if (_size > G_IM_SIZ_8b) { + _format = fbTexFormat.colorFormat; + _type = fbTexFormat.colorType; + _bytes = fbTexFormat.colorFormatBytes; + } else { + _format = fbTexFormat.monochromeFormat; + _type = fbTexFormat.monochromeType; + _bytes = fbTexFormat.monochromeFormatBytes; + } + } + + const u8 * ColorBufferReader::slotPixels(u32 _slot, u32 _width, u32 & _height, u32 _size, u32 & _stride) + { + ColorFormatParam format; + DatatypeParam type; + u32 bytes; + _slotFormat(_size, format, type, bytes); + + u32 stride = 0; + const u8 * pixelData = _mapSlot(_slot, _height, bytes, stride); + if (pixelData == nullptr) + return nullptr; + if (type == datatype::FLOAT && _size > G_IM_SIZ_8b) { + _stride = _width; + return _convertFloatTextureBuffer(pixelData, _width, _height, 0, stride); + } + _stride = stride; + return pixelData; + } + + const u8 * ColorBufferReader::readPixels(s32 _x0, s32 _y0, u32 _width, u32 & _height, u32 _size, bool _sync, + u32 & _stride) + { + const FramebufferTextureFormats & fbTexFormat = gfxContext.getFramebufferTextureFormats(); + + ReadColorBufferParams params; + params.x0 = _x0; + params.y0 = _y0; + params.width = _width; + params.height = _height; + params.sync = _sync; + + if (_size > G_IM_SIZ_8b) { + params.colorFormat = fbTexFormat.colorFormat; + params.colorType = fbTexFormat.colorType; + params.colorFormatBytes = fbTexFormat.colorFormatBytes; + } else { + params.colorFormat = fbTexFormat.monochromeFormat; + params.colorType = fbTexFormat.monochromeType; + params.colorFormatBytes = fbTexFormat.monochromeFormatBytes; + } + + u32 heightOffset = 0; + u32 stride = 0; + const u8* pixelData = _readPixels(params, heightOffset, stride); + + if (pixelData == nullptr) + return nullptr; + + if(params.colorType == datatype::FLOAT && _size > G_IM_SIZ_8b) { + _stride = _width; + return _convertFloatTextureBuffer(pixelData, params.width, _height, heightOffset, stride); + } + + /* no compacting copy: the consumer steps by the stride */ + const u32 strideBytes = stride * params.colorFormatBytes; + const u32 rows = strideBytes != 0 ? m_pTexture->textureBytes / strideBytes : 0; + _height = rows > heightOffset ? std::min(_height, rows - heightOffset) : 0; + _stride = stride; + return pixelData + heightOffset * strideBytes; + } + } \ No newline at end of file diff --git a/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.h b/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.h index 236994db..d95f2393 100644 --- a/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.h +++ b/mupen64plus-video-gliden64/src/Graphics/ColorBufferReader.h @@ -1,40 +1,52 @@ -#pragma once -#include -#include -#include - -namespace graphics { - -class ColorBufferReader -{ -public: - ColorBufferReader(CachedTexture * _pTexture); - virtual ~ColorBufferReader() = default; - - /* The rows read back, _stride pixels apart, valid until cleanUp(). - * _height comes back as the rows the buffer holds. Integer formats - * are the reader's own memory, read where they lie. */ - virtual const u8 * readPixels(s32 _x0, s32 _y0, u32 _width, u32 & _height, u32 _size, bool _sync, u32 & _stride); - virtual void cleanUp() = 0; - -protected: - struct ReadColorBufferParams { - s32 x0; - s32 y0; - u32 width; - u32 height; - bool sync; - ColorFormatParam colorFormat; - DatatypeParam colorType; - u32 colorFormatBytes; - }; - - CachedTexture * m_pTexture; - std::vector m_pixelData; - -private: - const u8* _convertFloatTextureBuffer(const u8* _gpuData, u32 _width, u32 & _height, u32 _heightOffset, u32 _stride); - virtual const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) = 0; -}; - -} +#pragma once +#include +#include +#include + +namespace graphics { + +class ColorBufferReader +{ +public: + ColorBufferReader(CachedTexture * _pTexture); + virtual ~ColorBufferReader() = default; + + /* The rows read back, _stride pixels apart, valid until cleanUp(). + * _height comes back as the rows the buffer holds. Integer formats + * are the reader's own memory, read where they lie. */ + virtual const u8 * readPixels(s32 _x0, s32 _y0, u32 _width, u32 & _height, u32 _size, bool _sync, u32 & _stride); + virtual void cleanUp() = 0; + + /* Readers with a ring of pixel buffers read into numbered slots and + * leave the wait to the caller: readToSlot starts a read of _height + * rows, slotReady polls it (or waits for it), slotPixels gives its + * _height rows of _width pixels, _stride apart, valid until cleanUp(). */ + virtual u32 numSlots() const { return 0; } + virtual void readToSlot(u32 _slot, s32 _x0, s32 _y0, u32 _height, u32 _size) {} + virtual bool slotReady(u32 _slot, bool _wait) { return true; } + const u8 * slotPixels(u32 _slot, u32 _width, u32 & _height, u32 _size, u32 & _stride); + +protected: + struct ReadColorBufferParams { + s32 x0; + s32 y0; + u32 width; + u32 height; + bool sync; + ColorFormatParam colorFormat; + DatatypeParam colorType; + u32 colorFormatBytes; + }; + + CachedTexture * m_pTexture; + std::vector m_pixelData; + + void _slotFormat(u32 _size, ColorFormatParam & _format, DatatypeParam & _type, u32 & _bytes) const; + +private: + const u8* _convertFloatTextureBuffer(const u8* _gpuData, u32 _width, u32 & _height, u32 _heightOffset, u32 _stride); + virtual const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) = 0; + virtual const u8 * _mapSlot(u32 _slot, u32 _height, u32 _bytes, u32 & _stride) { return nullptr; } +}; + +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.cpp b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.cpp index d83a8c00..905ae7a7 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.cpp +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.cpp @@ -1,226 +1,278 @@ -#include -#include -#include "GLFunctions.h" -#include "opengl_Attributes.h" -#include "opengl_BufferedDrawer.h" - -using namespace graphics; -using namespace opengl; - -const u32 BufferedDrawer::m_bufMaxSize = 8 * 1024 * 1024; // 8 MB -#ifndef GL_DEBUG -const GLbitfield BufferedDrawer::m_bufAccessBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; -const GLbitfield BufferedDrawer::m_bufMapBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; -#else -const GLbitfield BufferedDrawer::m_bufAccessBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; -const GLbitfield BufferedDrawer::m_bufMapBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT; -#endif - -BufferedDrawer::BufferedDrawer(const GLInfo & _glinfo, CachedVertexAttribArray * _cachedAttribArray, CachedBindBuffer * _bindBuffer) -: m_glInfo(_glinfo) -, m_cachedAttribArray(_cachedAttribArray) -, m_bindBuffer(_bindBuffer) -{ - m_vertices.resize(VERTBUFF_SIZE); - /* Init buffers for rects */ - glGenVertexArrays(1, &m_rectsBuffers.vao); - glBindVertexArray(m_rectsBuffers.vao); - _initBuffer(m_rectsBuffers.vbo, m_bufMaxSize); - m_cachedAttribArray->enableVertexAttribArray(rectAttrib::position, true); - m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord0, true); - m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord1, true); - glVertexAttribPointer(rectAttrib::position, 4, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, x))); - glVertexAttribPointer(rectAttrib::texcoord0, 2, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, s0))); - glVertexAttribPointer(rectAttrib::texcoord1, 2, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, s1))); - - /* Init buffers for triangles */ - glGenVertexArrays(1, &m_trisBuffers.vao); - glBindVertexArray(m_trisBuffers.vao); - _initBuffer(m_trisBuffers.vbo, m_bufMaxSize); - _initBuffer(m_trisBuffers.ebo, m_bufMaxSize); - m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::position, true); - m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::color, true); - m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::texcoord, true); - m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::modify, true); - m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::numlights, false); - glVertexAttribPointer(triangleAttrib::position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, x))); - glVertexAttribPointer(triangleAttrib::color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, r))); - glVertexAttribPointer(triangleAttrib::texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, s))); - glVertexAttribPointer(triangleAttrib::modify, 4, GL_BYTE, GL_TRUE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, modify))); -} - -void BufferedDrawer::_initBuffer(Buffer & _buffer, GLuint _bufSize) -{ - _buffer.size = _bufSize; - glGenBuffers(1, &_buffer.handle); - m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); - if (m_glInfo.bufferStorage) { - glBufferStorage(_buffer.type, _bufSize, nullptr, m_bufAccessBits); - _buffer.data = (GLubyte*)glMapBufferRange(_buffer.type, 0, _bufSize, m_bufMapBits); - } else { - glBufferData(_buffer.type, _bufSize, nullptr, GL_DYNAMIC_DRAW); - } -} - -BufferedDrawer::~BufferedDrawer() -{ - m_bindBuffer->bind(Parameter(GL_ARRAY_BUFFER), ObjectHandle::null); - m_bindBuffer->bind(Parameter(GL_ELEMENT_ARRAY_BUFFER), ObjectHandle::null); - GLuint buffers[3] = { m_rectsBuffers.vbo.handle, m_trisBuffers.vbo.handle, m_trisBuffers.ebo.handle }; - glDeleteBuffers(3, buffers); - glBindVertexArray(0); - GLuint arrays[2] = { m_rectsBuffers.vao, m_trisBuffers.vao }; - glDeleteVertexArrays(2, arrays); -} - -void BufferedDrawer::_updateBuffer(Buffer & _buffer, u32 _count, u32 _dataSize, const void * _data) -{ - if (_buffer.offset + _dataSize >= _buffer.size) { - _buffer.offset = 0; - _buffer.pos = 0; - } - - if (m_glInfo.bufferStorage) { - memcpy(&_buffer.data[_buffer.offset], _data, _dataSize); -#ifdef GL_DEBUG - m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); - glFlushMappedBufferRange(_buffer.type, _buffer.offset, _dataSize); -#endif - } else { - m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); - void* buffer_pointer = glMapBufferRange(_buffer.type, _buffer.offset, _dataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - memcpy(buffer_pointer, _data, _dataSize); - glUnmapBuffer(_buffer.type); - } - - _buffer.offset += _dataSize; - _buffer.pos += _count; -} - -void BufferedDrawer::_updateRectBuffer(const graphics::Context::DrawRectParameters & _params) -{ - const BuffersType type = BuffersType::rects; - if (m_type != type) { - glBindVertexArray(m_rectsBuffers.vao); - m_type = type; - } - - Buffer & buffer = m_rectsBuffers.vbo; - const u32 dataSize = _params.verticesCount * static_cast(sizeof(RectVertex)); - - if (m_glInfo.bufferStorage) { - _updateBuffer(buffer, _params.verticesCount, dataSize, _params.vertices); - return; - } - - const u64 crc = CRC_Calculate(UINT64_MAX, _params.vertices, dataSize); - auto iter = m_rectBufferOffsets.find(crc); - if (iter != m_rectBufferOffsets.end()) { - buffer.pos = iter->second; - return; - } - - const GLintptr prevOffset = buffer.offset; - _updateBuffer(buffer, _params.verticesCount, dataSize, _params.vertices); - if (buffer.offset < prevOffset) - m_rectBufferOffsets.clear(); - - buffer.pos = static_cast(buffer.offset / sizeof(RectVertex)); - m_rectBufferOffsets[crc] = buffer.pos; -} - - -void BufferedDrawer::drawRects(const graphics::Context::DrawRectParameters & _params) -{ - _updateRectBuffer(_params); - - m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord0, _params.texrect); - m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord1, _params.texrect); - - glDrawArrays(GLenum(_params.mode), m_rectsBuffers.vbo.pos - _params.verticesCount, _params.verticesCount); -} - -void BufferedDrawer::_convertFromSPVertex(bool _flatColors, u32 _count, const SPVertex * _data) -{ - if (_count > m_vertices.size()) - m_vertices.resize(_count); - - for (u32 i = 0; i < _count; ++i) { - const SPVertex & src = _data[i]; - Vertex & dst = m_vertices[i]; - dst.x = src.x; - dst.y = src.y; - dst.z = src.z; - dst.w = src.w; - if (_flatColors) { - dst.r = src.flat_r; - dst.g = src.flat_g; - dst.b = src.flat_b; - dst.a = src.flat_a; - } else { - dst.r = src.r; - dst.g = src.g; - dst.b = src.b; - dst.a = src.a; - } - dst.s = src.s; - dst.t = src.t; - dst.modify = src.modify; - } -} - -void BufferedDrawer::_updateTrianglesBuffers(const graphics::Context::DrawTriangleParameters & _params) -{ - const BuffersType type = BuffersType::triangles; - - if (m_type != type) { - glBindVertexArray(m_trisBuffers.vao); - m_type = type; - } - - _convertFromSPVertex(_params.flatColors, _params.verticesCount, _params.vertices); - const u32 vboDataSize = _params.verticesCount * static_cast(sizeof(Vertex)); - Buffer & vboBuffer = m_trisBuffers.vbo; - _updateBuffer(vboBuffer, _params.verticesCount, vboDataSize, m_vertices.data()); - - if (_params.elements == nullptr) - return; - - const u32 eboDataSize = static_cast(sizeof(GLushort)) * _params.elementsCount; - Buffer & eboBuffer = m_trisBuffers.ebo; - _updateBuffer(eboBuffer, _params.elementsCount, eboDataSize, _params.elements); -} - -void BufferedDrawer::drawTriangles(const graphics::Context::DrawTriangleParameters & _params) -{ - _updateTrianglesBuffers(_params); - - if (isHWLightingAllowed()) - glVertexAttrib1f(triangleAttrib::numlights, GLfloat(_params.vertices[0].HWLight)); - - if (_params.elements == nullptr) { - glDrawArrays(GLenum(_params.mode), m_trisBuffers.vbo.pos - _params.verticesCount, _params.verticesCount); - return; - } - - glDrawRangeElementsBaseVertex(GLenum(_params.mode), 0, _params.verticesCount - 1, _params.elementsCount, GL_UNSIGNED_SHORT, - (u16*)nullptr + m_trisBuffers.ebo.pos - _params.elementsCount, m_trisBuffers.vbo.pos - _params.verticesCount); -} - -void BufferedDrawer::drawLine(f32 _width, SPVertex * _vertices) -{ - const BuffersType type = BuffersType::triangles; - - if (m_type != type) { - glBindVertexArray(m_trisBuffers.vao); - m_type = type; - } - - _convertFromSPVertex(false, 2, _vertices); - const GLsizeiptr vboDataSize = 2 * sizeof(Vertex); - Buffer & vboBuffer = m_trisBuffers.vbo; - _updateBuffer(vboBuffer, 2, vboDataSize, m_vertices.data()); - - glLineWidth(_width); - glDrawArrays(GL_LINES, m_trisBuffers.vbo.pos - 2, 2); -} +#include +#include +#include "GLFunctions.h" +#include "opengl_Attributes.h" +#include "opengl_BufferedDrawer.h" + +using namespace graphics; +using namespace opengl; + +const u32 BufferedDrawer::m_bufMaxSize = 8 * 1024 * 1024; // 8 MB +#ifndef GL_DEBUG +const GLbitfield BufferedDrawer::m_bufAccessBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; +const GLbitfield BufferedDrawer::m_bufMapBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; +#else +const GLbitfield BufferedDrawer::m_bufAccessBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT; +const GLbitfield BufferedDrawer::m_bufMapBits = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT; +#endif + +BufferedDrawer::BufferedDrawer(const GLInfo & _glinfo, CachedVertexAttribArray * _cachedAttribArray, CachedBindBuffer * _bindBuffer) +: m_glInfo(_glinfo) +, m_cachedAttribArray(_cachedAttribArray) +, m_bindBuffer(_bindBuffer) +{ + m_vertices.resize(VERTBUFF_SIZE); + /* Init buffers for rects */ + glGenVertexArrays(1, &m_rectsBuffers.vao); + glBindVertexArray(m_rectsBuffers.vao); + _initBuffer(m_rectsBuffers.vbo, m_bufMaxSize); + m_cachedAttribArray->enableVertexAttribArray(rectAttrib::position, true); + m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord0, true); + m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord1, true); + glVertexAttribPointer(rectAttrib::position, 4, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, x))); + glVertexAttribPointer(rectAttrib::texcoord0, 2, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, s0))); + glVertexAttribPointer(rectAttrib::texcoord1, 2, GL_FLOAT, GL_FALSE, sizeof(RectVertex), (const GLvoid *)(offsetof(RectVertex, s1))); + + /* Init buffers for triangles */ + glGenVertexArrays(1, &m_trisBuffers.vao); + glBindVertexArray(m_trisBuffers.vao); + _initBuffer(m_trisBuffers.vbo, m_bufMaxSize); + _initBuffer(m_trisBuffers.ebo, m_bufMaxSize); + m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::position, true); + m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::color, true); + m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::texcoord, true); + m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::modify, true); + m_cachedAttribArray->enableVertexAttribArray(triangleAttrib::numlights, false); + glVertexAttribPointer(triangleAttrib::position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, x))); + glVertexAttribPointer(triangleAttrib::color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, r))); + glVertexAttribPointer(triangleAttrib::texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, s))); + glVertexAttribPointer(triangleAttrib::modify, 4, GL_BYTE, GL_TRUE, sizeof(Vertex), (const GLvoid *)(offsetof(Vertex, modify))); +} + +void BufferedDrawer::_initBuffer(Buffer & _buffer, GLuint _bufSize) +{ + _buffer.size = _bufSize; + _buffer.ring.init(_bufSize); + glGenBuffers(1, &_buffer.handle); + m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); + if (m_glInfo.bufferStorage) { + glBufferStorage(_buffer.type, _bufSize, nullptr, m_bufAccessBits); + _buffer.data = (GLubyte*)glMapBufferRange(_buffer.type, 0, _bufSize, m_bufMapBits); + } else { + glBufferData(_buffer.type, _bufSize, nullptr, GL_DYNAMIC_DRAW); + } +} + +BufferedDrawer::~BufferedDrawer() +{ + m_bindBuffer->bind(Parameter(GL_ARRAY_BUFFER), ObjectHandle::null); + m_bindBuffer->bind(Parameter(GL_ELEMENT_ARRAY_BUFFER), ObjectHandle::null); + Buffer * all[3] = { &m_rectsBuffers.vbo, &m_trisBuffers.vbo, &m_trisBuffers.ebo }; + for (Buffer * b : all) + for (GLsync & fence : b->fences) + if (fence != 0) + glDeleteSync(fence); + GLuint buffers[3] = { m_rectsBuffers.vbo.handle, m_trisBuffers.vbo.handle, m_trisBuffers.ebo.handle }; + glDeleteBuffers(3, buffers); + glBindVertexArray(0); + GLuint arrays[2] = { m_rectsBuffers.vao, m_trisBuffers.vao }; + glDeleteVertexArrays(2, arrays); +} + +namespace { + /* the draws so far, marked when the writes leave a segment */ + struct SegmentFences + { + GLsync * fences; + void fence(u32 _seg) + { + if (fences[_seg] != 0) + glDeleteSync(fences[_seg]); + fences[_seg] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + void wait(u32 _seg) + { + if (fences[_seg] == 0) + return; + GLenum res = glClientWaitSync(fences[_seg], GL_SYNC_FLUSH_COMMANDS_BIT, 0); + while (res == GL_TIMEOUT_EXPIRED) + res = glClientWaitSync(fences[_seg], 0, 1000000000); + glDeleteSync(fences[_seg]); + fences[_seg] = 0; + } + }; +} + +GLubyte * BufferedDrawer::_reserve(Buffer & _buffer, u32 _dataSize) +{ + SegmentFences fences = { _buffer.fences }; + const u32 offset = _buffer.ring.place(u32(_buffer.offset), _dataSize, fences); + if (offset != u32(_buffer.offset)) { + _buffer.offset = offset; + _buffer.pos = 0; + } + return m_glInfo.bufferStorage ? &_buffer.data[_buffer.offset] : nullptr; +} + +void BufferedDrawer::_commit(Buffer & _buffer, u32 _count, u32 _dataSize) +{ +#ifdef GL_DEBUG + if (m_glInfo.bufferStorage) { + m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); + glFlushMappedBufferRange(_buffer.type, _buffer.offset, _dataSize); + } +#endif + _buffer.offset += _dataSize; + _buffer.pos += _count; +} + +void BufferedDrawer::_updateBuffer(Buffer & _buffer, u32 _count, u32 _dataSize, const void * _data) +{ + GLubyte * dst = _reserve(_buffer, _dataSize); + if (dst != nullptr) { + memcpy(dst, _data, _dataSize); + } else { + /* unsynchronized: the segment fences keep the GPU's data apart */ + m_bindBuffer->bind(Parameter(_buffer.type), ObjectHandle(_buffer.handle)); + void* buffer_pointer = glMapBufferRange(_buffer.type, _buffer.offset, _dataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + memcpy(buffer_pointer, _data, _dataSize); + glUnmapBuffer(_buffer.type); + } + _commit(_buffer, _count, _dataSize); +} + +void BufferedDrawer::_updateRectBuffer(const graphics::Context::DrawRectParameters & _params) +{ + const BuffersType type = BuffersType::rects; + if (m_type != type) { + glBindVertexArray(m_rectsBuffers.vao); + m_type = type; + } + + Buffer & buffer = m_rectsBuffers.vbo; + const u32 dataSize = _params.verticesCount * static_cast(sizeof(RectVertex)); + + if (m_glInfo.bufferStorage) { + _updateBuffer(buffer, _params.verticesCount, dataSize, _params.vertices); + return; + } + + const u64 crc = CRC_Calculate(UINT64_MAX, _params.vertices, dataSize); + auto iter = m_rectBufferOffsets.find(crc); + if (iter != m_rectBufferOffsets.end()) { + buffer.pos = iter->second; + return; + } + + const GLintptr prevOffset = buffer.offset; + _updateBuffer(buffer, _params.verticesCount, dataSize, _params.vertices); + if (buffer.offset < prevOffset) + m_rectBufferOffsets.clear(); + + buffer.pos = static_cast(buffer.offset / sizeof(RectVertex)); + m_rectBufferOffsets[crc] = buffer.pos; +} + + +void BufferedDrawer::drawRects(const graphics::Context::DrawRectParameters & _params) +{ + _updateRectBuffer(_params); + + m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord0, _params.texrect); + m_cachedAttribArray->enableVertexAttribArray(rectAttrib::texcoord1, _params.texrect); + + glDrawArrays(GLenum(_params.mode), m_rectsBuffers.vbo.pos - _params.verticesCount, _params.verticesCount); +} + +void BufferedDrawer::_convertFromSPVertex(Vertex * _dst, bool _flatColors, u32 _count, const SPVertex * _data) +{ + for (u32 i = 0; i < _count; ++i) { + const SPVertex & src = _data[i]; + Vertex & dst = _dst[i]; + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; + dst.w = src.w; + if (_flatColors) { + dst.r = src.flat_r; + dst.g = src.flat_g; + dst.b = src.flat_b; + dst.a = src.flat_a; + } else { + dst.r = src.r; + dst.g = src.g; + dst.b = src.b; + dst.a = src.a; + } + dst.s = src.s; + dst.t = src.t; + dst.modify = src.modify; + } +} + +/* Converted straight into the mapped ring where there is one. */ +void BufferedDrawer::_updateVertices(bool _flatColors, u32 _count, const SPVertex * _data) +{ + Buffer & vboBuffer = m_trisBuffers.vbo; + const u32 vboDataSize = _count * static_cast(sizeof(Vertex)); + GLubyte * dst = _reserve(vboBuffer, vboDataSize); + if (dst != nullptr) { + _convertFromSPVertex(reinterpret_cast(dst), _flatColors, _count, _data); + _commit(vboBuffer, _count, vboDataSize); + return; + } + if (_count > m_vertices.size()) + m_vertices.resize(_count); + _convertFromSPVertex(m_vertices.data(), _flatColors, _count, _data); + _updateBuffer(vboBuffer, _count, vboDataSize, m_vertices.data()); +} + +void BufferedDrawer::_updateTrianglesBuffers(const graphics::Context::DrawTriangleParameters & _params) +{ + const BuffersType type = BuffersType::triangles; + + if (m_type != type) { + glBindVertexArray(m_trisBuffers.vao); + m_type = type; + } + + _updateVertices(_params.flatColors, _params.verticesCount, _params.vertices); + + if (_params.elements == nullptr) + return; + + const u32 eboDataSize = static_cast(sizeof(GLushort)) * _params.elementsCount; + Buffer & eboBuffer = m_trisBuffers.ebo; + _updateBuffer(eboBuffer, _params.elementsCount, eboDataSize, _params.elements); +} + +void BufferedDrawer::drawTriangles(const graphics::Context::DrawTriangleParameters & _params) +{ + _updateTrianglesBuffers(_params); + + if (isHWLightingAllowed()) + glVertexAttrib1f(triangleAttrib::numlights, GLfloat(_params.vertices[0].HWLight)); + + if (_params.elements == nullptr) { + glDrawArrays(GLenum(_params.mode), m_trisBuffers.vbo.pos - _params.verticesCount, _params.verticesCount); + return; + } + + glDrawRangeElementsBaseVertex(GLenum(_params.mode), 0, _params.verticesCount - 1, _params.elementsCount, GL_UNSIGNED_SHORT, + (u16*)nullptr + m_trisBuffers.ebo.pos - _params.elementsCount, m_trisBuffers.vbo.pos - _params.verticesCount); +} + +void BufferedDrawer::drawLine(f32 _width, SPVertex * _vertices) +{ + const BuffersType type = BuffersType::triangles; + + if (m_type != type) { + glBindVertexArray(m_trisBuffers.vao); + m_type = type; + } + + _updateVertices(false, 2, _vertices); + + glLineWidth(_width); + glDrawArrays(GL_LINES, m_trisBuffers.vbo.pos - 2, 2); +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.h b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.h index 307c69a6..6d907c05 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.h +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_BufferedDrawer.h @@ -1,85 +1,91 @@ -#pragma once -#include -#include -#include "opengl_GLInfo.h" -#include "opengl_GraphicsDrawer.h" -#include "opengl_CachedFunctions.h" - -namespace opengl { - - class BufferedDrawer : public GraphicsDrawer - { - public: - BufferedDrawer(const GLInfo & _glinfo, CachedVertexAttribArray * _cachedAttribArray, - CachedBindBuffer * _bindBuffer); - ~BufferedDrawer(); - - void drawTriangles(const graphics::Context::DrawTriangleParameters & _params) override; - - void drawRects(const graphics::Context::DrawRectParameters & _params) override; - - void drawLine(f32 _width, SPVertex * _vertices) override; - - private: - void _updateRectBuffer(const graphics::Context::DrawRectParameters & _params); - void _updateTrianglesBuffers(const graphics::Context::DrawTriangleParameters & _params); - - enum class BuffersType { - none, - rects, - triangles - }; - - struct Buffer { - Buffer(GLenum _type) : type(_type) {} - - GLenum type; - GLuint handle = 0; - GLintptr offset = 0; - GLint pos = 0; - GLuint size = 0; - GLubyte * data = nullptr; - }; - - struct RectBuffers { - GLuint vao = 0; - Buffer vbo = Buffer(GL_ARRAY_BUFFER); - }; - - struct TrisBuffers { - GLuint vao = 0; - Buffer vbo = Buffer(GL_ARRAY_BUFFER); - Buffer ebo = Buffer(GL_ELEMENT_ARRAY_BUFFER); - }; - - struct Vertex - { - f32 x, y, z, w; - f32 r, g, b, a; - f32 s, t; - u32 modify; - }; - - void _initBuffer(Buffer & _buffer, GLuint _bufSize); - void _updateBuffer(Buffer & _buffer, u32 _count, u32 _dataSize, const void * _data); - void _convertFromSPVertex(bool _flatColors, u32 _count, const SPVertex * _data); - - const GLInfo & m_glInfo; - CachedVertexAttribArray * m_cachedAttribArray; - CachedBindBuffer * m_bindBuffer; - - RectBuffers m_rectsBuffers; - TrisBuffers m_trisBuffers; - BuffersType m_type = BuffersType::none; - - std::vector m_vertices; - - typedef std::unordered_map BufferOffsets; - BufferOffsets m_rectBufferOffsets; - - static const u32 m_bufMaxSize; - static const GLbitfield m_bufAccessBits; - static const GLbitfield m_bufMapBits; - }; - -} +#pragma once +#include +#include +#include "opengl_GLInfo.h" +#include "opengl_GraphicsDrawer.h" +#include "opengl_CachedFunctions.h" +#include "opengl_RingSegments.h" + +namespace opengl { + + class BufferedDrawer : public GraphicsDrawer + { + public: + BufferedDrawer(const GLInfo & _glinfo, CachedVertexAttribArray * _cachedAttribArray, + CachedBindBuffer * _bindBuffer); + ~BufferedDrawer(); + + void drawTriangles(const graphics::Context::DrawTriangleParameters & _params) override; + + void drawRects(const graphics::Context::DrawRectParameters & _params) override; + + void drawLine(f32 _width, SPVertex * _vertices) override; + + private: + void _updateRectBuffer(const graphics::Context::DrawRectParameters & _params); + void _updateTrianglesBuffers(const graphics::Context::DrawTriangleParameters & _params); + + enum class BuffersType { + none, + rects, + triangles + }; + + struct Buffer { + Buffer(GLenum _type) : type(_type) {} + + GLenum type; + GLuint handle = 0; + GLintptr offset = 0; + GLint pos = 0; + GLuint size = 0; + GLubyte * data = nullptr; + RingSegments<3> ring; + GLsync fences[3] = {}; + }; + + struct RectBuffers { + GLuint vao = 0; + Buffer vbo = Buffer(GL_ARRAY_BUFFER); + }; + + struct TrisBuffers { + GLuint vao = 0; + Buffer vbo = Buffer(GL_ARRAY_BUFFER); + Buffer ebo = Buffer(GL_ELEMENT_ARRAY_BUFFER); + }; + + struct Vertex + { + f32 x, y, z, w; + f32 r, g, b, a; + f32 s, t; + u32 modify; + }; + + void _initBuffer(Buffer & _buffer, GLuint _bufSize); + void _updateBuffer(Buffer & _buffer, u32 _count, u32 _dataSize, const void * _data); + GLubyte * _reserve(Buffer & _buffer, u32 _dataSize); + void _commit(Buffer & _buffer, u32 _count, u32 _dataSize); + static void _convertFromSPVertex(Vertex * _dst, bool _flatColors, u32 _count, const SPVertex * _data); + void _updateVertices(bool _flatColors, u32 _count, const SPVertex * _data); + + const GLInfo & m_glInfo; + CachedVertexAttribArray * m_cachedAttribArray; + CachedBindBuffer * m_bindBuffer; + + RectBuffers m_rectsBuffers; + TrisBuffers m_trisBuffers; + BuffersType m_type = BuffersType::none; + + std::vector m_vertices; + + typedef std::unordered_map BufferOffsets; + BufferOffsets m_rectBufferOffsets; + + static const u32 m_bufMaxSize; + static const GLbitfield m_bufAccessBits; + static const GLbitfield m_bufMapBits; + }; + +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.cpp b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.cpp index cf2527eb..beea552f 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.cpp +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.cpp @@ -1,84 +1,106 @@ -#include -#include -#include -#include "opengl_ColorBufferReaderWithBufferStorage.h" - -using namespace graphics; -using namespace opengl; - -ColorBufferReaderWithBufferStorage::ColorBufferReaderWithBufferStorage(CachedTexture * _pTexture, - CachedBindBuffer * _bindBuffer) - : ColorBufferReader(_pTexture), m_bindBuffer(_bindBuffer) -{ - _initBuffers(); -} - -ColorBufferReaderWithBufferStorage::~ColorBufferReaderWithBufferStorage() -{ - _destroyBuffers(); -} - -void ColorBufferReaderWithBufferStorage::_initBuffers() -{ - m_numPBO = std::max(1u, config.frameBufferEmulation.copyToRDRAM); - if (m_numPBO > _maxPBO) - m_numPBO = _maxPBO; - - // Generate Pixel Buffer Objects - glGenBuffers(m_numPBO, m_PBO); - m_curIndex = 0; - - // Initialize Pixel Buffer Objects - for (u32 index = 0; index < m_numPBO; ++index) { - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[index])); - m_fence[index] = 0; - glBufferStorage(GL_PIXEL_PACK_BUFFER, m_pTexture->textureBytes, nullptr, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT | GL_CLIENT_STORAGE_BIT); - m_PBOData[index] = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_pTexture->textureBytes, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - } - - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); -} - -void ColorBufferReaderWithBufferStorage::_destroyBuffers() -{ - glDeleteBuffers(m_numPBO, m_PBO); - - for (u32 index = 0; index < m_numPBO; ++index) { - m_PBO[index] = 0; - glDeleteSync(m_fence[index]); - } -} - -const u8 * ColorBufferReaderWithBufferStorage::_readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, - u32& _stride) -{ - GLenum format = GLenum(_params.colorFormat); - GLenum type = GLenum(_params.colorType); - - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[m_curIndex])); - glReadPixels(_params.x0, _params.y0, m_pTexture->width, _params.height, format, type, 0); - - if (!_params.sync) { - //Setup a fence sync object so that we know when glReadPixels completes - m_fence[m_curIndex] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - m_curIndex = (m_curIndex + 1) % m_numPBO; - //Wait for glReadPixels to complete for the currently selected PBO - if (m_fence[m_curIndex] != 0) { - glClientWaitSync(m_fence[m_curIndex], 0, 100000000); - glDeleteSync(m_fence[m_curIndex]); - m_fence[m_curIndex] = 0; - } - } else { - glFinish(); - } - - _heightOffset = 0; - _stride = m_pTexture->width; - - return reinterpret_cast(m_PBOData[m_curIndex]); -} - -void ColorBufferReaderWithBufferStorage::cleanUp() -{ - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); -} +#include +#include +#include +#include "opengl_ColorBufferReaderWithBufferStorage.h" + +using namespace graphics; +using namespace opengl; + +ColorBufferReaderWithBufferStorage::ColorBufferReaderWithBufferStorage(CachedTexture * _pTexture, + CachedBindBuffer * _bindBuffer) + : ColorBufferReader(_pTexture), m_bindBuffer(_bindBuffer) +{ + _initBuffers(); +} + +ColorBufferReaderWithBufferStorage::~ColorBufferReaderWithBufferStorage() +{ + _destroyBuffers(); +} + +void ColorBufferReaderWithBufferStorage::_initBuffers() +{ + m_numPBO = std::max(1u, config.frameBufferEmulation.copyToRDRAM); + if (m_numPBO > _maxPBO) + m_numPBO = _maxPBO; + + // Generate Pixel Buffer Objects + glGenBuffers(m_numPBO, m_PBO); + + // Initialize Pixel Buffer Objects + for (u32 index = 0; index < m_numPBO; ++index) { + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[index])); + m_fence[index] = 0; + glBufferStorage(GL_PIXEL_PACK_BUFFER, m_pTexture->textureBytes, nullptr, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT | GL_CLIENT_STORAGE_BIT); + m_PBOData[index] = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_pTexture->textureBytes, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + } + + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); +} + +void ColorBufferReaderWithBufferStorage::_destroyBuffers() +{ + glDeleteBuffers(m_numPBO, m_PBO); + + for (u32 index = 0; index < m_numPBO; ++index) { + m_PBO[index] = 0; + glDeleteSync(m_fence[index]); + } +} + +const u8 * ColorBufferReaderWithBufferStorage::_readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, + u32& _stride) +{ + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[0])); + glReadPixels(_params.x0, _params.y0, m_pTexture->width, _params.height, + GLenum(_params.colorFormat), GLenum(_params.colorType), 0); + if (m_fence[0] != 0) + glDeleteSync(m_fence[0]); + m_fence[0] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + slotReady(0, true); + _heightOffset = 0; + _stride = m_pTexture->width; + return reinterpret_cast(m_PBOData[0]); +} + +void ColorBufferReaderWithBufferStorage::readToSlot(u32 _slot, s32 _x0, s32 _y0, u32 _height, u32 _size) +{ + ColorFormatParam format; + DatatypeParam type; + u32 bytes; + _slotFormat(_size, format, type, bytes); + + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[_slot])); + glReadPixels(_x0, _y0, m_pTexture->width, _height, GLenum(format), GLenum(type), 0); + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); + if (m_fence[_slot] != 0) + glDeleteSync(m_fence[_slot]); + m_fence[_slot] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +bool ColorBufferReaderWithBufferStorage::slotReady(u32 _slot, bool _wait) +{ + if (m_fence[_slot] == 0) + return true; + /* the poll flushes, so the fence is bound to signal; a wait has no + * limit, as the rows are not valid before it does */ + GLenum res = glClientWaitSync(m_fence[_slot], GL_SYNC_FLUSH_COMMANDS_BIT, 0); + while (_wait && res == GL_TIMEOUT_EXPIRED) + res = glClientWaitSync(m_fence[_slot], 0, 1000000000); + if (res == GL_TIMEOUT_EXPIRED) + return false; + glDeleteSync(m_fence[_slot]); + m_fence[_slot] = 0; + return true; +} + +const u8 * ColorBufferReaderWithBufferStorage::_mapSlot(u32 _slot, u32, u32, u32 & _stride) +{ + _stride = m_pTexture->width; + return reinterpret_cast(m_PBOData[_slot]); +} + +void ColorBufferReaderWithBufferStorage::cleanUp() +{ + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.h b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.h index 65ae65af..983ce5b3 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.h +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithBufferStorage.h @@ -1,35 +1,39 @@ -#pragma once - -#include -#include "opengl_CachedFunctions.h" -#include - -namespace opengl { - - class ColorBufferReaderWithBufferStorage : - public graphics::ColorBufferReader - { - public: - ColorBufferReaderWithBufferStorage(CachedTexture * _pTexture, - CachedBindBuffer * _bindBuffer); - virtual ~ColorBufferReaderWithBufferStorage(); - - const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) override; - - void cleanUp() override; - - private: - void _initBuffers(); - void _destroyBuffers(); - - CachedBindBuffer * m_bindBuffer; - - static const int _maxPBO = 3; - u32 m_numPBO; - GLuint m_PBO[_maxPBO]; - void* m_PBOData[_maxPBO]; - u32 m_curIndex; - GLsync m_fence[_maxPBO]; - }; - -} +#pragma once + +#include +#include "opengl_CachedFunctions.h" +#include + +namespace opengl { + + class ColorBufferReaderWithBufferStorage : + public graphics::ColorBufferReader + { + public: + ColorBufferReaderWithBufferStorage(CachedTexture * _pTexture, + CachedBindBuffer * _bindBuffer); + virtual ~ColorBufferReaderWithBufferStorage(); + + const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) override; + + void cleanUp() override; + + u32 numSlots() const override { return m_numPBO; } + void readToSlot(u32 _slot, s32 _x0, s32 _y0, u32 _height, u32 _size) override; + bool slotReady(u32 _slot, bool _wait) override; + + private: + const u8 * _mapSlot(u32 _slot, u32 _height, u32 _bytes, u32 & _stride) override; + void _initBuffers(); + void _destroyBuffers(); + + CachedBindBuffer * m_bindBuffer; + + static const int _maxPBO = 3; + u32 m_numPBO; + GLuint m_PBO[_maxPBO]; + void* m_PBOData[_maxPBO]; + GLsync m_fence[_maxPBO]; + }; + +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.cpp b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.cpp index 136331fd..0943ad1a 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.cpp +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.cpp @@ -1,74 +1,111 @@ #include -#include -#include -#include "opengl_ColorBufferReaderWithPixelBuffer.h" - -using namespace graphics; -using namespace opengl; - -ColorBufferReaderWithPixelBuffer::ColorBufferReaderWithPixelBuffer(CachedTexture *_pTexture, - CachedBindBuffer *_bindBuffer) - : ColorBufferReader(_pTexture), m_bindBuffer(_bindBuffer) -{ - _initBuffers(); -} - - -ColorBufferReaderWithPixelBuffer::~ColorBufferReaderWithPixelBuffer() -{ - _destroyBuffers(); -} - -void ColorBufferReaderWithPixelBuffer::_destroyBuffers() -{ - glDeleteBuffers(m_numPBO, m_PBO); - - for (u32 index = 0; index < m_numPBO; ++index) - m_PBO[index] = 0; -} - -void ColorBufferReaderWithPixelBuffer::_initBuffers() -{ - m_numPBO = std::max(1u, config.frameBufferEmulation.copyToRDRAM); - if (m_numPBO > _maxPBO) - m_numPBO = _maxPBO; - - // Generate Pixel Buffer Objects - glGenBuffers(m_numPBO, m_PBO); - m_curIndex = 0; - - // Initialize Pixel Buffer Objects - for (u32 i = 0; i < m_numPBO; ++i) { - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[i])); - glBufferData(GL_PIXEL_PACK_BUFFER, m_pTexture->textureBytes, nullptr, GL_DYNAMIC_READ); - } - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); -} - -const u8 * ColorBufferReaderWithPixelBuffer::_readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, - u32& _stride) -{ - GLenum format = GLenum(_params.colorFormat); - GLenum type = GLenum(_params.colorType); - - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[m_curIndex])); - glReadPixels(_params.x0, _params.y0, m_pTexture->width, _params.height, format, type, 0); - // If Sync, read pixels from the buffer, copy them to RDRAM. - // If not Sync, read pixels from the buffer, copy pixels from the previous buffer to RDRAM. - if (!_params.sync) { - m_curIndex = (m_curIndex + 1) % m_numPBO; - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[m_curIndex])); - } - - _heightOffset = 0; - _stride = m_pTexture->width; - - return reinterpret_cast(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, - m_pTexture->width * _params.height * _params.colorFormatBytes, GL_MAP_READ_BIT)); -} - -void ColorBufferReaderWithPixelBuffer::cleanUp() -{ - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); -} +#include +#include +#include "opengl_ColorBufferReaderWithPixelBuffer.h" + +using namespace graphics; +using namespace opengl; + +ColorBufferReaderWithPixelBuffer::ColorBufferReaderWithPixelBuffer(CachedTexture *_pTexture, + CachedBindBuffer *_bindBuffer) + : ColorBufferReader(_pTexture), m_bindBuffer(_bindBuffer) +{ + _initBuffers(); +} + + +ColorBufferReaderWithPixelBuffer::~ColorBufferReaderWithPixelBuffer() +{ + _destroyBuffers(); +} + +void ColorBufferReaderWithPixelBuffer::_destroyBuffers() +{ + glDeleteBuffers(m_numPBO, m_PBO); + + for (u32 index = 0; index < m_numPBO; ++index) { + m_PBO[index] = 0; + if (m_fence[index] != 0) + glDeleteSync(m_fence[index]); + m_fence[index] = 0; + } +} + +void ColorBufferReaderWithPixelBuffer::_initBuffers() +{ + m_numPBO = std::max(1u, config.frameBufferEmulation.copyToRDRAM); + if (m_numPBO > _maxPBO) + m_numPBO = _maxPBO; + + // Generate Pixel Buffer Objects + glGenBuffers(m_numPBO, m_PBO); + m_mapped = false; + + // Initialize Pixel Buffer Objects + for (u32 i = 0; i < m_numPBO; ++i) { + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[i])); + glBufferData(GL_PIXEL_PACK_BUFFER, m_pTexture->textureBytes, nullptr, GL_DYNAMIC_READ); + m_fence[i] = 0; + } + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); +} + +const u8 * ColorBufferReaderWithPixelBuffer::_readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, + u32& _stride) +{ + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[0])); + glReadPixels(_params.x0, _params.y0, m_pTexture->width, _params.height, + GLenum(_params.colorFormat), GLenum(_params.colorType), 0); + _heightOffset = 0; + _stride = m_pTexture->width; + m_mapped = true; + return reinterpret_cast(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, + m_pTexture->width * _params.height * _params.colorFormatBytes, GL_MAP_READ_BIT)); +} + +void ColorBufferReaderWithPixelBuffer::readToSlot(u32 _slot, s32 _x0, s32 _y0, u32 _height, u32 _size) +{ + ColorFormatParam format; + DatatypeParam type; + u32 bytes; + _slotFormat(_size, format, type, bytes); + + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[_slot])); + glReadPixels(_x0, _y0, m_pTexture->width, _height, GLenum(format), GLenum(type), 0); + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); + if (m_fence[_slot] != 0) + glDeleteSync(m_fence[_slot]); + m_fence[_slot] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +bool ColorBufferReaderWithPixelBuffer::slotReady(u32 _slot, bool _wait) +{ + if (m_fence[_slot] == 0) + return true; + /* mapping before the fence signals would stall in the driver */ + GLenum res = glClientWaitSync(m_fence[_slot], GL_SYNC_FLUSH_COMMANDS_BIT, 0); + while (_wait && res == GL_TIMEOUT_EXPIRED) + res = glClientWaitSync(m_fence[_slot], 0, 1000000000); + if (res == GL_TIMEOUT_EXPIRED) + return false; + glDeleteSync(m_fence[_slot]); + m_fence[_slot] = 0; + return true; +} + +const u8 * ColorBufferReaderWithPixelBuffer::_mapSlot(u32 _slot, u32 _height, u32 _bytes, u32 & _stride) +{ + _stride = m_pTexture->width; + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle(m_PBO[_slot])); + m_mapped = true; + return reinterpret_cast(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, + m_pTexture->width * _height * _bytes, GL_MAP_READ_BIT)); +} + +void ColorBufferReaderWithPixelBuffer::cleanUp() +{ + if (m_mapped) + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + m_mapped = false; + m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null); +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.h b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.h index 2662a7a6..fb7548da 100644 --- a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.h +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_ColorBufferReaderWithPixelBuffer.h @@ -1,30 +1,36 @@ -#pragma once -#include -#include "opengl_CachedFunctions.h" - -namespace opengl { - -class ColorBufferReaderWithPixelBuffer : - public graphics::ColorBufferReader -{ -public: - ColorBufferReaderWithPixelBuffer(CachedTexture * _pTexture, - CachedBindBuffer * _bindBuffer); - ~ColorBufferReaderWithPixelBuffer(); - - const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) override; - void cleanUp() override; - -private: - void _initBuffers(); - void _destroyBuffers(); - - CachedBindBuffer * m_bindBuffer; - - u32 m_numPBO; - static const int _maxPBO = 3; - GLuint m_PBO[_maxPBO]; - u32 m_curIndex; -}; - -} +#pragma once +#include +#include "opengl_CachedFunctions.h" + +namespace opengl { + +class ColorBufferReaderWithPixelBuffer : + public graphics::ColorBufferReader +{ +public: + ColorBufferReaderWithPixelBuffer(CachedTexture * _pTexture, + CachedBindBuffer * _bindBuffer); + ~ColorBufferReaderWithPixelBuffer(); + + const u8 * _readPixels(const ReadColorBufferParams& _params, u32& _heightOffset, u32& _stride) override; + void cleanUp() override; + + u32 numSlots() const override { return m_numPBO; } + void readToSlot(u32 _slot, s32 _x0, s32 _y0, u32 _height, u32 _size) override; + bool slotReady(u32 _slot, bool _wait) override; + +private: + const u8 * _mapSlot(u32 _slot, u32 _height, u32 _bytes, u32 & _stride) override; + void _initBuffers(); + void _destroyBuffers(); + + CachedBindBuffer * m_bindBuffer; + + u32 m_numPBO; + static const int _maxPBO = 3; + GLuint m_PBO[_maxPBO]; + GLsync m_fence[_maxPBO]; + bool m_mapped; +}; + +} diff --git a/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_RingSegments.h b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_RingSegments.h new file mode 100644 index 00000000..06b59d58 --- /dev/null +++ b/mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_RingSegments.h @@ -0,0 +1,67 @@ +#pragma once +#include + +namespace opengl { + +/* Where the draw data goes in a persistently mapped ring: in Segments + * parts, each fenced when the writes move on from it and waited for + * before they come back to it. A wait holds only for draws a whole lap + * behind, and the ring cannot overwrite what the GPU has yet to read. */ +template +class RingSegments +{ +public: + void init(u32 _size) + { + m_size = _size; + m_segSize = _size / Segments; + m_cur = 0; + for (u32 i = 0; i < Segments; ++i) + m_fenced[i] = false; + } + + /* The offset for _bytes (at most a segment) at _offset, moved on to + * the next segment when they would straddle two, so each draw reads + * from one: ops.fence(seg) marks the draws so far when the writes + * leave a segment, ops.wait(seg) waits for that mark when they come + * back to it. */ + template + u32 place(u32 _offset, u32 _bytes, Ops & _ops) + { + u32 start = _offset; + if (_seg(start) != _seg(start + _bytes - 1)) + start = (_seg(start) + 1) * m_segSize; + if (start + _bytes > m_size || _seg(start) != _seg(start + _bytes - 1)) + start = 0; + const u32 target = _seg(start); + /* going round to an earlier segment, or to this one from its end */ + bool round = start < _offset && target == m_cur; + while (m_cur != target || round) { + _ops.fence(m_cur); + m_fenced[m_cur] = true; + m_cur = (m_cur + 1) % Segments; + if (m_fenced[m_cur]) { + _ops.wait(m_cur); + m_fenced[m_cur] = false; + } + round = false; + } + return start; + } + + u32 segment() const { return m_cur; } + +private: + u32 _seg(u32 _at) const + { + const u32 seg = _at / m_segSize; + return seg < Segments ? seg : Segments - 1; + } + + u32 m_size = 0; + u32 m_segSize = 0; + u32 m_cur = 0; + bool m_fenced[Segments]; +}; + +} diff --git a/tools/ci/gl_renderers.py b/tools/ci/gl_renderers.py new file mode 100644 index 00000000..c28d95a1 --- /dev/null +++ b/tools/ci/gl_renderers.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""GL renderers lane. + +Runs the core's OpenGL renderers (Glide64, GLideN64, Rice, glN64) under +tools/lrhost_gl, headless on Mesa's llvmpipe (EGL_PLATFORM=surfaceless, no +GPU), over krom's RDP test ROMs, fetched as parallel_rdp_regress.py fetches +them (same folder, same pinned commit). The GL renderers are not bit-exact +across drivers, so the frames are not held to stored hashes; the lane holds them to: + + * every renderer running every ROM to the end, and all but glN64 (which + presents nothing for display lists sent straight to the RDP) presenting + frames: Glide64 and Rice used to crash on a display list given as a + KSEG address, as homebrew gives it, and glN64's GBI state, merged into + GLideN64's under -fcommon, crashed at exit; + * GLideN64 drawing the same frames whatever its colour buffer copy to + RDRAM is set to (Off, Sync, Async, TripleBuffer): the copy writes RDRAM + and must not disturb what is drawn, its pixel buffers or the vertex + ring the draws read. + + gl_renderers.py --core parallel_n64_libretro.so --host ./lrhost_gl [--roms krom-roms] +""" +import argparse, os, subprocess, sys, tempfile + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from parallel_rdp_regress import fetch # noqa: E402 + +ROMS = ["cube16", "rottri32", "rottri32aa", "shade32", "tlut16", "zbuf16"] +PLUGINS = ["glide64", "gliden64", "rice", "gln64"] +PRESENTS_NOTHING = {"gln64"} +# Rice crashes in the driver drawing a frame the CPU left in RDRAM (its +# DrawFrameBuffer); until that is fixed it runs the ROMs that draw. +SKIP = {"rice": {"shade32", "tlut16", "zbuf16"}} +COPY_MODES = ["Off", "Sync", "Async", "TripleBuffer"] +FRAMES = 40 + + +def run(host, core, rom, plugin, extra, tmp): + hashlog = os.path.join(tmp, "hash") + if os.path.exists(hashlog): + os.remove(hashlog) + env = dict(os.environ, EGL_PLATFORM="surfaceless", LRHOST_HASHLOG=hashlog) + args = [host, core, rom, str(FRAMES), + "parallel-n64-gfxplugin=" + plugin, + "parallel-n64-rspplugin=hle", + "parallel-n64-cpucore=cached_interpreter"] + extra + p = subprocess.run(args, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + timeout=600) + out = p.stdout.decode("utf-8", "replace") + frames = [] + if os.path.exists(hashlog): + with open(hashlog) as f: + frames = [line.split()[-1] for line in f if line.strip()] + return p.returncode, frames, out + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--core", required=True) + ap.add_argument("--host", required=True) + ap.add_argument("--roms", default="krom-roms") + a = ap.parse_args() + + failures = 0 + with tempfile.TemporaryDirectory() as tmp: + for rom in ROMS: + path = fetch(a.roms, rom) + for plugin in PLUGINS: + if rom in SKIP.get(plugin, ()): + continue + rc, frames, out = run(a.host, a.core, path, plugin, [], tmp) + ok = rc == 0 and (len(frames) > 0 or plugin in PRESENTS_NOTHING) + print("%-10s %-9s %s" % (rom, plugin, "ok" if ok else "FAILED (exit %d, %d frames)" % (rc, len(frames)))) + if not ok: + failures += 1 + sys.stdout.write(out[-2000:]) + reference = None + for mode in COPY_MODES: + rc, frames, out = run(a.host, a.core, path, "gliden64", + ["parallel-n64-gliden64-EnableFBEmulation=True", + "parallel-n64-gliden64-EnableCopyColorToRDRAM=" + mode], tmp) + if rc != 0 or not frames: + print("%-10s gliden64 copy %-12s FAILED (exit %d)" % (rom, mode, rc)) + sys.stdout.write(out[-2000:]) + failures += 1 + continue + if reference is None: + reference = frames + elif frames != reference: + print("%-10s gliden64 copy %-12s draws other frames than copy Off" % (rom, mode)) + failures += 1 + continue + print("%-10s gliden64 copy %-12s ok" % (rom, mode)) + + if failures: + print("%d failures" % failures) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/lrhost.c b/tools/lrhost.c index 084cb280..ff478ef4 100644 --- a/tools/lrhost.c +++ b/tools/lrhost.c @@ -52,6 +52,9 @@ #include #include #include +#ifdef LRHOST_GL +#include "lrhost_gl.h" +#endif #ifdef LRHOST_VULKAN #include "lrhost_vulkan.h" #endif @@ -87,6 +90,15 @@ static unsigned long swfb_lent, swfb_frames, swfb_hits; static void video_cb(const void *data, unsigned w, unsigned h, size_t pitch) { if (hidden_run) return; +#ifdef LRHOST_GL + if (data == RETRO_HW_FRAME_BUFFER_VALID) + { + data = gl_read_frame(w, h); + pitch = (size_t)w * 4; + pixfmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!data) { frames_duped++; return; } + } +#endif #ifdef LRHOST_VULKAN /* a HW frame: read the image back and treat it like a 32-bit frame */ if (data == RETRO_HW_FRAME_BUFFER_VALID) @@ -263,6 +275,19 @@ static bool env_cb(unsigned cmd, void *data) *(bool*)data = true; return true; case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE: *(int*)data = hidden_run ? 0 : 3; return true; +#ifdef LRHOST_GL + case RETRO_ENVIRONMENT_SET_HW_RENDER: + { + struct retro_hw_render_callback *cb = (struct retro_hw_render_callback*)data; + if (cb->context_type != RETRO_HW_CONTEXT_OPENGL && cb->context_type != RETRO_HW_CONTEXT_OPENGL_CORE) return false; + cb->get_current_framebuffer = gl_get_current_framebuffer; + cb->get_proc_address = gl_get_proc_address; + gl_hw = *cb; + return true; + } + case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER: + *(unsigned*)data = RETRO_HW_CONTEXT_OPENGL; return true; +#endif #ifdef LRHOST_VULKAN case RETRO_ENVIRONMENT_SET_HW_RENDER: { @@ -300,7 +325,7 @@ static bool env_cb(unsigned cmd, void *data) if (!vk_ready) { vk_up_announced++; return false; } *(const struct retro_hw_render_upscaler_interface**)data = &vk_up_iface; return true; -#else +#elif !defined(LRHOST_GL) case RETRO_ENVIRONMENT_SET_HW_RENDER: return false; /* software renderers only */ case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER: @@ -421,6 +446,15 @@ int main(int argc, char **argv) game.data = malloc(game.size); if (fread((void*)game.data, 1, game.size, f) != game.size) return 1; fclose(f); game.path = argv[2]; game.meta = NULL; if (!r_load(&game)) { fprintf(stderr, "retro_load_game failed\n"); return 1; } +#ifdef LRHOST_GL + if (gl_hw.context_type == RETRO_HW_CONTEXT_OPENGL || gl_hw.context_type == RETRO_HW_CONTEXT_OPENGL_CORE) + { + struct retro_system_av_info gav; + r_get_av(&gav); + if (!gl_init(gav.geometry.max_width, gav.geometry.max_height)) return 1; + if (gl_hw.context_reset) gl_hw.context_reset(); + } +#endif #ifdef LRHOST_VULKAN if (vk_hw.context_type == RETRO_HW_CONTEXT_VULKAN) { @@ -543,6 +577,9 @@ int main(int argc, char **argv) frames_presented, frames_duped, frame_crc); free(s); } +#ifdef LRHOST_GL + gl_shutdown(); +#endif #ifdef LRHOST_VULKAN if (vk_up_on) printf("upscaler: asked for in retro_load_game %d, frames with inputs %ld\n", vk_up_announced, vk_up_frames); diff --git a/tools/lrhost_gl.h b/tools/lrhost_gl.h new file mode 100644 index 00000000..e685278a --- /dev/null +++ b/tools/lrhost_gl.h @@ -0,0 +1,158 @@ +/* OpenGL HW render support for lrhost, built when LRHOST_GL is defined + * (lrhost_gl). Headless: an EGL context with no surface (Mesa's + * EGL_PLATFORM=surfaceless and llvmpipe need no GPU), of the profile and + * version the core asks for, and a framebuffer object the core draws into. + * Every presented frame is read back, flipped to top-down rows, so a frame + * can be signed and dumped exactly as for a software core. */ +#include +#include + +#define GLH_RGBA 0x1908 +#define GLH_UNSIGNED_BYTE 0x1401 +#define GLH_FRAMEBUFFER 0x8D40 +#define GLH_READ_FRAMEBUFFER 0x8CA8 +#define GLH_RENDERBUFFER 0x8D41 +#define GLH_COLOR_ATTACHMENT0 0x8CE0 +#define GLH_DEPTH_ATTACHMENT 0x8D00 +#define GLH_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GLH_RGBA8 0x8058 +#define GLH_DEPTH24_STENCIL8 0x88F0 +#define GLH_DEPTH_COMPONENT24 0x81A6 +#define GLH_PACK_ALIGNMENT 0x0D05 +#define GLH_PIXEL_PACK_BUFFER 0x88EB + +typedef unsigned int GLHuint; +typedef int GLHint; +typedef unsigned int GLHenum; +typedef int GLHsizei; + +static struct retro_hw_render_callback gl_hw; +static EGLDisplay gl_dpy = EGL_NO_DISPLAY; +static EGLContext gl_egl_ctx = EGL_NO_CONTEXT; +static GLHuint gl_fbo, gl_rb_color, gl_rb_depth; +static unsigned gl_fb_w, gl_fb_h; +static uint32_t *gl_frame; static size_t gl_frame_cap; + +static void (*p_glGenFramebuffers)(GLHsizei, GLHuint *); +static void (*p_glBindFramebuffer)(GLHenum, GLHuint); +static void (*p_glGenRenderbuffers)(GLHsizei, GLHuint *); +static void (*p_glBindRenderbuffer)(GLHenum, GLHuint); +static void (*p_glRenderbufferStorage)(GLHenum, GLHenum, GLHsizei, GLHsizei); +static void (*p_glFramebufferRenderbuffer)(GLHenum, GLHenum, GLHenum, GLHuint); +static void (*p_glReadPixels)(GLHint, GLHint, GLHsizei, GLHsizei, GLHenum, GLHenum, void *); +static void (*p_glPixelStorei)(GLHenum, GLHint); +static void (*p_glBindBuffer)(GLHenum, GLHuint); +static void (*p_glFinish)(void); + +static uintptr_t gl_get_current_framebuffer(void) { return gl_fbo; } + +static retro_proc_address_t gl_get_proc_address(const char *sym) +{ + return (retro_proc_address_t)eglGetProcAddress(sym); +} + +static int gl_init(unsigned max_w, unsigned max_h) +{ + EGLint major, minor, n; + EGLConfig cfg; + const EGLint cfg_attr[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE }; + EGLint ctx_attr[16]; + int i = 0; + PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = + (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + +#ifdef EGL_PLATFORM_SURFACELESS_MESA + if (get_platform_display) + gl_dpy = get_platform_display(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL); +#endif + if (gl_dpy == EGL_NO_DISPLAY) + gl_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (gl_dpy == EGL_NO_DISPLAY || !eglInitialize(gl_dpy, &major, &minor)) + { fprintf(stderr, "lrhost_gl: no EGL display\n"); return 0; } + if (!eglBindAPI(EGL_OPENGL_API) || !eglChooseConfig(gl_dpy, cfg_attr, &cfg, 1, &n) || n < 1) + { fprintf(stderr, "lrhost_gl: no OpenGL config\n"); return 0; } + + if (gl_hw.context_type == RETRO_HW_CONTEXT_OPENGL_CORE) + { + ctx_attr[i++] = EGL_CONTEXT_MAJOR_VERSION; ctx_attr[i++] = (EGLint)gl_hw.version_major; + ctx_attr[i++] = EGL_CONTEXT_MINOR_VERSION; ctx_attr[i++] = (EGLint)gl_hw.version_minor; + ctx_attr[i++] = EGL_CONTEXT_OPENGL_PROFILE_MASK; ctx_attr[i++] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT; + } + else + { + ctx_attr[i++] = EGL_CONTEXT_OPENGL_PROFILE_MASK; ctx_attr[i++] = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT; + } + ctx_attr[i++] = EGL_NONE; + gl_egl_ctx = eglCreateContext(gl_dpy, cfg, EGL_NO_CONTEXT, ctx_attr); + if (gl_egl_ctx == EGL_NO_CONTEXT || !eglMakeCurrent(gl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, gl_egl_ctx)) + { fprintf(stderr, "lrhost_gl: cannot make a %s %u.%u context current\n", + gl_hw.context_type == RETRO_HW_CONTEXT_OPENGL_CORE ? "core" : "compatibility", + gl_hw.version_major, gl_hw.version_minor); return 0; } + +#define GLP(n) *(void **)&p_##n = (void *)eglGetProcAddress(#n); if (!p_##n) { fprintf(stderr, "lrhost_gl: no %s\n", #n); return 0; } + GLP(glGenFramebuffers); GLP(glBindFramebuffer); GLP(glGenRenderbuffers); GLP(glBindRenderbuffer); + GLP(glRenderbufferStorage); GLP(glFramebufferRenderbuffer); GLP(glReadPixels); GLP(glPixelStorei); + GLP(glBindBuffer); GLP(glFinish); +#undef GLP + + gl_fb_w = max_w; gl_fb_h = max_h; + p_glGenFramebuffers(1, &gl_fbo); + p_glBindFramebuffer(GLH_FRAMEBUFFER, gl_fbo); + p_glGenRenderbuffers(1, &gl_rb_color); + p_glBindRenderbuffer(GLH_RENDERBUFFER, gl_rb_color); + p_glRenderbufferStorage(GLH_RENDERBUFFER, GLH_RGBA8, (GLHsizei)max_w, (GLHsizei)max_h); + p_glFramebufferRenderbuffer(GLH_FRAMEBUFFER, GLH_COLOR_ATTACHMENT0, GLH_RENDERBUFFER, gl_rb_color); + if (gl_hw.depth) + { + p_glGenRenderbuffers(1, &gl_rb_depth); + p_glBindRenderbuffer(GLH_RENDERBUFFER, gl_rb_depth); + p_glRenderbufferStorage(GLH_RENDERBUFFER, gl_hw.stencil ? GLH_DEPTH24_STENCIL8 : GLH_DEPTH_COMPONENT24, + (GLHsizei)max_w, (GLHsizei)max_h); + p_glFramebufferRenderbuffer(GLH_FRAMEBUFFER, gl_hw.stencil ? GLH_DEPTH_STENCIL_ATTACHMENT : GLH_DEPTH_ATTACHMENT, + GLH_RENDERBUFFER, gl_rb_depth); + } + p_glBindRenderbuffer(GLH_RENDERBUFFER, 0); + return 1; +} + +/* the frame the core drew, top-down XRGB8888 */ +static const uint32_t *gl_read_frame(unsigned w, unsigned h) +{ + size_t need = (size_t)w * h, y; + uint32_t *rows; + if (!w || !h || w > gl_fb_w || h > gl_fb_h) return NULL; + if (need * 2 > gl_frame_cap) + { + free(gl_frame); + gl_frame_cap = need * 2; + gl_frame = (uint32_t *)malloc(gl_frame_cap * 4); + if (!gl_frame) { gl_frame_cap = 0; return NULL; } + } + rows = gl_frame + need; + /* the core's state is its own: only what the read needs is changed */ + p_glBindBuffer(GLH_PIXEL_PACK_BUFFER, 0); + p_glPixelStorei(GLH_PACK_ALIGNMENT, 4); + p_glBindFramebuffer(GLH_READ_FRAMEBUFFER, gl_fbo); + p_glReadPixels(0, 0, (GLHsizei)w, (GLHsizei)h, GLH_RGBA, GLH_UNSIGNED_BYTE, rows); + for (y = 0; y < h; y++) + { + const uint8_t *src = (const uint8_t *)(gl_hw.bottom_left_origin ? rows + (h - 1 - y) * w : rows + y * w); + uint32_t *dst = gl_frame + y * w; + unsigned x; + for (x = 0; x < w; x++) + dst[x] = ((uint32_t)src[4 * x] << 16) | ((uint32_t)src[4 * x + 1] << 8) | src[4 * x + 2]; + } + return gl_frame; +} + +static void gl_shutdown(void) +{ + if (gl_hw.context_destroy) gl_hw.context_destroy(); + if (gl_dpy != EGL_NO_DISPLAY) + { + eglMakeCurrent(gl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (gl_egl_ctx != EGL_NO_CONTEXT) eglDestroyContext(gl_dpy, gl_egl_ctx); + eglTerminate(gl_dpy); + } + free(gl_frame); +} diff --git a/tools/test_glide64_fb_readback.c b/tools/test_glide64_fb_readback.c new file mode 100644 index 00000000..553294aa --- /dev/null +++ b/tools/test_glide64_fb_readback.c @@ -0,0 +1,195 @@ +/* test_glide64_fb_readback -- Glide64's colour image copy to RDRAM, + * converted as it is read. + * + * The copy used to read the whole window back, convert all of it to 565 + * in a window-sized buffer (flipping it), and then sample the N64 pixels + * from that and convert them again. Now only the rect the N64 pixels fall + * in is read, and each sampled pixel is converted once, straight from the + * RGBA8 rows. This holds the RDRAM the copy writes to what the two-pass + * copy wrote, at 1:1 and scaled, over window sizes, scales, offsets, + * colour image sizes, 16- and 32-bit images and the transparent-black + * rule, with the rows read back exactly as long as they are so a read + * past them shows under the address sanitizer. + */ +#include +#include +#include + +#include "../glide2gl/src/Glide64/FBReadback.h" + +/* the window, GL rows: the bottom row first */ +static uint8_t *window; +static int win_w, win_h; + +static void fill_window(unsigned seed) +{ + int i; + for (i = 0; i < win_w * win_h * 4; i++) + { + seed = seed * 1103515245u + 12345u; + window[i] = (uint8_t)(seed >> 16); + /* black now and then, for the transparent-black rule */ + if ((seed >> 8) % 7 == 0) + window[i] = 0; + } +} + +/* glReadPixels of the rect at upper-left (x, y), into its own buffer */ +static uint8_t *read_rect(int x, int y, int w, int h) +{ + uint8_t *out = (uint8_t*)malloc((size_t)w * h * 4); + int r; + for (r = 0; r < h; r++) + { + int gl_row = (win_h - (y + h)) + r; + memcpy(out + (size_t)r * w * 4, window + ((size_t)gl_row * win_w + x) * 4, (size_t)w * 4); + } + return out; +} + +/* the old copy: the window in 565, upper-left origin */ +static uint16_t *window_565(void) +{ + uint16_t *fb = (uint16_t*)malloc((size_t)win_w * win_h * 2); + int i, j; + for (j = 0; j < win_h; j++) + for (i = 0; i < win_w; i++) + { + const uint8_t *p = window + ((size_t)j * win_w + i) * 4; + fb[(win_h - j - 1) * win_w + i] = (uint16_t)(((p[0] >> 3) << 11) | ((p[1] >> 2) << 5) | (p[2] >> 3)); + } + return fb; +} + +static void old_direct(uint32_t ox, uint32_t oy, uint32_t width, uint32_t height, + int read_alpha, int size16, uint8_t *dst) +{ + uint16_t *fb = window_565(); + uint32_t x, y; + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + { + uint16_t c = fb[(oy + y) * win_w + ox + x]; + if (read_alpha && c <= 0) {} + else + c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; + fb_store(dst, size16, width, x, y, c); + } + free(fb); +} + +static void old_sampled(float scale_x, float scale_y, float offset_x, float offset_y, + int x_start, int y_start, int x_end, int y_end, uint32_t width, + int read_alpha, int size16, uint8_t *dst) +{ + uint16_t *fb = window_565(); + int x, y; + for (y = y_start; y < y_end; y++) + for (x = x_start; x < x_end; x++) + { + uint16_t c = fb[(int)(x*scale_x + offset_x) + (int)(y * scale_y + offset_y) * win_w]; + c = (c&0xFFC0) | ((c&0x001F) << 1) | 1; + if (read_alpha && c == 1) + c = 0; + fb_store(dst, size16, width, (uint32_t)x, (uint32_t)y, c); + } + free(fb); +} + +static int failures; + +static void compare(const char *what, const uint8_t *a, const uint8_t *b, size_t n, + int ww, int wh, uint32_t width, uint32_t height, int size16, int read_alpha) +{ + if (memcmp(a, b, n) != 0) + { + if (failures++ < 10) + fprintf(stderr, "%s: window %dx%d image %ux%u %s alpha %d differs\n", + what, ww, wh, width, height, size16 ? "16-bit" : "32-bit", read_alpha); + } +} + +int main(void) +{ + static const int wins[][2] = { { 320, 240 }, { 640, 480 }, { 1280, 960 }, { 960, 720 }, { 1600, 1200 } }; + static const uint32_t images[][2] = { { 320, 240 }, { 256, 224 }, { 640, 480 }, { 160, 120 }, { 32, 32 } }; + unsigned wi, ii, runs = 0; + + for (wi = 0; wi < sizeof(wins) / sizeof(wins[0]); wi++) + { + win_w = wins[wi][0]; + win_h = wins[wi][1]; + window = (uint8_t*)malloc((size_t)win_w * win_h * 4); + fill_window(wi * 77 + 1); + + for (ii = 0; ii < sizeof(images) / sizeof(images[0]); ii++) + { + const uint32_t width = images[ii][0], height = images[ii][1]; + int size16, read_alpha, bar; + for (size16 = 0; size16 <= 1; size16++) + for (read_alpha = 0; read_alpha <= 1; read_alpha++) + { + const size_t bytes = (size_t)width * height * (size16 ? 2 : 4) + 4; + uint8_t *a = (uint8_t*)malloc(bytes), *b = (uint8_t*)malloc(bytes); + + /* 1:1, at an offset that keeps the image inside the window */ + if (width <= (uint32_t)win_w && height <= (uint32_t)win_h) + { + uint32_t ox = (win_w - width) / 2, oy = (win_h - height) / 3; + int32_t rx = (int32_t)ox, ry = (int32_t)oy, rw = (int32_t)width, rh = (int32_t)height; + uint8_t *rows = read_rect(rx, ry, rw, rh); + memset(a, 0x5A, bytes); + memset(b, 0x5A, bytes); + old_direct(ox, oy, width, height, read_alpha, size16, a); + fb_readback_direct(rows, (uint32_t)rw * 4, width, height, width, read_alpha, size16, b); + compare("1:1", a, b, bytes, win_w, win_h, width, height, size16, read_alpha); + free(rows); + ++runs; + } + + /* scaled to fill the window, with or without the BAR rect */ + for (bar = 0; bar <= 1; bar++) + { + float offset_x = (float)(win_w / 16), offset_y = (float)(win_h / 24); + float scale_x = (win_w - offset_x*2.0f) / width; + float scale_y = (win_h - offset_y*2.0f) / height; + int x_start = 0, y_start = 0, x_end = (int)width, y_end = (int)height; + int32_t sx0, sy0, rx, ry, rw, rh; + uint8_t *rows; + if (bar) + { + if (width < 240 || height < 86) + continue; + x_start = 80; y_start = 24; x_end = 240; y_end = 86; + } + sx0 = (int)(x_start*scale_x + offset_x); + sy0 = (int)(y_start * scale_y + offset_y); + rx = sx0; ry = sy0; + rw = (int)((x_end - 1)*scale_x + offset_x) - sx0 + 1; + rh = (int)((y_end - 1) * scale_y + offset_y) - sy0 + 1; + rows = read_rect(rx, ry, rw, rh); + memset(a, 0x5A, bytes); + memset(b, 0x5A, bytes); + old_sampled(scale_x, scale_y, offset_x, offset_y, x_start, y_start, x_end, y_end, + width, read_alpha, size16, a); + fb_readback_sampled(rows, (uint32_t)rw * 4, rx, ry, rw, rh, scale_x, scale_y, + offset_x, offset_y, x_start, y_start, x_end, y_end, width, read_alpha, size16, b); + compare(bar ? "scaled BAR" : "scaled", a, b, bytes, win_w, win_h, width, height, size16, read_alpha); + free(rows); + ++runs; + } + free(a); + free(b); + } + } + free(window); + } + + if (failures) + { + fprintf(stderr, "test_glide64_fb_readback: %d failures\n", failures); + return 1; + } + printf("test_glide64_fb_readback: %u runs ok\n", runs); + return 0; +} diff --git a/tools/test_gliden64_deferred_readback.cpp b/tools/test_gliden64_deferred_readback.cpp new file mode 100644 index 00000000..d45f0423 --- /dev/null +++ b/tools/test_gliden64_deferred_readback.cpp @@ -0,0 +1,188 @@ +/* test_gliden64_deferred_readback -- the order of GLideN64's asynchronous + * colour copies to RDRAM. + * + * The asynchronous copy used to read into one pixel buffer and write the + * other: the read before, whatever it had been read for, at the address + * and size of the copy at hand, and on the first copy a buffer nothing had + * been read into. It also waited for that read on every copy. Now each + * read keeps what it was read for, is written once the GPU has finished + * it, oldest first, and a copy waits only when the slot it reuses is still + * in flight. + * + * A GPU is simulated that finishes each read a number of copies after it + * was issued, over ring sizes, lags and synchronous copies in between. The + * checks are run on a model of the old protocol too, which must fail them. + */ +#include +#include +#include + +#include "../mupen64plus-video-gliden64/src/BufferCopy/DeferredReadback.h" + +struct Geometry +{ + u32 id; +}; + +struct Write +{ + u32 id; /* the read the rows came from */ + u32 geom; /* what they were written as */ +}; + +struct Gpu +{ + u32 lag; /* copies until a read is finished */ + u32 now; /* the copy under way */ + std::vector issuedAt; + std::vector slotRead; /* the read in each slot, -1 for none */ + u32 waits; + std::vector writes; + + Gpu(u32 _lag, u32 _slots) : lag(_lag), now(0), slotRead(_slots, -1), waits(0) {} + + bool finished(u32 _slot) const + { + const int read = slotRead[_slot]; + return read < 0 || now >= issuedAt[read] + lag; + } +}; + +struct Ops : DeferredReadback::Ops +{ + Gpu &gpu; + explicit Ops(Gpu &_gpu) : gpu(_gpu) {} + bool ready(u32 _slot, bool _wait) override + { + if (gpu.finished(_slot)) + return true; + if (_wait) + ++gpu.waits; + return _wait; + } + void write(u32 _slot, const Geometry &_g) override + { + Write w; + w.id = gpu.slotRead[_slot] < 0 ? ~0u : u32(gpu.slotRead[_slot]); + w.geom = _g.id; + gpu.writes.push_back(w); + } +}; + +static int failures; +static bool quiet; + +#define CHECK(cond, ...) do { if (!(cond)) { ++failures; if (!quiet) { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); } return false; } } while (0) + +/* One run: copies, every syncEvery-th synchronous; returns the checks. */ +template +static bool run(u32 _slots, u32 _lag, u32 _syncEvery) +{ + const u32 copies = 40; + Gpu gpu(_lag, _slots); + Protocol p(_slots); + std::vector sync(copies, false); + + for (u32 k = 0; k < copies; ++k) { + gpu.now = k; + sync[k] = _syncEvery && (k % _syncEvery) == _syncEvery - 1; + p.copy(gpu, k, sync[k]); + } + + /* every write is the read it was meant as, once, in order */ + int last = -1; + std::vector seen(copies, false); + for (const Write &w : gpu.writes) { + CHECK(w.id != ~0u, "slots %u lag %u: wrote a buffer nothing was read into", _slots, _lag); + CHECK(w.id == w.geom, "slots %u lag %u: read %u written as copy %u", _slots, _lag, w.id, w.geom); + CHECK(!seen[w.id], "slots %u lag %u: read %u written twice", _slots, _lag, w.id); + CHECK(int(w.id) > last, "slots %u lag %u: read %u written after %d", _slots, _lag, w.id, last); + seen[w.id] = true; + last = int(w.id); + } + /* a synchronous copy has everything before it, and itself, in RDRAM */ + for (u32 k = 0; k < copies; ++k) { + if (!sync[k]) + continue; + for (u32 j = 0; j <= k; ++j) + CHECK(seen[j], "slots %u lag %u: read %u missing at synchronous copy %u", _slots, _lag, j, k); + } + /* no wait while the GPU keeps within the ring */ + if (_lag <= _slots && !_syncEvery) + CHECK(gpu.waits == 0, "slots %u lag %u: %u waits", _slots, _lag, gpu.waits); + return true; +} + +struct Current +{ + DeferredReadback r; + explicit Current(u32 _slots) { r.reset(_slots); } + void copy(Gpu &gpu, u32 k, bool _sync) + { + Ops ops(gpu); + const u32 slot = _sync ? r.drain(ops) : r.begin(ops); + gpu.slotRead[slot] = int(k); + gpu.issuedAt.push_back(k); + Geometry g = { k }; + if (_sync) { + ops.ready(slot, true); + ops.write(slot, g); + } else { + r.issued(g); + } + } +}; + +/* What the copy did before: read into one buffer, wait for the next and + * write it as this copy. */ +struct Old +{ + u32 slots, cur; + explicit Old(u32 _slots) : slots(_slots), cur(0) {} + void copy(Gpu &gpu, u32 k, bool _sync) + { + Ops ops(gpu); + gpu.slotRead[cur] = int(k); + gpu.issuedAt.push_back(k); + Geometry g = { k }; + if (!_sync) { + cur = (cur + 1) % slots; + ops.ready(cur, true); + } else { + ops.ready(cur, true); + } + ops.write(cur, g); + } +}; + +int main() +{ + unsigned runs = 0; + for (u32 slots = 1; slots <= 3; ++slots) + for (u32 lag = 0; lag <= 5; ++lag) + for (u32 syncEvery = 0; syncEvery <= 4; ++syncEvery) { + if (slots == 1 && syncEvery != 1) + continue; /* one buffer is the synchronous mode */ + if (!run(slots, lag, syncEvery)) { + fprintf(stderr, " (syncEvery %u)\n", syncEvery); + } + ++runs; + } + const int current_failures = failures; + + /* the checks catch what the copy did before */ + failures = 0; + quiet = true; + if (run(2, 1, 0) || run(2, 2, 0)) { + fprintf(stderr, "the checks pass the old protocol\n"); + return 1; + } + failures = 0; + + if (current_failures) { + fprintf(stderr, "test_gliden64_deferred_readback: %d failures\n", current_failures); + return 1; + } + printf("test_gliden64_deferred_readback: %u runs ok\n", runs); + return 0; +} diff --git a/tools/test_gliden64_ring_segments.cpp b/tools/test_gliden64_ring_segments.cpp new file mode 100644 index 00000000..5c380bd4 --- /dev/null +++ b/tools/test_gliden64_ring_segments.cpp @@ -0,0 +1,141 @@ +/* test_gliden64_ring_segments -- GLideN64's draw data ring cannot + * overwrite what the GPU has yet to read. + * + * The persistently mapped vertex and index buffers used to wrap back to + * the start when full, with nothing between the CPU's new writes and the + * draws still queued on the old data: a GPU a frame or two behind read + * vertices from the next lap. The ring is now in segments, fenced when the + * writes leave one and waited for before they come back. + * + * A GPU is simulated that runs each draw only some draws after it was + * issued, or when a fence after it is waited for. Every write is checked + * against the bytes of the draws not yet run, over ring sizes, allocation + * sizes and lags, and the waits are counted: there must be none while the + * GPU keeps within a lap less a segment. The old wrap must fail the check. + */ +#include +#include +#include + +#include "../mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_RingSegments.h" + +struct Draw +{ + u32 start, end; /* bytes it reads */ + bool done; +}; + +struct Gpu +{ + std::vector draws; + std::vector fenceAt; /* draws issued when each segment was fenced */ + u32 lag; + u32 waits; + bool failed; + + Gpu(u32 _lag, u32 _segments) : fenceAt(_segments, 0), lag(_lag), waits(0), failed(false) {} + + void run() + { + /* draws more than lag behind have run */ + if (draws.size() > lag) + for (size_t i = 0; i + lag < draws.size(); ++i) + draws[i].done = true; + } + void fence(u32 _seg) { fenceAt[_seg] = draws.size(); } + void wait(u32 _seg) + { + bool blocked = false; + for (size_t i = 0; i < fenceAt[_seg]; ++i) + if (!draws[i].done) { + draws[i].done = true; + blocked = true; + } + if (blocked) + ++waits; + } + bool overwrites(u32 _start, u32 _end) const + { + for (const Draw &d : draws) + if (!d.done && _start < d.end && d.start < _end) + return true; + return false; + } +}; + +struct Ops +{ + Gpu &gpu; + void fence(u32 _seg) { gpu.fence(_seg); } + void wait(u32 _seg) { gpu.wait(_seg); } +}; + +static unsigned rng = 12345; +static u32 next_rand(u32 _n) { rng = rng * 1103515245u + 12345u; return (rng >> 8) % _n; } + +/* returns whether every write kept clear of the GPU's data */ +template +static bool run(u32 _size, u32 _maxAlloc, u32 _lag, u32 &_waits) +{ + opengl::RingSegments<3> ring; + Gpu gpu(_lag, 3); + Ops ops = { gpu }; + u32 offset = 0; + ring.init(_size); + for (u32 k = 0; k < 4000; ++k) { + const u32 bytes = 4 + 4 * next_rand(_maxAlloc / 4); + gpu.run(); + u32 start; + if (Old) + start = offset + bytes >= _size ? 0 : offset; + else + start = ring.place(offset, bytes, ops); + if (gpu.overwrites(start, start + bytes)) + gpu.failed = true; + Draw d = { start, start + bytes, false }; + gpu.draws.push_back(d); + offset = start + bytes; + } + _waits = gpu.waits; + return !gpu.failed; +} + +int main() +{ + int failures = 0; + unsigned runs = 0; + const u32 sizes[] = { 3000, 4096, 9000, 65536 }; + for (u32 size : sizes) + for (u32 maxAlloc = 8; maxAlloc <= size / 3; maxAlloc *= 4) + for (u32 lag = 0; lag <= 64; lag += 8) { + u32 waits = 0; + ++runs; + if (!run(size, maxAlloc, lag, waits)) { + fprintf(stderr, "size %u alloc %u lag %u: a write overwrote data a draw had yet to read\n", + size, maxAlloc, lag); + ++failures; + } + /* lag draws of at most maxAlloc fit two segments: no wait */ + if ((lag + 1) * maxAlloc <= 2 * (size / 3) - maxAlloc && waits != 0) { + fprintf(stderr, "size %u alloc %u lag %u: %u waits within the ring\n", + size, maxAlloc, lag, waits); + ++failures; + } + } + + /* the check catches the old wrap */ + { + u32 waits = 0; + if (run(4096, 256, 32, waits)) { + fprintf(stderr, "the check passes the old wrap\n"); + return 1; + } + } + + if (failures) { + fprintf(stderr, "test_gliden64_ring_segments: %d failures\n", failures); + return 1; + } + printf("test_gliden64_ring_segments: %u runs ok\n", runs); + return 0; +}