SDL: Spill render targets to ram on PSP

From c1f152292b23a8d1af27c3559942fefded645590 Mon Sep 17 00:00:00 2001
From: stdgregwar <[EMAIL REDACTED]>
Date: Sun, 27 Dec 2020 11:43:22 +0100
Subject: [PATCH] Spill render targets to ram on PSP

---
 src/render/psp/SDL_render_psp.c | 226 +++++++++++++++++++++++++++-----
 1 file changed, 191 insertions(+), 35 deletions(-)

diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c
index 8dc40d90c36..28e01c0e1d7 100644
--- a/src/render/psp/SDL_render_psp.c
+++ b/src/render/psp/SDL_render_psp.c
@@ -74,8 +74,8 @@ typedef struct PSP_TextureData
     unsigned int        format;                             /**< Image format - one of ::pgePixelFormat. */
     unsigned int        pitch;
     SDL_bool            swizzled;                           /**< Is image swizzled. */
-    struct PSP_TextureData*    prevhot;                            /**< More recently used render target */
-    struct PSP_TextureData*    nexthot;                            /**< Less recently used render target */
+    struct PSP_TextureData*    prevhotw;                            /**< More recently used render target */
+    struct PSP_TextureData*    nexthotw;                            /**< Less recently used render target */
 } PSP_TextureData;
 
 
@@ -165,6 +165,12 @@ Swap(float *a, float *b)
     *b = n;
 }
 
+static inline int
+InVram(void* data)
+{
+    return data < 0x04200000;
+}
+
 /* Return next power of 2 */
 static int
 TextureNextPow2(unsigned int w)
@@ -203,35 +209,70 @@ PixelFormatToPSPFMT(Uint32 format)
     }
 }
 
+///SECTION render target LRU management
 static void
-StartDrawing(SDL_Renderer * renderer)
-{
-    PSP_RenderData *data = (PSP_RenderData *) renderer->driverdata;
+LRUTargetRelink(PSP_TextureData* psp_texture) {
+    if(psp_texture->prevhotw) {
+        psp_texture->prevhotw->nexthotw = psp_texture->nexthotw;
+    }
+    if(psp_texture->nexthotw) {
+        psp_texture->nexthotw->prevhotw = psp_texture->prevhotw;
+    }
+}
 
-    // Check if we need to start GU displaylist
-    if(!data->displayListAvail) {
-        sceGuStart(GU_DIRECT, DisplayList);
-        data->displayListAvail = SDL_TRUE;
+static void
+LRUTargetFixTail(PSP_RenderData* data, PSP_TextureData* psp_texture) {
+    if(data->least_recent_target == psp_texture) {
+        data->least_recent_target = psp_texture->prevhotw;
+    } else if(!data->least_recent_target) {
+        data->least_recent_target = psp_texture;
     }
+}
 
-    // Check if we need a draw buffer change
-    if(renderer->target != data->boundTarget) {
-        SDL_Texture* texture = renderer->target;
-        if(texture) {
-            PSP_TextureData* psp_texture = (PSP_TextureData*) texture->driverdata;
-            // Set target back to screen
-            sceGuDrawBufferList(psp_texture->format, vrelptr(psp_texture->data), psp_texture->textureWidth);
-        } else {
-            // Set target back to screen
-            sceGuDrawBufferList(data->psm, vrelptr(data->frontbuffer), PSP_FRAME_BUFFER_WIDTH);
-        }
-        data->boundTarget = texture;
+static void
+LRUTargetPushFront(PSP_RenderData* data, PSP_TextureData* psp_texture) {
+    psp_texture->nexthotw = data->most_recent_target;
+    if(data->most_recent_target) {
+        data->most_recent_target->prevhotw = psp_texture;
+    }
+    LRUTargetFixTail(data, psp_texture);
+}
+
+static void
+LRUTargetBringFront(PSP_RenderData* data, PSP_TextureData* psp_texture) {
+    if(data->most_recent_target == psp_texture) {
+        return; //nothing to do
+    }
+    LRUTargetRelink(psp_texture);
+    psp_texture->prevhotw = NULL;
+    psp_texture->nexthotw = NULL;
+    LRUTargetPushFront(data, psp_texture);
+}
+
+static void
+LRUTargetRemove(PSP_RenderData* data, PSP_TextureData* psp_texture) {
+    LRUTargetRelink(psp_texture);
+    if(data->most_recent_target == psp_texture) {
+        data->most_recent_target = psp_texture->nexthotw;
     }
+    if(data->least_recent_target == psp_texture) {
+        data->least_recent_target = psp_texture->prevhotw;
+    }
+    psp_texture->prevhotw = NULL;
+    psp_texture->nexthotw = NULL;
 }
 
+static void
+TextureStorageFree(void* storage) {
+    if(InVram(storage)) {
+        vfree(storage);
+    } else {
+        SDL_free(storage);
+    }
+}
 
 static int
-TextureSwizzle(PSP_TextureData *psp_texture)
+TextureSwizzle(PSP_TextureData *psp_texture, void* dst)
 {
     int bytewidth, height;
     int rowblocks, rowblocksadd;
@@ -251,7 +292,14 @@ TextureSwizzle(PSP_TextureData *psp_texture)
 
     src = (unsigned int*) psp_texture->data;
 
-    data = SDL_malloc(psp_texture->size);
+    data = dst;
+    if(!data) {
+        data = SDL_malloc(psp_texture->size);
+    }
+
+    if(!data) {
+        return SDL_OutOfMemory();
+    }
 
     for(j = 0; j < height; j++, blockaddress += 16)
     {
@@ -272,7 +320,7 @@ TextureSwizzle(PSP_TextureData *psp_texture)
             blockaddress += rowblocksadd;
     }
 
-    SDL_free(psp_texture->data);
+    TextureStorageFree(psp_texture->data);
     psp_texture->data = data;
     psp_texture->swizzled = SDL_TRUE;
 
@@ -281,7 +329,7 @@ TextureSwizzle(PSP_TextureData *psp_texture)
 }
 
 static int
-TextureUnswizzle(PSP_TextureData *psp_texture)
+TextureUnswizzle(PSP_TextureData *psp_texture, void* dst)
 {
     int bytewidth, height;
     int widthblocks, heightblocks;
@@ -306,10 +354,14 @@ TextureUnswizzle(PSP_TextureData *psp_texture)
 
     src = (unsigned int*) psp_texture->data;
 
-    data = SDL_malloc(psp_texture->size);
+    data = dst;
+
+    if(!data) {
+        data = SDL_malloc(psp_texture->size);
+    }
 
     if(!data)
-        return 0;
+        return SDL_OutOfMemory();
 
     ydst = (unsigned char *)data;
 
@@ -338,7 +390,7 @@ TextureUnswizzle(PSP_TextureData *psp_texture)
         ydst += dstrow;
     }
 
-    SDL_free(psp_texture->data);
+    TextureStorageFree(psp_texture->data);
 
     psp_texture->data = data;
 
@@ -348,6 +400,80 @@ TextureUnswizzle(PSP_TextureData *psp_texture)
     return 1;
 }
 
+static int
+TextureSpillToSram(PSP_RenderData* data, PSP_TextureData* psp_texture)
+{
+    // Assumes the texture is in VRAM
+    if(psp_texture->swizzled) {
+        //Texture was swizzled in vram, just copy to system memory
+        void* data = SDL_malloc(psp_texture->size);
+        if(!data) {
+            return SDL_OutOfMemory();
+        }
+
+        SDL_memcpy(data, psp_texture->data, psp_texture->size);
+        vfree(psp_texture->data);
+        psp_texture->data = data;
+        return 0;
+    } else {
+        return TextureSwizzle(psp_texture, NULL); //Will realloc in sysram
+    }
+}
+
+static int
+TexturePromoteToVram(PSP_RenderData* data, PSP_TextureData* psp_texture, SDL_bool target)
+{
+    // Assumes texture in sram and a large enough continuous block in vram
+    void* tdata = valloc(psp_texture->size);
+    if(psp_texture->swizzled && target) {
+        return TextureUnswizzle(psp_texture, tdata);
+    } else {
+        SDL_memcpy(tdata, psp_texture->data, psp_texture->size);
+        SDL_free(psp_texture->data);
+        psp_texture->data = tdata;
+        return 0;
+    }
+}
+
+static int
+TextureSpillLRU(PSP_RenderData* data) {
+    PSP_TextureData* lru = data->least_recent_target;
+    if(lru) {
+        if(TextureSpillToSram(data, lru) < 0) {
+            return -1;
+        }
+        LRUTargetRemove(data, lru);
+    }
+    return 0;
+}
+
+static int
+TextureSpillTargetsForSpace(PSP_RenderData* data, size_t size)
+{
+    while(vlargestblock() < size) {
+        if(TextureSpillLRU(data) < 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+TextureBindAsTarget(PSP_RenderData* data, PSP_TextureData* psp_texture) {
+    if(!InVram(psp_texture->data)) {
+        // Bring back the texture in vram
+        if(TextureSpillTargetsForSpace(data, psp_texture->size) < 0) {
+            return -1;
+        }
+        if(TexturePromoteToVram(data, psp_texture, SDL_TRUE) < 0) {
+            return -1;
+        }
+    }
+    LRUTargetBringFront(data, psp_texture);
+    sceGuDrawBufferList(psp_texture->format, vrelptr(psp_texture->data), psp_texture->textureWidth);
+    return 0;
+}
+
 static void
 PSP_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
 {
@@ -357,7 +483,7 @@ PSP_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
 static int
 PSP_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
 {
-/*      PSP_RenderData *renderdata = (PSP_RenderData *) renderer->driverdata; */
+    PSP_RenderData *data = renderer->driverdata;
     PSP_TextureData* psp_texture = (PSP_TextureData*) SDL_calloc(1, sizeof(*psp_texture));
 
     if(!psp_texture)
@@ -389,7 +515,13 @@ PSP_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
     psp_texture->pitch = psp_texture->textureWidth * SDL_BYTESPERPIXEL(texture->format);
     psp_texture->size = psp_texture->textureHeight*psp_texture->pitch;
     if(texture->access & SDL_TEXTUREACCESS_TARGET) {
+        if(TextureSpillTargetsForSpace(renderer->driverdata, psp_texture->size) < 0){
+            return -1;
+        }
         psp_texture->data = valloc(psp_texture->size);
+        if(psp_texture->data) {
+            LRUTargetPushFront(data, psp_texture);
+        }
     } else {
         psp_texture->data = SDL_calloc(1, psp_texture->size);
     }
@@ -426,7 +558,7 @@ TextureActivate(SDL_Texture * texture)
     /* Swizzling is useless with small textures. */
     if (TextureShouldSwizzle(psp_texture, texture))
     {
-        TextureSwizzle(psp_texture);
+        TextureSwizzle(psp_texture, NULL);
     }
 
     sceGuEnable(GU_TEXTURE_2D);
@@ -812,6 +944,34 @@ PSP_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * t
     return 0;
 }
 
+
+static void
+StartDrawing(SDL_Renderer * renderer)
+{
+    PSP_RenderData *data = (PSP_RenderData *) renderer->driverdata;
+
+    // Check if we need to start GU displaylist
+    if(!data->displayListAvail) {
+        sceGuStart(GU_DIRECT, DisplayList);
+        data->displayListAvail = SDL_TRUE;
+    }
+
+    // Check if we need a draw buffer change
+    if(renderer->target != data->boundTarget) {
+        SDL_Texture* texture = renderer->target;
+        if(texture) {
+            PSP_TextureData* psp_texture = (PSP_TextureData*) texture->driverdata;
+            // Set target, registering LRU
+            TextureBindAsTarget(data, psp_texture);
+        } else {
+            // Set target back to screen
+            sceGuDrawBufferList(data->psm, vrelptr(data->frontbuffer), PSP_FRAME_BUFFER_WIDTH);
+        }
+        data->boundTarget = texture;
+    }
+}
+
+
 static void
 PSP_SetBlendMode(SDL_Renderer * renderer, int blendMode)
 {
@@ -1072,11 +1232,7 @@ PSP_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
     if(psp_texture == 0)
         return;
 
-    if(InVram(psp_texture->data)) {
-        vfree(psp_texture->data);
-    } else {
-        SDL_free(psp_texture->data);
-    }
+    TextureStorageFree(psp_texture->data);
     SDL_free(psp_texture);
     texture->driverdata = NULL;
 }