SDL: [N-Gage] Remove optimisations except for native texture handling prior to some rework of the rendering back-end

From 6e65c3fac4d3f0c9eb017c7d11b2ef49a960e422 Mon Sep 17 00:00:00 2001
From: Michael Fitzmayer <[EMAIL REDACTED]>
Date: Sat, 18 Apr 2026 12:52:22 +0200
Subject: [PATCH] [N-Gage] Remove optimisations except for native texture
 handling prior to some rework of the rendering back-end

[N-Gage] Set proper brush style to draw filled rects properly.

[N-Gage] Add persistent buffers to avoid per-frame memory allocations (which are expensive)

[N-Gage] Add support for SDL_TEXTURE_ACCESS_TARGET, fixes #13165

[N-Gage] Update README, add hint that the compiler does not support aggregate initializations for structs (knowing this, avoids a lot of headache during debugging)

[N-Gage] Add basic fast-path optimisations for render operations.

[N-Gage] Fix line drawing.
---
 docs/README-ngage.md                    |  11 +-
 src/render/ngage/SDL_render_ngage.c     |  66 +--
 src/render/ngage/SDL_render_ngage.cpp   | 682 +++++++-----------------
 src/render/ngage/SDL_render_ngage_c.h   |  26 +-
 src/render/ngage/SDL_render_ngage_c.hpp |  48 +-
 src/render/ngage/SDL_render_ops.cpp     | 517 ++++++++----------
 src/render/ngage/SDL_render_ops.hpp     |   2 +-
 7 files changed, 497 insertions(+), 855 deletions(-)

diff --git a/docs/README-ngage.md b/docs/README-ngage.md
index beed7af0af0d9..eebbcabd810e4 100644
--- a/docs/README-ngage.md
+++ b/docs/README-ngage.md
@@ -33,14 +33,6 @@ software renderer has been removed.
 The outcome is a significantly leaner and more efficient SDL port, which we hope
 will breathe new life into this beloved yet obscure platform.
 
-## To the Stubborn Legends of the DC Scene
-
-This port is lovingly dedicated to the ever-nostalgic Dreamcast homebrew scene --
-because if we managed to pull this off for the N-Gage (yes, the N-Gage), surely
-you guys can stop clinging to SDL2 like it's a rare Shenmue prototype and finally
-make the leap to SDL3.  It's 2025, not 1999 -- and let's be honest, you're rocking
-a state-of-the-art C23 compiler.  The irony writes itself.
-
 ## Existing Issues and Limitations
 
 - For now, the new
@@ -62,3 +54,6 @@ a state-of-the-art C23 compiler.  The irony writes itself.
  expected to be resolved in a future update.
 
 - Dependency tracking is currently non-functional.
+
+- The compiler doesn't support aggregate initialization for structs, so
+  each field must be assigned explicitly.
diff --git a/src/render/ngage/SDL_render_ngage.c b/src/render/ngage/SDL_render_ngage.c
index b960a84d87864..b3710c39dc474 100644
--- a/src/render/ngage/SDL_render_ngage.c
+++ b/src/render/ngage/SDL_render_ngage.c
@@ -155,7 +155,7 @@ static bool NGAGE_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
         return false;
     }
 
-    if (!NGAGE_CreateTextureData(data, texture->w, texture->h)) {
+    if (!NGAGE_CreateTextureData(data, texture->w, texture->h, texture->access)) {
         SDL_free(data);
         return false;
     }
@@ -283,8 +283,12 @@ static bool NGAGE_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SD
     verts->dstrect.h = (int)dstrect->h;
 
     verts->angle = Real2Fix(angle);
-    verts->center.x = Real2Fix(center->x);
-    verts->center.y = Real2Fix(center->y);
+    // Convert center from destination-space to source-space.
+    // Center is relative to dstrect, but rotation is applied in source texture space.
+    float center_x_src = (center->x / dstrect->w) * srcquad->w;
+    float center_y_src = (center->y / dstrect->h) * srcquad->h;
+    verts->center.x = Real2Fix(center_x_src);
+    verts->center.y = Real2Fix(center_y_src);
     verts->scale_x = Real2Fix(scale_x);
     verts->scale_y = Real2Fix(scale_y);
 
@@ -444,27 +448,24 @@ static bool NGAGE_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, co
         return false;
     }
 
-    void *bitmapData = NGAGE_GetBitmapDataAddress(phdata);
-    int bitmapPitch = NGAGE_GetBitmapPitch(phdata);
-
-    if (!bitmapData || bitmapPitch == 0) {
+    Uint8 *dst = (Uint8 *)NGAGE_GetBitmapDataAddress(phdata);
+    if (!dst) {
         return false;
     }
 
-    Uint8 *src = (Uint8 *)pixels;
-    Uint8 *dst = (Uint8 *)bitmapData + rect->y * bitmapPitch + rect->x * 2; // 2 bytes per pixel for EColor4K
+    const int bytes_per_pixel = 2;
+    const int bitmap_pitch = texture->w * bytes_per_pixel;
+
+    const Uint8 *src = (const Uint8 *)pixels;
+    dst += rect->y * bitmap_pitch + rect->x * bytes_per_pixel;
 
-    size_t length = (size_t)rect->w * 2; // 2 bytes per pixel for EColor4K
+    const size_t length = (size_t)rect->w * bytes_per_pixel;
     for (int row = 0; row < rect->h; ++row) {
         SDL_memcpy(dst, src, length);
         src += pitch;
-        dst += bitmapPitch;
+        dst += bitmap_pitch;
     }
 
-    // Mark texture as dirty.
-    phdata->isDirty = true;
-    phdata->dirtyRect = *rect;
-
     return true;
 }
 
@@ -476,34 +477,39 @@ static bool NGAGE_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons
         return false;
     }
 
-    void *bitmapData = NGAGE_GetBitmapDataAddress(phdata);
-    int bitmapPitch = NGAGE_GetBitmapPitch(phdata);
-
-    if (!bitmapData || bitmapPitch == 0) {
+    Uint8 *data = (Uint8 *)NGAGE_GetBitmapDataAddress(phdata);
+    if (!data) {
         return false;
     }
 
-    *pixels = (void *)((Uint8 *)bitmapData + rect->y * bitmapPitch + rect->x * 2); // 2 bytes per pixel for EColor4K
-    *pitch = bitmapPitch;
-
-    // Store the lock rectangle for dirty tracking.
-    phdata->dirtyRect = *rect;
+    const int bytes_per_pixel = 2;
+    const int bitmap_pitch = texture->w * bytes_per_pixel;
 
+    *pixels = (void *)(data + rect->y * bitmap_pitch + rect->x * bytes_per_pixel);
+    *pitch = bitmap_pitch;
     return true;
 }
 
 static void NGAGE_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 {
-    NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal;
-
-    if (phdata) {
-        // Mark texture as dirty after unlock (assume it was modified).
-        phdata->isDirty = true;
-    }
 }
 
 static bool NGAGE_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
+    NGAGE_RendererData *data = (NGAGE_RendererData *)renderer->internal;
+
+    if (texture) {
+        NGAGE_TextureData *texturedata = (NGAGE_TextureData *)texture->internal;
+        if (!texturedata || !texturedata->gc) {
+            return SDL_SetError("Texture is not a render target");
+        }
+        data->current_target = texture;
+        NGAGE_SetRenderTargetInternal(texturedata);
+    } else {
+        data->current_target = NULL;
+        NGAGE_SetRenderTargetInternal(NULL);
+    }
+
     return true;
 }
 
diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp
index 88fe359275354..a4b9a6d0a1753 100644
--- a/src/render/ngage/SDL_render_ngage.cpp
+++ b/src/render/ngage/SDL_render_ngage.cpp
@@ -59,57 +59,33 @@ bool NGAGE_CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, NGAGE_CopyExData
     return gRenderer->CopyEx(renderer, texture, copydata);
 }
 
-bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height)
+bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height, const int access)
 {
-    return gRenderer->CreateTextureData(data, width, height);
+    return gRenderer->CreateTextureData(data, width, height, access);
 }
 
 void NGAGE_DestroyTextureData(NGAGE_TextureData *data)
 {
     if (data) {
+        if (data->gc) {
+            delete data->gc;
+            data->gc = NULL;
+        }
+        if (data->device) {
+            delete data->device;
+            data->device = NULL;
+        }
         delete data->bitmap;
         data->bitmap = NULL;
-
-        // Free cardinal rotation cache.
-        for (int i = 0; i < 4; i++) {
-            if (data->cardinalRotations[i]) {
-                delete data->cardinalRotations[i];
-                data->cardinalRotations[i] = NULL;
-            }
-        }
     }
 }
 
 void *NGAGE_GetBitmapDataAddress(NGAGE_TextureData *data)
 {
-    if (data) {
-        return data->bitmap->DataAddress();
-    }
-    return NULL;
-}
-
-int NGAGE_GetBitmapPitch(NGAGE_TextureData *data)
-{
-    if (data) {
-        return data->cachedPitch;
-    }
-    return 0;
-}
-
-int NGAGE_GetBitmapWidth(NGAGE_TextureData *data)
-{
-    if (data) {
-        return data->cachedWidth;
-    }
-    return 0;
-}
-
-int NGAGE_GetBitmapHeight(NGAGE_TextureData *data)
-{
-    if (data) {
-        return data->cachedHeight;
+    if (!data || !data->bitmap) {
+        return NULL;
     }
-    return 0;
+    return data->bitmap->DataAddress();
 }
 
 void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count)
@@ -139,7 +115,9 @@ void NGAGE_SetClipRect(const SDL_Rect *rect)
 
 void NGAGE_SetDrawColor(const Uint32 color)
 {
-    gRenderer->SetDrawColor(color);
+    if (gRenderer) {
+        gRenderer->SetDrawColor(color);
+    }
 }
 
 void NGAGE_PumpEventsInternal()
@@ -152,19 +130,17 @@ void NGAGE_SuspendScreenSaverInternal(bool suspend)
     gRenderer->SuspendScreenSaver(suspend);
 }
 
+void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target)
+{
+    if (gRenderer) {
+        gRenderer->SetRenderTarget(target);
+    }
+}
+
 #ifdef __cplusplus
 }
 #endif
 
-// Pre-calculated fixed-point angle constants for cardinal rotation checks.
-// These avoid repeated Real2Fix conversions in CopyEx hot path.
-static const TFixed kAngleZero = 0;
-static const TFixed kAnglePi_2 = Real2Fix(M_PI / 2.0);        // 90 degrees
-static const TFixed kAnglePi = Real2Fix(M_PI);                // 180 degrees
-static const TFixed kAnglePi3_2 = Real2Fix(3.0 * M_PI / 2.0); // 270 degrees
-static const TFixed kAnglePi2 = Real2Fix(2.0 * M_PI);         // 360 degrees
-static const TFixed kAngleTolerance = 100;                    // Tolerance for angle comparison
-
 CRenderer *CRenderer::NewL()
 {
     CRenderer *self = new (ELeave) CRenderer();
@@ -174,32 +150,16 @@ CRenderer *CRenderer::NewL()
     return self;
 }
 
-CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSession(), iWsWindowGroup(), iWsWindowGroupID(0), iWsWindow(), iWsScreen(0), iWsEventStatus(), iWsEvent(), iShowFPS(EFalse), iFPS(0), iFont(0), iWorkBuffer1(0), iWorkBuffer2(0), iWorkBufferSize(0), iTempRenderBitmap(0), iTempRenderBitmapWidth(0), iTempRenderBitmapHeight(0), iLastColorR(-1), iLastColorG(-1), iLastColorB(-1), iLinePointsBuffer(0), iLinePointsBufferCapacity(0), iLastDrawColor(0), iLastClearColor(0xFFFFFFFF)
-{
-}
+CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSession(), iWsWindowGroup(), iWsWindowGroupID(0), iWsWindow(), iWsScreen(0), iWsEventStatus(), iWsEvent(), iShowFPS(EFalse), iFPS(0), iFont(0), iCurrentRenderTarget(0), iPixelBufferA(0), iPixelBufferB(0), iPixelBufferSize(0), iPointsBuffer(0), iPointsBufferSize(0) {}
 
 CRenderer::~CRenderer()
 {
     delete iRenderer;
     iRenderer = 0;
 
-    // Free work buffers.
-    SDL_free(iWorkBuffer1);
-    SDL_free(iWorkBuffer2);
-    iWorkBuffer1 = 0;
-    iWorkBuffer2 = 0;
-    iWorkBufferSize = 0;
-
-    // Free temp render bitmap.
-    delete iTempRenderBitmap;
-    iTempRenderBitmap = 0;
-    iTempRenderBitmapWidth = 0;
-    iTempRenderBitmapHeight = 0;
-
-    // Free line points buffer.
-    delete[] iLinePointsBuffer;
-    iLinePointsBuffer = 0;
-    iLinePointsBufferCapacity = 0;
+    SDL_free(iPixelBufferA);
+    SDL_free(iPixelBufferB);
+    delete[] iPointsBuffer;
 }
 
 void CRenderer::ConstructL()
@@ -312,186 +272,11 @@ void CRenderer::AbortNow(RDirectScreenAccess::TTerminationReasons aReason)
 
 void CRenderer::Clear(TUint32 iColor)
 {
-    if (iRenderer && iRenderer->Gc()) {
-        // Skip redundant SetBrushColor if color hasn't changed.
-        if (iColor != iLastClearColor) {
-            iRenderer->Gc()->SetBrushColor(iColor);
-            iLastClearColor = iColor;
-        }
-        iRenderer->Gc()->Clear();
-    }
-}
-
-bool CRenderer::EnsureWorkBufferCapacity(TInt aRequiredSize)
-{
-    if (aRequiredSize <= iWorkBufferSize) {
-        return true;
+    CFbsBitGc *gc = GetCurrentGc();
+    if (gc) {
+        gc->SetBrushColor(iColor);
+        gc->Clear();
     }
-
-    // Free old buffers.
-    SDL_free(iWorkBuffer1);
-    SDL_free(iWorkBuffer2);
-
-    // Allocate new buffers.
-    iWorkBuffer1 = SDL_calloc(1, aRequiredSize);
-    if (!iWorkBuffer1) {
-        iWorkBuffer2 = 0;
-        iWorkBufferSize = 0;
-        return false;
-    }
-
-    iWorkBuffer2 = SDL_calloc(1, aRequiredSize);
-    if (!iWorkBuffer2) {
-        SDL_free(iWorkBuffer1);
-        iWorkBuffer1 = 0;
-        iWorkBufferSize = 0;
-        return false;
-    }
-
-    iWorkBufferSize = aRequiredSize;
-    return true;
-}
-
-bool CRenderer::EnsureLinePointsCapacity(TInt aRequiredCount)
-{
-    if (aRequiredCount <= iLinePointsBufferCapacity) {
-        return true;
-    }
-
-    // Free old buffer.
-    delete[] iLinePointsBuffer;
-
-    // Allocate new buffer.
-    iLinePointsBuffer = new TPoint[aRequiredCount];
-    if (!iLinePointsBuffer) {
-        iLinePointsBufferCapacity = 0;
-        return false;
-    }
-
-    iLinePointsBufferCapacity = aRequiredCount;
-    return true;
-}
-
-bool CRenderer::EnsureTempBitmapCapacity(TInt aWidth, TInt aHeight)
-{
-    if (iTempRenderBitmap &&
-        iTempRenderBitmapWidth >= aWidth &&
-        iTempRenderBitmapHeight >= aHeight) {
-        return true;
-    }
-
-    // Delete old bitmap.
-    delete iTempRenderBitmap;
-    iTempRenderBitmap = 0;
-
-    // Create new bitmap.
-    iTempRenderBitmap = new CFbsBitmap();
-    if (!iTempRenderBitmap) {
-        iTempRenderBitmapWidth = 0;
-        iTempRenderBitmapHeight = 0;
-        return false;
-    }
-
-    TInt error = iTempRenderBitmap->Create(TSize(aWidth, aHeight), EColor4K);
-    if (error != KErrNone) {
-        delete iTempRenderBitmap;
-        iTempRenderBitmap = 0;
-        iTempRenderBitmapWidth = 0;
-        iTempRenderBitmapHeight = 0;
-        return false;
-    }
-
-    iTempRenderBitmapWidth = aWidth;
-    iTempRenderBitmapHeight = aHeight;
-    return true;
-}
-
-void CRenderer::BuildColorModLUT(TFixed rf, TFixed gf, TFixed bf)
-{
-    // Build lookup tables for R, G, B channels.
-    for (int i = 0; i < 256; i++) {
-        TFixed val = i << 16;                                                   // Convert to fixed-point
-        iColorModLUT[i] = (TUint8)SDL_min(Fix2Int(FixMul(val, rf)), 255);       // R
-        iColorModLUT[i + 256] = (TUint8)SDL_min(Fix2Int(FixMul(val, gf)), 255); // G
-        iColorModLUT[i + 512] = (TUint8)SDL_min(Fix2Int(FixMul(val, bf)), 255); // B
-    }
-
-    // Remember the last color to avoid rebuilding unnecessarily.
-    iLastColorR = rf;
-    iLastColorG = gf;
-    iLastColorB = bf;
-}
-
-CFbsBitmap *CRenderer::GetCardinalRotation(NGAGE_TextureData *aTextureData, TInt aAngleIndex)
-{
-    // Check if already cached.
-    if (aTextureData->cardinalRotations[aAngleIndex]) {
-        return aTextureData->cardinalRotations[aAngleIndex];
-    }
-
-    // Create rotated bitmap.
-    CFbsBitmap *rotated = new CFbsBitmap();
-    if (!rotated) {
-        return NULL;
-    }
-
-    TInt w = aTextureData->cachedWidth;
-    TInt h = aTextureData->cachedHeight;
-    TSize size(w, h);
-
-    // For 90 and 270 degree rotations, swap width/height.
-    if (aAngleIndex == 1 || aAngleIndex == 3) {
-        size = TSize(h, w);
-    }
-
-    TInt error = rotated->Create(size, EColor4K);
-    if (error != KErrNone) {
-        delete rotated;
-        return NULL;
-    }
-
-    // Rotate the bitmap data.
-    TUint16 *src = (TUint16 *)aTextureData->cachedDataAddress;
-    TUint16 *dst = (TUint16 *)rotated->DataAddress();
-    TInt srcPitch = aTextureData->cachedPitch >> 1;
-    TInt dstPitch = rotated->ScanLineLength(size.iWidth, rotated->DisplayMode()) >> 1;
-
-    for (int y = 0; y < h; ++y) {
-        for (int x = 0; x < w; ++x) {
-            TUint16 pixel = src[y * srcPitch + x];
-            int dstX = 0;
-            int dstY = 0;
-
-            switch (aAngleIndex) {
-            case 0: // 0 degrees
-                dstX = x;
-                dstY = y;
-                break;
-            case 1: // 90 degrees
-                dstX = h - 1 - y;
-                dstY = x;
-                break;
-            case 2: // 180 degrees
-                dstX = w - 1 - x;
-                dstY = h - 1 - y;
-                break;
-            case 3: // 270 degrees
-                dstX = y;
-                dstY = w - 1 - x;
-                break;
-            default:
-                // Should never happen, but initialize to avoid warnings
-                dstX = x;
-                dstY = y;
-                break;
-            }
-
-            dst[dstY * dstPitch + dstX] = pixel;
-        }
-    }
-
-    aTextureData->cardinalRotations[aAngleIndex] = rotated;
-    return rotated;
 }
 
 #ifdef __cplusplus
@@ -508,9 +293,13 @@ Uint32 NGAGE_ConvertColor(float r, float g, float b, float a, float color_scale)
     TFixed bf = Real2Fix(b);
     TFixed af = Real2Fix(a);
 
-    rf = SDL_clamp(FixMul(rf, scalef), 0, ff);
-    gf = SDL_clamp(FixMul(gf, scalef), 0, ff);
-    bf = SDL_clamp(FixMul(bf, scalef), 0, ff);
+    rf = FixMul(rf, scalef);
+    gf = FixMul(gf, scalef);
+    bf = FixMul(bf, scalef);
+
+    rf = SDL_clamp(rf, 0, ff);
+    gf = SDL_clamp(gf, 0, ff);
+    bf = SDL_clamp(bf, 0, ff);
     af = SDL_clamp(af, 0, ff);
 
     rf = FixMul(rf, ff) >> 16;
@@ -537,80 +326,69 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
     }
 
     SDL_FColor *c = &texture->color;
-
-    // Fast path 1: No transformations needed; direct BitBlt.
-    if (c->a == 1.f && c->r == 1.f && c->g == 1.f && c->b == 1.f) {
-        // Only check render scale if color mod passes.
-        float sx;
-        float sy;
-        SDL_GetRenderScale(renderer, &sx, &sy);
-        if (sx == 1.f && sy == 1.f) {
-            TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h));
-            TPoint aDest(dstrect->x, dstrect->y);
-            iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource);
-            return true;
-        }
-    }
-
-    // Slow path: Transformations needed.
-    float sx;
-    float sy;
-    SDL_GetRenderScale(renderer, &sx, &sy);
-    int w = phdata->cachedWidth;
-    int h = phdata->cachedHeight;
-    int pitch = phdata->cachedPitch;
-    void *source = phdata->cachedDataAddress;
+    int w = texture->w;
+    int h = texture->h;
+    const int bytes_per_pixel = 2;
+    int pitch = w * bytes_per_pixel;
+    void *source = phdata->bitmap->DataAddress();
     void *dest;
 
     if (!source) {
         return false;
     }
 
-    // Ensure work buffers have sufficient capacity.
-    TInt bufferSize = pitch * h;
-    if (!EnsureWorkBufferCapacity(bufferSize)) {
-        return false;
+    TInt required_size = pitch * h;
+    if (required_size > iPixelBufferSize) {
+        void *new_buffer_a = SDL_realloc(iPixelBufferA, required_size);
+        if (!new_buffer_a) {
+            return false;
+        }
+        iPixelBufferA = new_buffer_a;
+
+        void *new_buffer_b = SDL_realloc(iPixelBufferB, required_size);
+        if (!new_buffer_b) {
+            return false;
+        }
+        iPixelBufferB = new_buffer_b;
+
+        iPixelBufferSize = required_size;
     }
 
-    dest = iWorkBuffer1;
+    dest = iPixelBufferA;
 
     if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) {
-        TFixed rf = Real2Fix(c->r);
-        TFixed gf = Real2Fix(c->g);
-        TFixed bf = Real2Fix(c->b);
+        ApplyColorMod(dest, source, pitch, w, h, texture->color);
 
-        // Build LUT if color changed.
-        if (rf != iLastColorR || gf != iLastColorG || bf != iLastColorB) {
-            BuildColorModLUT(rf, gf, bf);
-        }
-
-        ApplyColorMod(dest, source, pitch, w, h, texture->color, iColorModLUT);
         source = dest;
-        dest = (dest == iWorkBuffer1) ? iWorkBuffer2 : iWorkBuffer1;
     }
 
+    float sx;
+    float sy;
+    SDL_GetRenderScale(renderer, &sx, &sy);
+
     if (sx != 1.f || sy != 1.f) {
         TFixed scale_x = Real2Fix(sx);
         TFixed scale_y = Real2Fix(sy);
         TFixed center_x = Int2Fix(w / 2);
         TFixed center_y = Int2Fix(h / 2);
 
+        dest == iPixelBufferA ? dest = iPixelBufferB : dest = iPixelBufferA;
+
         ApplyScale(dest, source, pitch, w, h, center_x, center_y, scale_x, scale_y);
-        source = dest;
-    }
 
-    // Use temp bitmap to avoid destroying source texture.
-    if (!EnsureTempBitmapCapacity(w, h)) {
-        return false;
+        source = dest;
     }
 
-    // Copy transformed data to temp bitmap.
-    Mem::Copy(iTempRenderBitmap->DataAddress(), source, pitch * h);
+    Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h);
 
-    // Render from temp bitmap, preserving original texture.
-    TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h));
-    TPoint aDest(dstrect->x, dstrect->y);
-    iRenderer->Gc()->BitBlt(aDest, iTempRenderBitmap, aSource);
+    if (phdata->bitmap) {
+        CFbsBitGc *gc = GetCurrentGc();
+        if (gc) {
+            TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h));
+            TPoint aDest(dstrect->x, dstrect->y);
+            gc->BitBlt(aDest, phdata->bitmap, aSource);
+        }
+    }
 
     return true;
 }
@@ -623,118 +401,74 @@ bool CRenderer::CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const NGAGE
     }
 
     SDL_FColor *c = &texture->color;
+    int w = texture->w;
+    int h = texture->h;
+    const int bytes_per_pixel = 2;
+    int pitch = w * bytes_per_pixel;
+    void *source = phdata->bitmap->DataAddress();
+    void *dest;
 
-    // Pre-calculate common checks.
-    const bool isNoFlip = (!copydata->flip);
-    const bool isNoRotation = (copydata->angle == 0);
-    const bool isNoColorMod = (c->a == 1.f && c->r == 1.f && c->g == 1.f && c->b == 1.f);
-    const bool isIdentityScale = (copydata->scale_x == Int2Fix(1) && copydata->scale_y == Int2Fix(1));
-
-    // Fast path 1: No transformations needed; direct BitBlt.
-    if (isNoFlip && isNoRotation && isNoColorMod && isIdentityScale) {
-        TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h));
-        TPoint aDest(copydata->dstrect.x, copydata->dstrect.y);
-        iRenderer->Gc()->BitBlt(aDest, phdata->bitmap, aSource);
-        return true;
+    if (!source) {
+        return false;
     }
 
-    // Fast path 2: Check for cardinal rotation cache opportunity (0°, 90°, 180°, 270°).
-    if (isNoFlip && isIdentityScale && isNoColorMod && !isNoRotation) {
-        TFixed angle = copydata->angle;
-        TInt angleIndex = -1;
-
-        // Check cardinal angles with tolerance - optimized for early exit.
-        if (SDL_abs(angle - kAngleZero) < kAngleTolerance) {
-            angleIndex = 0; // 0°
-        } else if (SDL_abs(angle - kAnglePi_2) < kAngleTolerance) {
-            angleIndex = 1; // 90°
-        } else if (SDL_abs(angle - kAnglePi) < kAngleTolerance) {
-            angleIndex = 2; // 180°
-        } else if (SDL_abs(angle - kAnglePi3_2) < kAngleTolerance) {
-            angleIndex = 3; // 270°
-        } else if (SDL_abs(angle - kAnglePi2) < kAngleTolerance) {
-            angleIndex = 0; // 360° = 0°
+    TInt required_size = pitch * h;
+    if (required_size > iPixelBufferSize) {
+        void *new_buffer_a = SDL_realloc(iPixelBufferA, required_size);
+        if (!new_buffer_a) {
+            return false;
         }
+        iPixelBufferA = new_buffer_a;
 
-        if (angleIndex >= 0) {
-            CFbsBitmap *cached = GetCardinalRotation(phdata, angleIndex);
-            if (cached) {
-                TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h));
-                TPoint aDest(copydata->dstrect.x, copydata->dstrect.y);
-                iRenderer->Gc()->BitBlt(aDest, cached, aSource);
-                return true;
-            }
+        void *new_buffer_b = SDL_realloc(iPixelBufferB, required_size);
+        if (!new_buffer_b) {
+            return false;
         }
-    }
+        iPixelBufferB = new_buffer_b;
 
-    // Slow path: Transformations needed.
-    int w = phdata->cachedWidth;
-    int h = phdata->cachedHeight;
-    int pitch = phdata->cachedPitch;
-    void *source = phdata->cachedDataAddress;
-    void *dest;
-
-    if (!source) {
-        return false;
-    }
-
-    // Ensure work buffers have sufficient capacity.
-    TInt bufferSize = pitch * h;
-    if (!EnsureWorkBufferCapacity(bufferSize)) {
-        return false;
+        iPixelBufferSize = required_size;
     }
 
-    dest = iWorkBuffer1;
+    dest = iPixelBufferA;
 
     if (copydata->flip) {
         ApplyFlip(dest, source, pitch, w, h, copydata->flip);
         source = dest;
-        dest = (dest == iWorkBuffer1) ? iWorkBuffer2 : iWorkBuffer1;
     }
 
-    if (!isIdentityScale) {
+    if (copydata->scale_x != 1.f || copydata->scale_y != 1.f) {
+        dest == iPixelBufferA ? dest = iPixelBufferB : dest = iPixelBufferA;
         ApplyScale(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->scale_x, copydata->scale_y);
         source = dest;
-        dest = (dest == iWorkBuffer1) ? iWorkBuffer2 : iWorkBuffer1;
     }
 
     if (copydata->angle) {
+        dest == iPixelBufferA ? dest = iPixelBufferB : dest = iPixelBufferA;
         ApplyRotation(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->angle);
         source = dest;
-        dest = (dest == iWorkBuffer1) ? iWorkBuffer2 : iWorkBuffer1;
     }
 
-    if (!isNoColorMod) {
-        TFixed rf = Real2Fix(c->r);
-        TFixed gf = Real2Fix(c->g);
-        TFixed bf = Real2Fix(c->b);
-
-        // Build LUT if color changed.
-        if (rf != iLastColorR || gf != iLastColorG || bf != iLastColorB) {
-            BuildColorModLUT(rf, gf, bf);
-        }
-
-        ApplyColorMod(dest, source, pitch, w, h, texture->color, iColorModLUT);
+    if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) {
+        dest == iPixelBufferA ? dest = iPixelBufferB : dest = iPixelBufferA;
+        ApplyColorMod(dest, source, pitch, w, h, texture->color);
         source = dest;
     }
 
-    // Use temp bitmap to avoid destroying source texture.
-    if (!EnsureTempBitmapCapacity(w, h)) {
-        return false;
-    }
-
-    // Copy transformed data to temp bitmap.
-    Mem::Copy(iTempRenderBitmap->DataAddress(), source, pitch * h);
+    Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h);
 
-    // Render from temp bitmap, preserving original texture.
-    TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h));
-    TPoint aDest(copydata->dstrect.x, copydata->dstrect.y);
-    iRenderer->Gc()->BitBlt(aDest, iTempRenderBitmap, aSource);
+    if (phdata->bitmap) {
+        CFbsBitGc *gc = GetCurrentGc();
+        if (gc) {
+            TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h));
+            TPoint aDest(copydata->dstrect.x, copydata->dstrect.y);
+            gc->BitBlt(aDest, phdata->bitmap, aSource);
+        }
+    }
 
     return true;
 }
 
-bool CRenderer::CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aWidth, const TInt aHeight)
+bool CRenderer::CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aWidth, const TInt aHeight, const TInt aAccess)
 {
     if (!aTextureData) {
         return false;
@@ -752,101 +486,88 @@ bool CRenderer::CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aW
         return false;
     }
 
-    // Cache texture properties to avoid repeated API calls.
-    TSize bitmapSize = aTextureData->bitmap->SizeInPixels();
-    aTextureData->cachedWidth = bitmapSize.iWidth;
-    aTextureData->cachedHeight = bitmapSize.iHeight;
-    aTextureData->cachedPitch = aTextureData->bitmap->ScanLineLength(aWidth, aTextureData->bitmap->DisplayMode());
-    aTextureData->cachedDataAddress = aTextureData->bitmap->DataAddress();
+    if (aAccess == SDL_TEXTUREACCESS_TARGET) {
+        TRAPD(err1, aTextureData->device = CFbsBitmapDevice::NewL(aTextureData->bitmap));
+        if (err1 != KErrNone || !aTextureData->device) {
+            delete aTextureData->bitmap;
+            aTextureData->bitmap = NULL;
+            return false;
+        }
 
-    // Initialize cardinal rotation cache to NULL.
-    for (int i = 0; i < 4; i++) {
-        aTextureData->cardinalRotations[i] = NULL;
-    }
+        TRAPD(err2, aTextureData->gc = CFbsBitGc::NewL());
+        if (err2 != KErrNone || !aTextureData->gc) {
+            delete aTextureData->device;
+            aTextureData->device = NULL;
+            delete aTextureData->bitmap;
+            aTextureData->bitmap = NULL;
+            return false;
+        }
 
-    // Initialize dirty tracking.
-    aTextureData->isDirty = true; // New textures start dirty.
-    aTextureData->dirtyRect.x = 0;
-    aTextureData->dirtyRect.y = 0;
-    aTextureData->dirtyRect.w = aWidth;
-    aTextureData->dirtyRect.h = aHeight;
+        aTextureData->gc->Activate(aTextureData->device);
+    } else {
+        aTextureData->gc = NULL;
+        aTextureData->device = NULL;
+    }
 
     return true;
 }
 
 void CRenderer::DrawLines(NGAGE_Vertex *aVerts, const TInt aCount)
 {
-    if (iRenderer && iRenderer->Gc()) {
-        // Ensure reusable buffer has sufficient capacity.
-        if (!EnsureLinePointsCapacity(aCount)) {
-            return;
-        }
+    CFbsBitGc *gc = GetCurrentGc();
+    if (gc) {
+        gc->SetPenStyle(CGraphicsContext::ESolidPen);
 
-        // Fill points from vertex data.
-        for (TInt i = 0; i < aCount; i++) {
-            iLinePointsBuffer[i] = TPoint(aVerts[i].x, aVerts[i].y);
-        }
+        // Draw lines as pairs of points (start, end)
+        for (TInt i = 0; i < aCount - 1; i += 2) {
+            TPoint start(aVerts[i].x, aVerts[i].y);
+            TPoint end(aVerts[i + 1].x, aVerts[i + 1].y);
 
-        // Pack color once - all vertices use the same color in polyline.
-        Uint8 ca = aVerts->color.a;
-        Uint8 cr = aVerts->color.r;
-        Uint8 cg = aVerts->color.g;
-        Uint8 cb = aVerts->color.b;
-        TUint32 aColor = (ca << 24) | (cb << 16) | (cg << 8) | cr;
+            TRgb color = TRgb(aVerts[i].color.r, aVerts[i].color.g, aVerts[i].color.b);
 
-        iRenderer->Gc()->SetPenColor(aColor);
-        iRenderer->Gc()->DrawPolyLineNoEndPoint(iLinePointsBuffer, aCount);
+            gc->SetPenColor(color);
+            gc->DrawLine(start, end);
+        }
     }
 }
 
 void CRenderer::DrawPoints(NGAGE_Vertex *aVerts, const TInt aCount)
 {
-    if (iRenderer && iRenderer->Gc()) {
-        // Batch points by color to minimize SetPenColor calls.
-        TUint32 currentColor = 0;
-        bool colorSet = false;
-
+    CFbsBitGc *gc = GetCurrentGc();
+    if (gc) {
         for (TInt i = 0; i < aCount; i++, aVerts++) {
-            TUint32 aColor = (TUint32(aVerts->color.a) << 24) | (TUint32(aVerts->color.b) << 16) |
-                             (TUint32(aVerts->color.g) << 8) | TUint32(aVerts->color.r);
-
-            // Only set pen color when it changes.
-            if (!colorSet || aColor != currentColor) {
-                iRenderer->Gc()->SetPenColor(aColor);
-                currentColor = aColor;
-                colorSet = true;
-            }
+            TUint32 aColor = (((TUint8)aVerts->color.a << 24) |
+                              ((TUint8)

(Patch may be truncated, please check the link at the top of this post.)