mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
gxm/exynos: frames copied with memcpy, memory/neon gone
The Vita driver copies the core's frame into its texture a row at a time, the frame's width of each row, or in one copy when the core's rows are the texture's; textures are allocated at their row stride, the width rounded to 8. Both drivers copy with the C library's memcpy, NEON-tuned on their targets, and memory/neon/memcpy-neon.S goes. samples/gfx/gxm_frame_upload runs the driver's frame upload against the psp2 stubs with guarded GPU memory: core rows longer, as long and shorter than the texture's, widths not of 8, rows copied once each, nothing written past the texture or read past the frame.
This commit is contained in:
1 parent
3863c2c1fa
commit
778f937839
8 files changed
+400
-266
No files matched your search
+1
-2
@@ -888,8 +888,7 @@ OBJ += midi/drivers/fmsynth_midi.o
|
||||
# Audio Resamplers
|
||||
|
||||
ifeq ($(HAVE_NEON),1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/audio/resampler/drivers/sinc_resampler_neon.o \
|
||||
memory/neon/memcpy-neon.o
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/audio/resampler/drivers/sinc_resampler_neon.o
|
||||
|
||||
DEFINES += -DHAVE_NEON
|
||||
ASFLAGS += $(NEON_ASFLAGS)
|
||||
|
||||
@@ -309,7 +309,6 @@ else ifeq ($(platform), vita)
|
||||
PLATOBJS += \
|
||||
libretro-common/audio/conversion/s16_to_float_neon.o \
|
||||
libretro-common/audio/conversion/float_to_s16_neon.o \
|
||||
memory/neon/memcpy-neon.o \
|
||||
libretro-common/audio/resampler/drivers/sinc_resampler_neon.o
|
||||
|
||||
LIBDIRS += -L.
|
||||
|
||||
@@ -64,8 +64,6 @@
|
||||
/* Set to '1' to enable debug perf code. */
|
||||
#define EXYNOS_GFX_DEBUG_PERF 0
|
||||
|
||||
extern void *memcpy_neon(void *dst, const void *src, size_t n);
|
||||
|
||||
/* GEM buffers for the 'data' from the frontend: the frame (main), the
|
||||
* menu (aux) and the messages (font), each its own, as the font buffer
|
||||
* keeps what it last drew */
|
||||
@@ -1096,7 +1094,7 @@ static int exynos_copy_frame(
|
||||
exynos_perf_memcpy(&pdata->perf, true);
|
||||
#endif
|
||||
|
||||
memcpy_neon(pdata->buf[buf_type]->vaddr, frame, size);
|
||||
memcpy(pdata->buf[buf_type]->vaddr, frame, size);
|
||||
src->stride = src_pitch;
|
||||
|
||||
#if (EXYNOS_GFX_DEBUG_PERF == 1)
|
||||
@@ -1942,7 +1940,7 @@ static int exynos_overlay_blend(struct exynos_video *vid)
|
||||
unsigned x, y;
|
||||
uint32_t *to = (uint32_t*)o->bo->vaddr;
|
||||
if (m == 0xFF && !mirror)
|
||||
memcpy_neon(to, o->px, (size_t)o->w * o->h * sizeof(uint32_t));
|
||||
memcpy(to, o->px, (size_t)o->w * o->h * sizeof(uint32_t));
|
||||
else
|
||||
for (y = 0; y < o->h; y++)
|
||||
{
|
||||
@@ -2506,7 +2504,7 @@ static void exynos_set_texture_frame(void *data, const void *frame, bool rgb32,
|
||||
}
|
||||
}
|
||||
else
|
||||
memcpy_neon(pdata->buf[buf_type]->vaddr, frame, size);
|
||||
memcpy(pdata->buf[buf_type]->vaddr, frame, size);
|
||||
|
||||
#if (EXYNOS_GFX_DEBUG_PERF == 1)
|
||||
exynos_perf_memcpy(&pdata->perf, false);
|
||||
|
||||
+66
-109
@@ -52,10 +52,6 @@
|
||||
#include "../../configuration.h"
|
||||
#include "../../input/input_osk.h"
|
||||
|
||||
/* Forward declaration
|
||||
TODO/FIXME - check if this custom memcpy is genuinely more efficient */
|
||||
extern void *memcpy_neon(void *dst, const void *src, size_t n);
|
||||
|
||||
/* Defines */
|
||||
|
||||
#define RGBA8(r,g,b,a) ((((a)&0xFF)<<24) | (((b)&0xFF)<<16) | (((g)&0xFF)<<8) | (((r)&0xFF)<<0))
|
||||
@@ -1236,7 +1232,8 @@ static gxm_texture_t *gxm_create_empty_texture_format(unsigned int w,
|
||||
|
||||
memset(texture, 0, sizeof(gxm_texture_t));
|
||||
|
||||
tex_size = w * h * tex_format_to_bytespp(format);
|
||||
/* Rows are gxm_texture_get_stride() apart: the width to 8 */
|
||||
tex_size = ((w + 7) & ~7) * h * tex_format_to_bytespp(format);
|
||||
|
||||
/* Allocate a GPU buffer for the texture */
|
||||
texture_data = gpu_alloc(
|
||||
@@ -1298,6 +1295,26 @@ static unsigned int gxm_texture_get_stride(const gxm_texture_t *texture)
|
||||
* tex_format_to_bytespp(sceGxmTextureGetFormat(&texture->gxm_tex));
|
||||
}
|
||||
|
||||
/* @height rows of @row_len bytes from @src, @pitch bytes apart, into a
|
||||
* texture's rows @stride bytes apart; one copy when the two line up.
|
||||
* @row_len is no more than either. */
|
||||
static void gxm_copy_rows(void *dst, unsigned stride, const void *src,
|
||||
unsigned pitch, unsigned row_len, unsigned height)
|
||||
{
|
||||
uint8_t *out = (uint8_t*)dst;
|
||||
const uint8_t *in = (const uint8_t*)src;
|
||||
|
||||
if (!height)
|
||||
return;
|
||||
if (stride == pitch)
|
||||
{
|
||||
memcpy(out, in, (size_t)pitch * (height - 1) + row_len);
|
||||
return;
|
||||
}
|
||||
for (; height; height--, out += stride, in += pitch)
|
||||
memcpy(out, in, row_len);
|
||||
}
|
||||
|
||||
static void gxm_texture_set_filters(gxm_texture_t *texture,
|
||||
SceGxmTextureFilter min_filter, SceGxmTextureFilter mag_filter)
|
||||
{
|
||||
@@ -2074,13 +2091,42 @@ static void gxm_common_dialog_update(void)
|
||||
sceCommonDialogUpdate(¶m);
|
||||
}
|
||||
|
||||
/* The core's frame into the texture it is drawn from, made anew when
|
||||
* its size changes. Only the frame's @dims of each row are copied: a
|
||||
* row of the frame can be longer than the texture's. */
|
||||
static void gxm_upload_frame(vita_video_t *vita, const void *frame,
|
||||
unsigned dims, unsigned pitch)
|
||||
{
|
||||
if (vita->dims != dims && vita->texture)
|
||||
{
|
||||
if (gxm_initialized)
|
||||
sceGxmFinish(gxm_context);
|
||||
gxm_free_texture(vita->texture);
|
||||
vita->texture = NULL;
|
||||
}
|
||||
|
||||
if (!vita->texture)
|
||||
{
|
||||
vita->dims = dims;
|
||||
vita->texture = gxm_create_empty_texture_format(VIDEO_SCALE_W(dims),
|
||||
VIDEO_SCALE_H(dims), vita->format);
|
||||
if (!vita->texture)
|
||||
return;
|
||||
gxm_texture_set_filters(vita->texture, vita->tex_filter,
|
||||
vita->tex_filter);
|
||||
}
|
||||
|
||||
gxm_copy_rows(sceGxmTextureGetData(&vita->texture->gxm_tex),
|
||||
gxm_texture_get_stride(vita->texture), frame, pitch,
|
||||
VIDEO_SCALE_W(dims)
|
||||
* ((vita->format == SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1RGB) ? 4 : 2),
|
||||
VIDEO_SCALE_H(dims));
|
||||
}
|
||||
|
||||
static bool gxm_frame(void *data, const void *frame,
|
||||
unsigned dims, uint64_t frame_count,
|
||||
unsigned pitch, const char *msg, video_frame_info_t *video_info)
|
||||
{
|
||||
unsigned width = VIDEO_SCALE_W(dims);
|
||||
unsigned height = VIDEO_SCALE_H(dims);
|
||||
void *tex_p;
|
||||
gxm_display_data_t displayData;
|
||||
vita_video_t *vita = (vita_video_t *)data;
|
||||
unsigned temp_width = PSP_FB_WIDTH;
|
||||
@@ -2094,60 +2140,9 @@ static bool gxm_frame(void *data, const void *frame,
|
||||
bool statistics_show = video_info->statistics_show;
|
||||
struct font_params *osd_params = (struct font_params*)&video_info->osd_stat_params;
|
||||
|
||||
if (frame)
|
||||
{
|
||||
if (!(vita->texture && sceGxmTextureGetData(&vita->texture->gxm_tex)==frame))
|
||||
{
|
||||
unsigned i;
|
||||
unsigned int stride;
|
||||
|
||||
if (vita->dims != dims && vita->texture)
|
||||
{
|
||||
if (gxm_initialized)
|
||||
sceGxmFinish(gxm_context);
|
||||
gxm_free_texture(vita->texture);
|
||||
vita->texture = NULL;
|
||||
}
|
||||
|
||||
if (!vita->texture)
|
||||
{
|
||||
vita->dims = dims;
|
||||
vita->texture = gxm_create_empty_texture_format(width, height,
|
||||
vita->format);
|
||||
gxm_texture_set_filters(vita->texture, vita->tex_filter,
|
||||
vita->tex_filter);
|
||||
}
|
||||
tex_p = sceGxmTextureGetData(&vita->texture->gxm_tex);
|
||||
stride = gxm_texture_get_stride(vita->texture);
|
||||
|
||||
if (vita->format == SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1RGB)
|
||||
{
|
||||
const uint32_t *frame32;
|
||||
uint32_t *tex32;
|
||||
stride /= 4;
|
||||
pitch /= 4;
|
||||
tex32 = tex_p;
|
||||
frame32 = frame;
|
||||
|
||||
for (i = 0; i < height; i++)
|
||||
memcpy_neon(&tex32[i*stride], &frame32[i*pitch],
|
||||
pitch*sizeof(uint32_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint16_t *frame16;
|
||||
uint16_t *tex16;
|
||||
stride /= 2;
|
||||
pitch /= 2;
|
||||
tex16 = tex_p;
|
||||
frame16 = frame;
|
||||
|
||||
for (i = 0; i < height; i++)
|
||||
memcpy_neon(&tex16[i*stride], &frame16[i*pitch],
|
||||
width*sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (frame && !(vita->texture
|
||||
&& sceGxmTextureGetData(&vita->texture->gxm_tex) == frame))
|
||||
gxm_upload_frame(vita, frame, dims, pitch);
|
||||
|
||||
if (vita->should_resize)
|
||||
gxm_update_viewport(vita);
|
||||
@@ -2478,7 +2473,6 @@ static void gxm_set_texture_frame(void *data,
|
||||
const void *frame, bool rgb32,
|
||||
unsigned dims, float alpha)
|
||||
{
|
||||
unsigned i;
|
||||
void *tex_p;
|
||||
unsigned int stride;
|
||||
vita_video_t *vita = (vita_video_t*)data;
|
||||
@@ -2518,31 +2512,10 @@ static void gxm_set_texture_frame(void *data,
|
||||
tex_p = sceGxmTextureGetData(&vita->menu.texture->gxm_tex);
|
||||
stride = gxm_texture_get_stride(vita->menu.texture);
|
||||
|
||||
/* Copy row-by-row via memcpy rather than pixel-by-pixel. The
|
||||
* texture stride (destination) can differ from the source pitch
|
||||
* (width * bpp) for alignment reasons, so a single memcpy of the
|
||||
* whole buffer isn't safe when stride != width*bpp, but a per-row
|
||||
* memcpy handles both cases and is materially faster than the
|
||||
* previous nested loop with per-pixel indexing. */
|
||||
if (rgb32)
|
||||
{
|
||||
uint32_t *tex32 = (uint32_t*)tex_p;
|
||||
const uint32_t *frame32 = (const uint32_t*)frame;
|
||||
size_t rowlen = (size_t)VIDEO_SCALE_W(dims) * 4;
|
||||
|
||||
stride /= 4;
|
||||
for (i = 0; i < VIDEO_SCALE_H(dims); i++)
|
||||
memcpy(tex32 + i * stride, frame32 + i * VIDEO_SCALE_W(dims), rowlen);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint16_t *tex16 = (uint16_t*)tex_p;
|
||||
const uint16_t *frame16 = (const uint16_t*)frame;
|
||||
size_t rowlen = (size_t)VIDEO_SCALE_W(dims) * 2;
|
||||
|
||||
stride /= 2;
|
||||
for (i = 0; i < VIDEO_SCALE_H(dims); i++)
|
||||
memcpy(tex16 + i * stride, frame16 + i * VIDEO_SCALE_W(dims), rowlen);
|
||||
unsigned row_len = VIDEO_SCALE_W(dims) * (rgb32 ? 4 : 2);
|
||||
gxm_copy_rows(tex_p, stride, frame, row_len, row_len,
|
||||
VIDEO_SCALE_H(dims));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2555,9 +2528,6 @@ static void gxm_set_texture_enable(void *data, bool state, bool full_screen)
|
||||
static uintptr_t gxm_load_texture(void *video_data, void *data,
|
||||
bool threaded, enum texture_filter_type filter_type)
|
||||
{
|
||||
unsigned int stride, pitch, j;
|
||||
uint32_t *tex32 = NULL;
|
||||
const uint32_t *frame32 = NULL;
|
||||
struct texture_image *image = (struct texture_image*)data;
|
||||
gxm_texture_t *texture = gxm_create_empty_texture_format(
|
||||
image->width, image->height,
|
||||
@@ -2572,18 +2542,9 @@ static uintptr_t gxm_load_texture(void *video_data, void *data,
|
||||
SCE_GXM_TEXTURE_FILTER_LINEAR,
|
||||
SCE_GXM_TEXTURE_FILTER_LINEAR);
|
||||
|
||||
stride = gxm_texture_get_stride(texture);
|
||||
stride /= 4;
|
||||
|
||||
tex32 = sceGxmTextureGetData(&texture->gxm_tex);
|
||||
frame32 = image->pixels;
|
||||
pitch = image->width;
|
||||
|
||||
for (j = 0; j < image->height; j++)
|
||||
memcpy_neon(
|
||||
&tex32[j*stride],
|
||||
&frame32[j*pitch],
|
||||
pitch * sizeof(uint32_t));
|
||||
gxm_copy_rows(sceGxmTextureGetData(&texture->gxm_tex),
|
||||
gxm_texture_get_stride(texture), image->pixels, image->width * 4,
|
||||
image->width * 4, image->height);
|
||||
|
||||
return (uintptr_t)texture;
|
||||
}
|
||||
@@ -2593,8 +2554,6 @@ static uintptr_t gxm_load_texture(void *video_data, void *data,
|
||||
static enum video_texture_update gxm_update_texture(void *video_data,
|
||||
uintptr_t id, const struct texture_image *ti, bool threaded)
|
||||
{
|
||||
unsigned int stride, j;
|
||||
uint32_t *tex32;
|
||||
gxm_texture_t *texture = (gxm_texture_t*)id;
|
||||
|
||||
if ( !texture || !ti || !ti->pixels || ti->pix10 || ti->fp16
|
||||
@@ -2604,11 +2563,9 @@ static enum video_texture_update gxm_update_texture(void *video_data,
|
||||
|| sceGxmTextureGetHeight(&texture->gxm_tex) != ti->height)
|
||||
return VIDEO_TEXTURE_UPDATE_REFUSED;
|
||||
|
||||
stride = gxm_texture_get_stride(texture) / 4;
|
||||
tex32 = sceGxmTextureGetData(&texture->gxm_tex);
|
||||
for (j = 0; j < ti->height; j++)
|
||||
memcpy_neon(&tex32[j * stride], &ti->pixels[j * ti->width],
|
||||
ti->width * sizeof(uint32_t));
|
||||
gxm_copy_rows(sceGxmTextureGetData(&texture->gxm_tex),
|
||||
gxm_texture_get_stride(texture), ti->pixels, ti->width * 4,
|
||||
ti->width * 4, ti->height);
|
||||
|
||||
return VIDEO_TEXTURE_UPDATE_DONE;
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* NEON code contributed by Siarhei Siamashka <siarhei.siamashka@nokia.com>.
|
||||
* Origin: http://sourceware.org/ml/libc-ports/2009-07/msg00003.html
|
||||
*
|
||||
* The GNU C Library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License.
|
||||
*
|
||||
* Tweaked for Android by Jim Huang <jserv@0xlab.org>
|
||||
*/
|
||||
|
||||
.arm
|
||||
.fpu neon
|
||||
|
||||
@ void* memcpy(void *destination, const void *source, size_t num)
|
||||
.global memcpy_neon
|
||||
.type memcpy_neon, %function
|
||||
/*
|
||||
* ENABLE_UNALIGNED_MEM_ACCESSES macro can be defined to permit the use
|
||||
* of unaligned load/store memory accesses supported since ARMv6. This
|
||||
* will further improve performance, but can purely theoretically cause
|
||||
* problems if somebody decides to set SCTLR.A bit in the OS kernel
|
||||
* (to trap each unaligned memory access) or somehow mess with strongly
|
||||
* ordered/device memory.
|
||||
*/
|
||||
#define ENABLE_UNALIGNED_MEM_ACCESSES 1
|
||||
|
||||
#define NEON_MAX_PREFETCH_DISTANCE 320
|
||||
|
||||
.align 4
|
||||
memcpy_neon:
|
||||
.fnstart
|
||||
mov ip, r0
|
||||
cmp r2, #16
|
||||
blt 4f @ Have less than 16 bytes to copy
|
||||
|
||||
@ First ensure 16 byte alignment for the destination buffer
|
||||
tst r0, #0xF
|
||||
beq 2f
|
||||
tst r0, #1
|
||||
ldrneb r3, [r1], #1
|
||||
strneb r3, [ip], #1
|
||||
subne r2, r2, #1
|
||||
tst ip, #2
|
||||
#ifdef ENABLE_UNALIGNED_MEM_ACCESSES
|
||||
ldrneh r3, [r1], #2
|
||||
strneh r3, [ip], #2
|
||||
#else
|
||||
ldrneb r3, [r1], #1
|
||||
strneb r3, [ip], #1
|
||||
ldrneb r3, [r1], #1
|
||||
strneb r3, [ip], #1
|
||||
#endif
|
||||
subne r2, r2, #2
|
||||
|
||||
tst ip, #4
|
||||
beq 1f
|
||||
vld4.8 {d0[0], d1[0], d2[0], d3[0]}, [r1]!
|
||||
vst4.8 {d0[0], d1[0], d2[0], d3[0]}, [ip, :32]!
|
||||
sub r2, r2, #4
|
||||
1:
|
||||
tst ip, #8
|
||||
beq 2f
|
||||
vld1.8 {d0}, [r1]!
|
||||
vst1.8 {d0}, [ip, :64]!
|
||||
sub r2, r2, #8
|
||||
2:
|
||||
subs r2, r2, #32
|
||||
blt 3f
|
||||
mov r3, #32
|
||||
|
||||
@ Main copy loop, 32 bytes are processed per iteration.
|
||||
@ ARM instructions are used for doing fine-grained prefetch,
|
||||
@ increasing prefetch distance progressively up to
|
||||
@ NEON_MAX_PREFETCH_DISTANCE at runtime
|
||||
1:
|
||||
vld1.8 {d0-d3}, [r1]!
|
||||
cmp r3, #(NEON_MAX_PREFETCH_DISTANCE - 32)
|
||||
pld [r1, r3]
|
||||
addle r3, r3, #32
|
||||
vst1.8 {d0-d3}, [ip, :128]!
|
||||
sub r2, r2, #32
|
||||
cmp r2, r3
|
||||
bge 1b
|
||||
cmp r2, #0
|
||||
blt 3f
|
||||
1: @ Copy the remaining part of the buffer (already prefetched)
|
||||
vld1.8 {d0-d3}, [r1]!
|
||||
subs r2, r2, #32
|
||||
vst1.8 {d0-d3}, [ip, :128]!
|
||||
bge 1b
|
||||
3: @ Copy up to 31 remaining bytes
|
||||
tst r2, #16
|
||||
beq 4f
|
||||
vld1.8 {d0, d1}, [r1]!
|
||||
vst1.8 {d0, d1}, [ip, :128]!
|
||||
4:
|
||||
@ Use ARM instructions exclusively for the final trailing part
|
||||
@ not fully fitting into full 16 byte aligned block in order
|
||||
@ to avoid "ARM store after NEON store" hazard. Also NEON
|
||||
@ pipeline will be (mostly) flushed by the time when the
|
||||
@ control returns to the caller, making the use of NEON mostly
|
||||
@ transparent (and avoiding hazards in the caller code)
|
||||
|
||||
#ifdef ENABLE_UNALIGNED_MEM_ACCESSES
|
||||
movs r3, r2, lsl #29
|
||||
ldrcs r3, [r1], #4
|
||||
strcs r3, [ip], #4
|
||||
ldrcs r3, [r1], #4
|
||||
strcs r3, [ip], #4
|
||||
ldrmi r3, [r1], #4
|
||||
strmi r3, [ip], #4
|
||||
movs r2, r2, lsl #31
|
||||
ldrcsh r3, [r1], #2
|
||||
strcsh r3, [ip], #2
|
||||
ldrmib r3, [r1], #1
|
||||
strmib r3, [ip], #1
|
||||
#else
|
||||
movs r3, r2, lsl #29
|
||||
bcc 1f
|
||||
.rept 8
|
||||
ldrcsb r3, [r1], #1
|
||||
strcsb r3, [ip], #1
|
||||
.endr
|
||||
1:
|
||||
bpl 1f
|
||||
.rept 4
|
||||
ldrmib r3, [r1], #1
|
||||
strmib r3, [ip], #1
|
||||
.endr
|
||||
1:
|
||||
movs r2, r2, lsl #31
|
||||
ldrcsb r3, [r1], #1
|
||||
strcsb r3, [ip], #1
|
||||
ldrcsb r3, [r1], #1
|
||||
strcsb r3, [ip], #1
|
||||
ldrmib r3, [r1], #1
|
||||
strmib r3, [ip], #1
|
||||
#endif
|
||||
bx lr
|
||||
.fnend
|
||||
|
||||
/* Prevent the stack from becoming executable for no reason...
|
||||
* Placed last: a bare .section switches the active section, and these
|
||||
* files emit into the assembler default (.text) with no .text directive
|
||||
* of their own to switch back. */
|
||||
#if defined(__linux__) && defined(__ELF__)
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
#endif
|
||||
@@ -17,7 +17,12 @@
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* The driver's copies, counted: its only memcpy calls are the frame's
|
||||
* and the overlays' */
|
||||
static void *count_memcpy(void *dst, const void *src, size_t n);
|
||||
#define memcpy(dst, src, n) count_memcpy(dst, src, n)
|
||||
#include "../../../gfx/drivers/exynos_gfx.c"
|
||||
#undef memcpy
|
||||
#include "../../../gfx/common/drm_common.c"
|
||||
|
||||
/* ---- frontend pieces the driver reaches ------------------------------ */
|
||||
@@ -41,7 +46,7 @@ static settings_t settings;
|
||||
settings_t *config_get_ptr(void) { return &settings; }
|
||||
|
||||
static unsigned memcpys;
|
||||
void *memcpy_neon(void *dst, const void *src, size_t n)
|
||||
static void *count_memcpy(void *dst, const void *src, size_t n)
|
||||
{
|
||||
memcpys++;
|
||||
return memcpy(dst, src, n);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# gfx/drivers/gxm_gfx.c's upload of the core's frame into its texture,
|
||||
# on the host against the tree's psp2 stubs (tools/platform_stubs/vita).
|
||||
# The test includes the driver's source, its tables made static so the
|
||||
# compiler drops all of it the upload does not reach. Builds for Linux
|
||||
# and, with CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine, for Windows.
|
||||
TARGET := gxm_frame_upload_test$(EXE)
|
||||
REPO_ROOT := ../../..
|
||||
|
||||
CFLAGS += -Wall -Wno-unused-function -Wno-unused-variable -std=gnu99 -g1 -O1 \
|
||||
-DRARCH_INTERNAL -DHAVE_MENU -DVITA -DRARCH_CONSOLE \
|
||||
-U__linux__ -U__gnu_linux__ -Ulinux -U__unix__ -U__unix -Uunix \
|
||||
-I$(REPO_ROOT) -I$(REPO_ROOT)/libretro-common/include \
|
||||
-I$(REPO_ROOT)/deps -I$(REPO_ROOT)/input/include \
|
||||
-I$(REPO_ROOT)/tools/platform_stubs/vita \
|
||||
-Wno-pointer-to-int-cast -Wno-int-to-pointer-cast
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
ifneq ($(SAN),)
|
||||
CFLAGS := -fsanitize=$(SAN) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SAN) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): gxm_frame_upload_test.c $(REPO_ROOT)/gfx/drivers/gxm_gfx.c
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
|
||||
check: all
|
||||
$(RUNNER) ./$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f gxm_frame_upload_test gxm_frame_upload_test.exe
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,287 @@
|
||||
/* gfx/drivers/gxm_gfx.c's upload of the core's frame into the texture
|
||||
* it is drawn from, on the host against the tree's psp2 stubs, with GPU
|
||||
* memory blocks malloc'd at the size asked for and a guard after each.
|
||||
* Held to:
|
||||
*
|
||||
* - each texture row gets the frame's row, the frame's width of it and
|
||||
* no more, whether the core's rows are longer than the texture's,
|
||||
* as long, or shorter;
|
||||
* - nothing is written past the texture's memory, a width that is not
|
||||
* a multiple of 8 included, and nothing read past the frame's last
|
||||
* row's pixels;
|
||||
* - rows that line up go over in one copy, others one copy a row;
|
||||
* - a frame of new dimensions gets a new texture, the old one freed. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
/* The driver names its clip planes near and far, which windef.h,
|
||||
* reached through the libretro-common headers, defines away */
|
||||
#include <windows.h>
|
||||
#undef near
|
||||
#undef far
|
||||
#endif
|
||||
|
||||
/* The driver's two tables made static and so, unused, never emitted,
|
||||
* with all the driver they reach: the upload, and what it calls, is
|
||||
* all that is left to link. Their extern declarations are read first,
|
||||
* as the driver would. */
|
||||
#include "../../../gfx/video_driver.h"
|
||||
#include "../../../gfx/gfx_display.h"
|
||||
#define video_gxm static unused_video_gxm
|
||||
#define gfx_display_ctx_gxm static unused_gfx_display_ctx_gxm
|
||||
|
||||
static unsigned memcpys;
|
||||
static void *count_memcpy(void *dst, const void *src, size_t n);
|
||||
#define memcpy(dst, src, n) count_memcpy(dst, src, n)
|
||||
#include "../../../gfx/drivers/gxm_gfx.c"
|
||||
#undef memcpy
|
||||
/* psp_defines.h sends printf to the Vita's own */
|
||||
#undef printf
|
||||
|
||||
int sceClibPrintf(const char *fmt, ...)
|
||||
{
|
||||
int n;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
n = vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
static void *count_memcpy(void *dst, const void *src, size_t n)
|
||||
{
|
||||
memcpys++;
|
||||
return memcpy(dst, src, n);
|
||||
}
|
||||
|
||||
static unsigned failures;
|
||||
|
||||
#define CHECK(c, what) do { if (!(c)) { \
|
||||
printf("FAIL: %s (%s:%d)\n", what, __FILE__, __LINE__); \
|
||||
failures++; } else printf(" ok %s\n", what); } while (0)
|
||||
|
||||
/* ---- memory blocks ---- */
|
||||
#define MAX_BLOCKS 16
|
||||
#define GUARD 65536
|
||||
|
||||
static uint8_t *blocks[MAX_BLOCKS + 1];
|
||||
static size_t block_size[MAX_BLOCKS + 1];
|
||||
static unsigned blocks_live;
|
||||
|
||||
SceUID sceKernelAllocMemBlock(const char *name, SceKernelMemBlockType type,
|
||||
SceSize size, SceKernelAllocMemBlockOpt *opt)
|
||||
{
|
||||
int i;
|
||||
(void)name; (void)type; (void)opt;
|
||||
for (i = 1; i <= MAX_BLOCKS; i++)
|
||||
if (!blocks[i])
|
||||
{
|
||||
blocks[i] = (uint8_t*)malloc(size + GUARD);
|
||||
block_size[i] = size;
|
||||
memset(blocks[i] + size, 0x5a, GUARD);
|
||||
blocks_live++;
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sceKernelGetMemBlockBase(SceUID uid, void **base)
|
||||
{
|
||||
if (uid < 1 || uid > MAX_BLOCKS || !blocks[uid])
|
||||
return -1;
|
||||
*base = blocks[uid];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sceKernelFreeMemBlock(SceUID uid)
|
||||
{
|
||||
if (uid < 1 || uid > MAX_BLOCKS || !blocks[uid])
|
||||
return -1;
|
||||
free(blocks[uid]);
|
||||
blocks[uid] = NULL;
|
||||
blocks_live--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int guards_intact(void)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 1; i <= MAX_BLOCKS; i++)
|
||||
if (blocks[i])
|
||||
for (j = 0; j < GUARD; j++)
|
||||
if (blocks[i][block_size[i] + j] != 0x5a)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sceGxmMapMemory(void *base, SceSize size, SceGxmMemoryAttribFlags attr)
|
||||
{ (void)base; (void)size; (void)attr; return 0; }
|
||||
int sceGxmUnmapMemory(void *base) { (void)base; return 0; }
|
||||
void sceGxmFinish(SceGxmContext *context) { (void)context; }
|
||||
int sceGxmDestroyRenderTarget(SceGxmRenderTarget *target)
|
||||
{ (void)target; return 0; }
|
||||
|
||||
/* ---- textures: the stub's fields are the target's 30-bit ones, so the
|
||||
* host keeps each texture's own here ---- */
|
||||
struct tex_rec
|
||||
{
|
||||
const SceGxmTexture *tex;
|
||||
void *data;
|
||||
unsigned w, h;
|
||||
SceGxmTextureFormat fmt;
|
||||
};
|
||||
static struct tex_rec texs[MAX_BLOCKS];
|
||||
|
||||
static struct tex_rec *tex_find(const SceGxmTexture *t, int make)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAX_BLOCKS; i++)
|
||||
if (texs[i].tex == t)
|
||||
return &texs[i];
|
||||
if (make)
|
||||
for (i = 0; i < MAX_BLOCKS; i++)
|
||||
if (!texs[i].tex)
|
||||
return &texs[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int sceGxmTextureInitLinear(SceGxmTexture *texture, const void *data,
|
||||
SceGxmTextureFormat fmt, unsigned int w, unsigned int h,
|
||||
unsigned int mips)
|
||||
{
|
||||
struct tex_rec *r = tex_find(texture, 1);
|
||||
(void)mips;
|
||||
r->tex = texture;
|
||||
r->data = (void*)data;
|
||||
r->w = w;
|
||||
r->h = h;
|
||||
r->fmt = fmt;
|
||||
return 0;
|
||||
}
|
||||
void *sceGxmTextureGetData(const SceGxmTexture *t)
|
||||
{ return tex_find(t, 0)->data; }
|
||||
unsigned int sceGxmTextureGetWidth(const SceGxmTexture *t)
|
||||
{ return tex_find(t, 0)->w; }
|
||||
unsigned int sceGxmTextureGetHeight(const SceGxmTexture *t)
|
||||
{ return tex_find(t, 0)->h; }
|
||||
SceGxmTextureFormat sceGxmTextureGetFormat(const SceGxmTexture *t)
|
||||
{ return tex_find(t, 0)->fmt; }
|
||||
int sceGxmTextureSetMagFilter(SceGxmTexture *t, SceGxmTextureFilter f)
|
||||
{ (void)t; (void)f; return 0; }
|
||||
int sceGxmTextureSetMinFilter(SceGxmTexture *t, SceGxmTextureFilter f)
|
||||
{ (void)t; (void)f; return 0; }
|
||||
int sceGxmTextureSetPalette(SceGxmTexture *t, const void *p)
|
||||
{ (void)t; (void)p; return 0; }
|
||||
|
||||
/* ---- the checks ---- */
|
||||
|
||||
/* A frame of @h rows @pitch bytes apart, allocated to the end of its
|
||||
* last row's @w pixels and no further */
|
||||
static uint8_t *make_frame(unsigned w, unsigned h, unsigned pitch,
|
||||
unsigned bpp, unsigned seed)
|
||||
{
|
||||
size_t size = (size_t)pitch * (h - 1) + (size_t)w * bpp;
|
||||
uint8_t *f = (uint8_t*)malloc(size);
|
||||
size_t i;
|
||||
for (i = 0; i < size; i++)
|
||||
f[i] = (uint8_t)(i * 7 + seed);
|
||||
return f;
|
||||
}
|
||||
|
||||
static int rows_match(const vita_video_t *vita, const uint8_t *frame,
|
||||
unsigned w, unsigned h, unsigned pitch, unsigned bpp)
|
||||
{
|
||||
const uint8_t *tex = (const uint8_t*)
|
||||
sceGxmTextureGetData(&vita->texture->gxm_tex);
|
||||
unsigned stride = gxm_texture_get_stride(vita->texture);
|
||||
unsigned y;
|
||||
for (y = 0; y < h; y++)
|
||||
if (memcmp(tex + (size_t)y * stride, frame + (size_t)y * pitch,
|
||||
(size_t)w * bpp))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Past the frame's width, each texture row as it was made: cleared.
|
||||
* Rows that go over in one copy bring the frame's padding with them,
|
||||
* so only those copied a row at a time are held to it. */
|
||||
static int padding_clear(const vita_video_t *vita, unsigned w, unsigned h,
|
||||
unsigned bpp)
|
||||
{
|
||||
const uint8_t *tex = (const uint8_t*)
|
||||
sceGxmTextureGetData(&vita->texture->gxm_tex);
|
||||
unsigned stride = gxm_texture_get_stride(vita->texture);
|
||||
unsigned y, x;
|
||||
for (y = 0; y < h; y++)
|
||||
for (x = w * bpp; x < stride; x++)
|
||||
if (tex[(size_t)y * stride + x])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void upload(vita_video_t *vita, unsigned w, unsigned h,
|
||||
unsigned pitch, unsigned bpp, unsigned seed, unsigned expect_copies,
|
||||
const char *what)
|
||||
{
|
||||
uint8_t *frame = make_frame(w, h, pitch, bpp, seed);
|
||||
unsigned m0 = memcpys;
|
||||
char msg[160];
|
||||
|
||||
gxm_upload_frame(vita, frame, VIDEO_SCALE_PACK(w, h), pitch);
|
||||
snprintf(msg, sizeof(msg), "%s: rows copied", what);
|
||||
CHECK(vita->texture && rows_match(vita, frame, w, h, pitch, bpp), msg);
|
||||
snprintf(msg, sizeof(msg), "%s: nothing past the texture", what);
|
||||
CHECK(guards_intact(), msg);
|
||||
if (expect_copies > 1)
|
||||
{
|
||||
snprintf(msg, sizeof(msg), "%s: no more than the width a row", what);
|
||||
CHECK(padding_clear(vita, w, h, bpp), msg);
|
||||
}
|
||||
snprintf(msg, sizeof(msg), "%s: %u copies (%u made)", what,
|
||||
expect_copies, memcpys - m0);
|
||||
CHECK(memcpys - m0 == expect_copies, msg);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
vita_video_t vita;
|
||||
|
||||
/* GPU memory rounded to 4 KiB, not the 256 KiB of CDRAM, so the
|
||||
* guard sits close behind each texture */
|
||||
MemBlockType = SCE_KERNEL_MEMBLOCK_TYPE_USER_RW;
|
||||
|
||||
memset(&vita, 0, sizeof(vita));
|
||||
vita.format = SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1RGB;
|
||||
|
||||
/* A core's rows longer than the texture's, a width not of 8 */
|
||||
upload(&vita, 257, 224, 512 * 4, 4, 1, 224,
|
||||
"xrgb, 257 wide, rows of 512");
|
||||
/* Rows as long as the texture's: one copy */
|
||||
upload(&vita, 320, 240, 320 * 4, 4, 2, 1, "xrgb, rows aligned");
|
||||
CHECK(blocks_live == 1, "the old texture freed for the new size");
|
||||
|
||||
gxm_free_texture(vita.texture);
|
||||
memset(&vita, 0, sizeof(vita));
|
||||
vita.format = SCE_GXM_TEXTURE_FORMAT_R5G6B5;
|
||||
/* 250 wide, so the texture's rows are 256 */
|
||||
upload(&vita, 250, 100, 300 * 2, 2, 4, 100, "rgb565, rows of 300");
|
||||
/* Shorter rows than the texture's */
|
||||
upload(&vita, 250, 100, 250 * 2, 2, 5, 100, "rgb565, rows of 250");
|
||||
upload(&vita, 250, 100, 256 * 2, 2, 3, 1, "rgb565, rows of 256");
|
||||
gxm_free_texture(vita.texture);
|
||||
CHECK(blocks_live == 0, "every texture freed");
|
||||
|
||||
if (failures)
|
||||
{
|
||||
printf("%u failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("PASS gxm_frame_upload_test\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user