mirror of
https://github.com/libretro/parallel-n64.git
synced 2026-11-07 04:25:59 -10:00
GL renderers: fewer copies and waits around RDRAM, and crash fixes
GLideN64's asynchronous colour copy to RDRAM keeps what each read was for and writes it once the GPU has finished it, oldest first, waiting only when the buffer it is about to reuse is still in flight. It used to wait on every copy, write the previous read at the current buffer's address and size, and on the first copy write a buffer nothing had been read into. Its vertex ring converts straight into the mapped buffer and is fenced in thirds, so a wrap no longer overwrites vertices the GPU has yet to read. Glide64's colour copy reads back only the pixels the N64 image falls on and converts each once, instead of the whole window to 565 first. Glide64 and Rice read a display list given to the DP as a KSEG address, as homebrew gives it, from RDRAM instead of crashing, and glN64's GBI no longer merges into GLideN64's under -fcommon. tools/lrhost_gl runs the GL renderers headless through EGL, and a new CI lane runs all four over krom's RDP tests on llvmpipe; it fails on the crashes. Unit tests hold the readback order, the ring fences and the Glide64 conversion.
This commit is contained in:
1 parent
be40bfbc75
commit
fb56b3946e
29 files changed
+2509
-1265
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) \
|
||||
{ \
|
||||
|
||||
@@ -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<src_height; j++)
|
||||
{
|
||||
for (i=0; i<src_width; i++)
|
||||
{
|
||||
glide64_frameBuffer[j*(dst_stride/2)+i] =
|
||||
((buf[(src_height-j-1)*src_width*4+i*4+0] >> 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<src_height; j++)
|
||||
for (i=0; i<src_width; i++)
|
||||
buf[j*src_width + i] = (uint8_t)
|
||||
((frameBuffer[(src_height-j-1)*(src_stride/2)+i]/(65536.0f*(2.0f/zscale)))+1-zscale/2.0f)
|
||||
;
|
||||
|
||||
/* depth images are not drawn: the depth buffer is cleared */
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ColorBufferToRDRAM.h"
|
||||
#include "WriteToRDRAM.h"
|
||||
|
||||
#include <FrameBuffer.h>
|
||||
#include <FrameBufferInfo.h>
|
||||
#include <Config.h>
|
||||
#include <N64.h>
|
||||
#include <VI.h>
|
||||
#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 <Graphics/Context.h>
|
||||
#include <Graphics/Parameters.h>
|
||||
#include <Graphics/ColorBufferReader.h>
|
||||
#include <DisplayWindow.h>
|
||||
|
||||
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 <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ColorBufferToRDRAM.h"
|
||||
#include "WriteToRDRAM.h"
|
||||
|
||||
#include <FrameBuffer.h>
|
||||
#include <FrameBufferInfo.h>
|
||||
#include <Config.h>
|
||||
#include <N64.h>
|
||||
#include <VI.h>
|
||||
#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 <Graphics/Context.h>
|
||||
#include <Graphics/Parameters.h>
|
||||
#include <Graphics/ColorBufferReader.h>
|
||||
#include <DisplayWindow.h>
|
||||
|
||||
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<u32>(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<u32, u32>(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<u32, u16>(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<u8, u8>(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<u32>(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<u32>(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<u32, u32>((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<u32, u16>((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<u8, u8>(_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<u32>(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;
|
||||
}
|
||||
@@ -1,69 +1,86 @@
|
||||
#ifndef ColorBufferToRDRAM_H
|
||||
#define ColorBufferToRDRAM_H
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <Graphics/ObjectHandle.h>
|
||||
|
||||
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<u32, 3> m_allowedRealWidths;
|
||||
std::unique_ptr<graphics::ColorBufferReader> m_bufferReader;
|
||||
};
|
||||
|
||||
void copyWhiteToRDRAM(FrameBuffer * _pBuffer);
|
||||
|
||||
#endif // ColorBufferToRDRAM
|
||||
#ifndef ColorBufferToRDRAM_H
|
||||
#define ColorBufferToRDRAM_H
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <Graphics/ObjectHandle.h>
|
||||
#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<ColorCopyGeometry>::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<u32, 3> m_allowedRealWidths;
|
||||
std::unique_ptr<graphics::ColorBufferReader> m_bufferReader;
|
||||
DeferredReadback<ColorCopyGeometry> m_readback;
|
||||
};
|
||||
|
||||
void copyWhiteToRDRAM(FrameBuffer * _pBuffer);
|
||||
|
||||
#endif // ColorBufferToRDRAM
|
||||
@@ -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 <typename Geometry, u32 MaxSlots = 3>
|
||||
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
|
||||
@@ -1,83 +1,116 @@
|
||||
|
||||
#include "ColorBufferReader.h"
|
||||
#include "FramebufferTextureFormats.h"
|
||||
#include "Context.h"
|
||||
#include "Parameters.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
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<const float*>(_gpuData);
|
||||
const u32 colorsPerPixel = 4;
|
||||
const u32 widthPixels = _width * colorsPerPixel;
|
||||
const u32 stridePixels = _stride * colorsPerPixel;
|
||||
|
||||
if (_height * widthPixels > m_pixelData.size())
|
||||
_height = static_cast<u32>(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<u8>(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 <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
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<const float*>(_gpuData);
|
||||
const u32 colorsPerPixel = 4;
|
||||
const u32 widthPixels = _width * colorsPerPixel;
|
||||
const u32 stridePixels = _stride * colorsPerPixel;
|
||||
|
||||
if (_height * widthPixels > m_pixelData.size())
|
||||
_height = static_cast<u32>(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<u8>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +1,52 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <Types.h>
|
||||
#include <Textures.h>
|
||||
|
||||
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<u8> 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 <vector>
|
||||
#include <Types.h>
|
||||
#include <Textures.h>
|
||||
|
||||
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<u8> 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; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,226 +1,278 @@
|
||||
#include <Config.h>
|
||||
#include <CRC.h>
|
||||
#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<u32>(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<GLint>(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<u32>(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<u32>(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 <Config.h>
|
||||
#include <CRC.h>
|
||||
#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<u32>(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<GLint>(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<u32>(sizeof(Vertex));
|
||||
GLubyte * dst = _reserve(vboBuffer, vboDataSize);
|
||||
if (dst != nullptr) {
|
||||
_convertFromSPVertex(reinterpret_cast<Vertex*>(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<u32>(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);
|
||||
}
|
||||
@@ -1,85 +1,91 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#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<Vertex> m_vertices;
|
||||
|
||||
typedef std::unordered_map<u64, u32> BufferOffsets;
|
||||
BufferOffsets m_rectBufferOffsets;
|
||||
|
||||
static const u32 m_bufMaxSize;
|
||||
static const GLbitfield m_bufAccessBits;
|
||||
static const GLbitfield m_bufMapBits;
|
||||
};
|
||||
|
||||
}
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#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<Vertex> m_vertices;
|
||||
|
||||
typedef std::unordered_map<u64, u32> BufferOffsets;
|
||||
BufferOffsets m_rectBufferOffsets;
|
||||
|
||||
static const u32 m_bufMaxSize;
|
||||
static const GLbitfield m_bufAccessBits;
|
||||
static const GLbitfield m_bufMapBits;
|
||||
};
|
||||
|
||||
}
|
||||
+106
-84
@@ -1,84 +1,106 @@
|
||||
#include <algorithm>
|
||||
#include <Config.h>
|
||||
#include <Graphics/Context.h>
|
||||
#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<u8*>(m_PBOData[m_curIndex]);
|
||||
}
|
||||
|
||||
void ColorBufferReaderWithBufferStorage::cleanUp()
|
||||
{
|
||||
m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null);
|
||||
}
|
||||
#include <algorithm>
|
||||
#include <Config.h>
|
||||
#include <Graphics/Context.h>
|
||||
#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<u8*>(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<const u8*>(m_PBOData[_slot]);
|
||||
}
|
||||
|
||||
void ColorBufferReaderWithBufferStorage::cleanUp()
|
||||
{
|
||||
m_bindBuffer->bind(Parameter(GL_PIXEL_PACK_BUFFER), ObjectHandle::null);
|
||||
}
|
||||
+39
-35
@@ -1,35 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <Graphics/ColorBufferReader.h>
|
||||
#include "opengl_CachedFunctions.h"
|
||||
#include <Graphics/Parameter.h>
|
||||
|
||||
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 <Graphics/ColorBufferReader.h>
|
||||
#include "opengl_CachedFunctions.h"
|
||||
#include <Graphics/Parameter.h>
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
}
|
||||
+110
-73
@@ -1,74 +1,111 @@
|
||||
#include <algorithm>
|
||||
#include <Config.h>
|
||||
#include <Graphics/Context.h>
|
||||
#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<u8*>(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 <Config.h>
|
||||
#include <Graphics/Context.h>
|
||||
#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<u8*>(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<const u8*>(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);
|
||||
}
|
||||
+36
-30
@@ -1,30 +1,36 @@
|
||||
#pragma once
|
||||
#include <Graphics/ColorBufferReader.h>
|
||||
#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 <Graphics/ColorBufferReader.h>
|
||||
#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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include <Types.h>
|
||||
|
||||
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 <u32 Segments = 3>
|
||||
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 <typename Ops>
|
||||
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];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
+38
-1
@@ -52,6 +52,9 @@
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <libretro.h>
|
||||
#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);
|
||||
|
||||
@@ -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 <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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 <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
#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<u32> issuedAt;
|
||||
std::vector<int> slotRead; /* the read in each slot, -1 for none */
|
||||
u32 waits;
|
||||
std::vector<Write> 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<Geometry>::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 <typename Protocol>
|
||||
static bool run(u32 _slots, u32 _lag, u32 _syncEvery)
|
||||
{
|
||||
const u32 copies = 40;
|
||||
Gpu gpu(_lag, _slots);
|
||||
Protocol p(_slots);
|
||||
std::vector<bool> 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<bool> 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<Geometry> 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<Current>(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<Old>(2, 1, 0) || run<Old>(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;
|
||||
}
|
||||
@@ -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 <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
#include "../mupen64plus-video-gliden64/src/Graphics/OpenGLContext/opengl_RingSegments.h"
|
||||
|
||||
struct Draw
|
||||
{
|
||||
u32 start, end; /* bytes it reads */
|
||||
bool done;
|
||||
};
|
||||
|
||||
struct Gpu
|
||||
{
|
||||
std::vector<Draw> draws;
|
||||
std::vector<size_t> 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 <bool Old>
|
||||
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<false>(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<true>(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;
|
||||
}
|
||||
Reference in new issue
Block a user