SDL: PSP: zero-copy WindowSurface API for direct VRAM access

From 7b6695f4d4be2032b8ec90b20b58809fe24c6a34 Mon Sep 17 00:00:00 2001
From: rofl0r <[EMAIL REDACTED]>
Date: Thu, 14 Mar 2024 02:58:44 +0000
Subject: [PATCH] PSP: zero-copy WindowSurface API for direct VRAM access

if all one needs is a raw framebuffer to the PSP's vram,
instead of dealing with renderers and textures, that need to be
copied hence and forth, this method allows one to create a window,
set the pixel format using SDL_SetWindowDisplayMode() - preferably
BGR565 for optimal speed (the other possible natively supported
option is ABGR8888) - and then request SDL_GetWindowSurface(),
which provides one with a surface with direct framebuffer access.
note that the pixels pointer inside the surface will be switched
after each call because of double-buffering.

it's advisable to overwrite all pixels of the PSP visible area
(480x272) to not encounter old data.

after writing the pixels, a call to SDL_UpdateWindowSurface()
sends the changes to the graphics chip.

the result is a raw framerate of 250 fps with BGR565 mode, under
optimal circumstances - i.e. nothing else is done than drawing,
and the drawing loop is as simple as possible.
that leaves about 12 ms per frame for other tasks and still allow
a fluent 60 fps.
---
 src/render/psp/SDL_render_psp.c |  19 +++++
 src/render/psp/SDL_render_psp.h |  32 +++++++++
 src/video/psp/SDL_pspvideo.c    | 121 ++++++++++++++++++++++++++++++++
 src/video/psp/SDL_pspvideo.h    |   8 +++
 4 files changed, 180 insertions(+)
 create mode 100644 src/render/psp/SDL_render_psp.h

diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c
index 47fab82c153df..4a446ad42cf32 100644
--- a/src/render/psp/SDL_render_psp.c
+++ b/src/render/psp/SDL_render_psp.c
@@ -36,6 +36,7 @@
 #include <stdarg.h>
 #include <stdlib.h>
 #include <vram.h>
+#include "SDL_render_psp.h"
 
 /* PSP renderer implementation, based on the PGE  */
 
@@ -124,6 +125,24 @@ typedef struct
     float x, y, z;
 } VertTCV;
 
+int SDL_PSP_RenderGetProp(SDL_Renderer *r, enum SDL_PSP_RenderProps which, void** out)
+{
+    PSP_RenderData *rd;
+    if (r == NULL) {
+        return -1;
+    }
+    rd = r->driverdata;
+    switch (which) {
+        case SDL_PSP_RENDERPROPS_FRONTBUFFER:
+            *out = rd->frontbuffer;
+            return 0;
+        case SDL_PSP_RENDERPROPS_BACKBUFFER:
+            *out = rd->backbuffer;
+            return 0;
+    }
+    return -1;
+}
+
 #define PI 3.14159265358979f
 
 #define radToDeg(x) ((x)*180.f / PI)
diff --git a/src/render/psp/SDL_render_psp.h b/src/render/psp/SDL_render_psp.h
new file mode 100644
index 0000000000000..f952886c04386
--- /dev/null
+++ b/src/render/psp/SDL_render_psp.h
@@ -0,0 +1,32 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/* this header is meant to be included after the other related internal SDL
+   headers. it's the interface between psp renderer and video driver code. */
+
+enum SDL_PSP_RenderProps
+{
+    SDL_PSP_RENDERPROPS_FRONTBUFFER,
+    SDL_PSP_RENDERPROPS_BACKBUFFER,
+};
+
+int SDL_PSP_RenderGetProp(SDL_Renderer *r, enum SDL_PSP_RenderProps which, void** out);
+
diff --git a/src/video/psp/SDL_pspvideo.c b/src/video/psp/SDL_pspvideo.c
index 9631904bd1357..46837b6ac7a15 100644
--- a/src/video/psp/SDL_pspvideo.c
+++ b/src/video/psp/SDL_pspvideo.c
@@ -29,14 +29,128 @@
 #include "SDL_syswm.h"
 #include "SDL_loadso.h"
 #include "SDL_events.h"
+#include "SDL_render.h"
 #include "../../events/SDL_mouse_c.h"
 #include "../../events/SDL_keyboard_c.h"
+#include "../../render/SDL_sysrender.h"
 
 
 /* PSP declarations */
 #include "SDL_pspvideo.h"
 #include "SDL_pspevents_c.h"
 #include "SDL_pspgl_c.h"
+#include "../../render/psp/SDL_render_psp.h"
+
+#define SDL_WINDOWTEXTUREDATA "_SDL_WindowTextureData"
+
+typedef struct
+{
+    SDL_Renderer *renderer;
+    SDL_Texture *texture;
+    void *pixels;
+    int pitch;
+    int bytes_per_pixel;
+} SDL_WindowTextureData;
+
+int PSP_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch)
+{
+    SDL_RendererInfo info;
+    SDL_WindowTextureData *data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA);
+    int i, w, h;
+
+    SDL_GetWindowSizeInPixels(window, &w, &h);
+
+    if (w != 480) {
+        return SDL_SetError("Unexpected window size");
+    }
+
+    if (!data) {
+        SDL_Renderer *renderer = NULL;
+        for (i = 0; i < SDL_GetNumRenderDrivers(); ++i) {
+            SDL_GetRenderDriverInfo(i, &info);
+            if (SDL_strcmp(info.name, "software") != 0) {
+                renderer = SDL_CreateRenderer(window, i, 0);
+                if (renderer && (SDL_GetRendererInfo(renderer, &info) == 0) && (info.flags & SDL_RENDERER_ACCELERATED)) {
+                    break; /* this will work. */
+                }
+                if (renderer) { /* wasn't accelerated, etc, skip it. */
+                    SDL_DestroyRenderer(renderer);
+                    renderer = NULL;
+                }
+            }
+        }
+        if (!renderer) {
+            return SDL_SetError("No hardware accelerated renderers available");
+        }
+
+        SDL_assert(renderer != NULL); /* should have explicitly checked this above. */
+
+        /* Create the data after we successfully create the renderer (bug #1116) */
+        data = (SDL_WindowTextureData *)SDL_calloc(1, sizeof(*data));
+
+        if (!data) {
+            SDL_DestroyRenderer(renderer);
+            return SDL_OutOfMemory();
+        }
+
+        SDL_SetWindowData(window, SDL_WINDOWTEXTUREDATA, data);
+        data->renderer = renderer;
+    } else {
+        if (SDL_GetRendererInfo(data->renderer, &info) == -1) {
+            return -1;
+        }
+    }
+
+    /* Find the first format without an alpha channel */
+    *format = info.texture_formats[0];
+
+    for (i = 0; i < (int)info.num_texture_formats; ++i) {
+        if (!SDL_ISPIXELFORMAT_FOURCC(info.texture_formats[i]) &&
+            !SDL_ISPIXELFORMAT_ALPHA(info.texture_formats[i])) {
+            *format = info.texture_formats[i];
+            break;
+        }
+    }
+
+    /* get the PSP renderer's "private" data */
+    SDL_PSP_RenderGetProp(data->renderer, SDL_PSP_RENDERPROPS_FRONTBUFFER, &data->pixels);
+
+    /* Create framebuffer data */
+    data->bytes_per_pixel = SDL_BYTESPERPIXEL(*format);
+    /* since we point directly to VRAM's frontbuffer, we have to use
+       the upscaled pitch of 512 width - since PSP requires all textures to be
+       powers of 2. */
+    data->pitch = 512 * data->bytes_per_pixel;
+    *pixels = data->pixels;
+    *pitch = data->pitch;
+
+    /* Make sure we're not double-scaling the viewport */
+    SDL_RenderSetViewport(data->renderer, NULL);
+
+    return 0;
+}
+
+int PSP_UpdateWindowFramebuffer(_THIS, SDL_Window *window, const SDL_Rect *rects, int numrects)
+{
+    SDL_WindowTextureData *data;
+    data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA);
+    if (!data || !data->renderer || !window->surface) {
+        return -1;
+    }
+    data->renderer->RenderPresent(data->renderer);
+    SDL_PSP_RenderGetProp(data->renderer, SDL_PSP_RENDERPROPS_BACKBUFFER, &window->surface->pixels);
+    return 0;
+}
+
+void PSP_DestroyWindowFramebuffer(_THIS, SDL_Window *window)
+{
+    SDL_WindowTextureData *data = SDL_GetWindowData(window, SDL_WINDOWTEXTUREDATA);
+    if (!data || !data->renderer) {
+        return;
+    }
+    SDL_DestroyRenderer(data->renderer);
+    data->renderer = NULL;
+}
 
 /* unused
 static SDL_bool PSP_initialized = SDL_FALSE;
@@ -128,6 +242,13 @@ static SDL_VideoDevice *PSP_Create()
 
     device->PumpEvents = PSP_PumpEvents;
 
+    /* backend to use VRAM directly as a framebuffer using
+       SDL_GetWindowSurface() API. */
+    device->checked_texture_framebuffer = 1;
+    device->CreateWindowFramebuffer = PSP_CreateWindowFramebuffer;
+    device->UpdateWindowFramebuffer = PSP_UpdateWindowFramebuffer;
+    device->DestroyWindowFramebuffer = PSP_DestroyWindowFramebuffer;
+
     return device;
 }
 
diff --git a/src/video/psp/SDL_pspvideo.h b/src/video/psp/SDL_pspvideo.h
index 442d3eae03e27..44413e788fc80 100644
--- a/src/video/psp/SDL_pspvideo.h
+++ b/src/video/psp/SDL_pspvideo.h
@@ -68,6 +68,14 @@ void PSP_MinimizeWindow(_THIS, SDL_Window *window);
 void PSP_RestoreWindow(_THIS, SDL_Window *window);
 void PSP_DestroyWindow(_THIS, SDL_Window *window);
 
+/* "methods" aka callbacks for SDL_WindowSurface API */
+int PSP_CreateWindowFramebuffer(_THIS, SDL_Window *window, Uint32 *format,
+ void **pixels, int *pitch);
+int PSP_UpdateWindowFramebuffer(_THIS, SDL_Window *window,
+const SDL_Rect *rects, int numrects);
+void PSP_DestroyWindowFramebuffer(_THIS, SDL_Window *window);
+
+
 /* Window manager function */
 SDL_bool PSP_GetWindowWMInfo(_THIS, SDL_Window * window,
                              struct SDL_SysWMinfo *info);