From e5c8523b36fae58983d020414f8dec22359e6e04 Mon Sep 17 00:00:00 2001
From: Michael Fitzmayer <[EMAIL REDACTED]>
Date: Wed, 15 Apr 2026 20:05:26 +0200
Subject: [PATCH] [N-Gage] Preserve source textures and optimize rotation with
DDA
- Add temporary render bitmap to avoid destroying source texture data
- Implement incremental DDA algorithm for rotation
- Replaces per-pixel FixMul operations with simple additions and preserves
textures for reuse.
---
src/render/ngage/SDL_render_ngage.cpp | 70 +++++++++++++++++++++----
src/render/ngage/SDL_render_ngage_c.hpp | 8 ++-
src/render/ngage/SDL_render_ops.cpp | 40 +++++++++-----
3 files changed, 94 insertions(+), 24 deletions(-)
diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp
index 18f5084139afe..47ba9fbf9ad30 100644
--- a/src/render/ngage/SDL_render_ngage.cpp
+++ b/src/render/ngage/SDL_render_ngage.cpp
@@ -160,7 +160,7 @@ 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) {}
+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) {}
CRenderer::~CRenderer()
{
@@ -173,6 +173,12 @@ CRenderer::~CRenderer()
iWorkBuffer1 = 0;
iWorkBuffer2 = 0;
iWorkBufferSize = 0;
+
+ // Free temp render bitmap.
+ delete iTempRenderBitmap;
+ iTempRenderBitmap = 0;
+ iTempRenderBitmapWidth = 0;
+ iTempRenderBitmapHeight = 0;
}
void CRenderer::ConstructL()
@@ -321,6 +327,40 @@ bool CRenderer::EnsureWorkBufferCapacity(TInt aRequiredSize)
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;
+}
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -421,14 +461,18 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
useBuffer1 = !useBuffer1;
}
- // Render directly from work buffer without copying back to bitmap.
- // Note: We need a temporary bitmap for rendering the transformed data.
- // For now, copy to original bitmap (this could be further optimized with a render target).
- Mem::Copy(phdata->cachedDataAddress, source, pitch * h);
+ // 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);
+ // 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, phdata->bitmap, aSource);
+ iRenderer->Gc()->BitBlt(aDest, iTempRenderBitmap, aSource);
return true;
}
@@ -500,14 +544,18 @@ bool CRenderer::CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const NGAGE
useBuffer1 = !useBuffer1;
}
- // Render directly from work buffer without copying back to bitmap.
- // Note: We need a temporary bitmap for rendering the transformed data.
- // For now, copy to original bitmap (this could be further optimized with a render target).
- Mem::Copy(phdata->cachedDataAddress, source, pitch * h);
+ // 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);
+ // 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, phdata->bitmap, aSource);
+ iRenderer->Gc()->BitBlt(aDest, iTempRenderBitmap, aSource);
return true;
}
diff --git a/src/render/ngage/SDL_render_ngage_c.hpp b/src/render/ngage/SDL_render_ngage_c.hpp
index 007b7145a7af9..600d4d0622933 100644
--- a/src/render/ngage/SDL_render_ngage_c.hpp
+++ b/src/render/ngage/SDL_render_ngage_c.hpp
@@ -92,8 +92,14 @@ class CRenderer : public MDirectScreenAccess
void *iWorkBuffer2;
TInt iWorkBufferSize;
- // Helper method to ensure work buffers have sufficient capacity.
+ // Temporary render bitmap to avoid destroying source textures.
+ CFbsBitmap *iTempRenderBitmap;
+ TInt iTempRenderBitmapWidth;
+ TInt iTempRenderBitmapHeight;
+
+ // Helper methods.
bool EnsureWorkBufferCapacity(TInt aRequiredSize);
+ bool EnsureTempBitmapCapacity(TInt aWidth, TInt aHeight);
};
#endif // ngage_video_render_ngage_c_hpp
diff --git a/src/render/ngage/SDL_render_ops.cpp b/src/render/ngage/SDL_render_ops.cpp
index 8a2244436b67b..89006e662c55a 100644
--- a/src/render/ngage/SDL_render_ops.cpp
+++ b/src/render/ngage/SDL_render_ops.cpp
@@ -87,26 +87,42 @@ void ApplyRotation(void *dest, void *source, int pitch, int width, int height, T
FixSinCos(angle, sin_angle, cos_angle);
}
+ // Pre-calculate pitch in pixels to avoid repeated division.
+ const TInt pitchPixels = pitch >> 1;
+
+ // Incremental DDA: Calculate per-pixel increments.
+ // As we move right (x+1), the rotated position changes by (cos, -sin).
+ TFixed dx_cos = cos_angle;
+ TFixed dx_sin = -sin_angle;
+
for (int y = 0; y < height; ++y) {
- for (int x = 0; x < width; ++x) {
- // Translate point to origin.
- TFixed translated_x = Int2Fix(x) - center_x;
- TFixed translated_y = Int2Fix(y) - center_y;
+ // Calculate destination row offset once per row.
+ TInt dstRowOffset = y * pitchPixels;
- // Rotate point (clockwise).
- TFixed rotated_x = FixMul(translated_x, cos_angle) + FixMul(translated_y, sin_angle);
- TFixed rotated_y = FixMul(translated_y, cos_angle) - FixMul(translated_x, sin_angle);
+ // Calculate starting position for this row.
+ TFixed translated_y = Int2Fix(y) - center_y;
+ TFixed row_start_x = FixMul(translated_y, sin_angle) + center_x;
+ TFixed row_start_y = FixMul(translated_y, cos_angle) + center_y;
- // Translate point back.
- int final_x = Fix2Int(rotated_x + center_x);
- int final_y = Fix2Int(rotated_y + center_y);
+ // For first pixel in row, account for x=0 translation.
+ TFixed src_x = row_start_x - FixMul(center_x, cos_angle);
+ TFixed src_y = row_start_y + FixMul(center_x, sin_angle);
+
+ for (int x = 0; x < width; ++x) {
+ // Convert to integer coordinates.
+ int final_x = Fix2Int(src_x);
+ int final_y = Fix2Int(src_y);
// Check bounds.
if (final_x >= 0 && final_x < width && final_y >= 0 && final_y < height) {
- dst_pixels[y * pitch / 2 + x] = src_pixels[final_y * pitch / 2 + final_x];
+ dst_pixels[dstRowOffset + x] = src_pixels[final_y * pitchPixels + final_x];
} else {
- dst_pixels[y * pitch / 2 + x] = 0;
+ dst_pixels[dstRowOffset + x] = 0;
}
+
+ // Incremental step: move to next pixel (just additions, no multiplications!).
+ src_x += dx_cos;
+ src_y += dx_sin;
}
}
}