diff --git a/Makefile.common b/Makefile.common index 5acdea8fea..319967cb5e 100644 --- a/Makefile.common +++ b/Makefile.common @@ -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) diff --git a/Makefile.griffin b/Makefile.griffin index decee3a3e1..8000aa467f 100644 --- a/Makefile.griffin +++ b/Makefile.griffin @@ -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. diff --git a/gfx/drivers/exynos_gfx.c b/gfx/drivers/exynos_gfx.c index 6ae8945cc8..3b5f263bde 100644 --- a/gfx/drivers/exynos_gfx.c +++ b/gfx/drivers/exynos_gfx.c @@ -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); diff --git a/gfx/drivers/gxm_gfx.c b/gfx/drivers/gxm_gfx.c index e3b6183cbf..136ff448e0 100644 --- a/gfx/drivers/gxm_gfx.c +++ b/gfx/drivers/gxm_gfx.c @@ -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; } diff --git a/memory/neon/memcpy-neon.S b/memory/neon/memcpy-neon.S deleted file mode 100644 index 72e49c9082..0000000000 --- a/memory/neon/memcpy-neon.S +++ /dev/null @@ -1,148 +0,0 @@ -/* - * NEON code contributed by Siarhei Siamashka . - * 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 - */ - -.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 diff --git a/samples/gfx/exynos_g2d/exynos_g2d_test.c b/samples/gfx/exynos_g2d/exynos_g2d_test.c index 683a71dc79..d84235d99a 100644 --- a/samples/gfx/exynos_g2d/exynos_g2d_test.c +++ b/samples/gfx/exynos_g2d/exynos_g2d_test.c @@ -17,7 +17,12 @@ #include #include +/* 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); diff --git a/samples/gfx/gxm_frame_upload/Makefile b/samples/gfx/gxm_frame_upload/Makefile new file mode 100644 index 0000000000..ad8af6e3d1 --- /dev/null +++ b/samples/gfx/gxm_frame_upload/Makefile @@ -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 diff --git a/samples/gfx/gxm_frame_upload/gxm_frame_upload_test.c b/samples/gfx/gxm_frame_upload/gxm_frame_upload_test.c new file mode 100644 index 0000000000..4de62ed47f --- /dev/null +++ b/samples/gfx/gxm_frame_upload/gxm_frame_upload_test.c @@ -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 +#include +#include +#include +#include + +#ifdef _WIN32 +/* The driver names its clip planes near and far, which windef.h, + * reached through the libretro-common headers, defines away */ +#include +#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; +}