From 1cb61df1e51a715916233ba91e845c66a5a87e81 Mon Sep 17 00:00:00 2001 From: Michael Fitzmayer Date: Fri, 1 May 2026 11:14:17 +0200 Subject: [PATCH] [N-Gage] Fix image transformations, add color keying, and improve overall rendering performance. Fixes #15427 [N-Gage] Correct SDL_FLIP_HORIZONTAL for sprite sheet textures - ApplyFlip: swap toward midpoint when dest == source to avoid overwriting pixels before they are read - CopyEx: restore original bitmap pixels after BitBlt so the texture is not permanently mutated across frames - CopyEx: extract srcrect sub-region before transforming so flip/ rotate/scale operate on the correct pixels, not the full texture [N-Gage] Use scratch bitmap in CopyEx to avoid mutating source texture. Replace the write-then-restore pattern on the source texture with a persistent iScratchBitmap. The transform pipeline operates entirely within iPixelBufferA/B; the final result is copied into iScratchBitmap and BitBlt reads from there. The source texture is never written to. [N-Gage] Fix Copy() for render-target textures by using direct BitBlt Bypass pixel readback for SDL_TEXTUREACCESS_TARGET textures in CRenderer::Copy(). Reading raw pixels via DataAddress() on a server-side bitmap is unreliable; use BitBlt directly instead. [N-Gage] Implement color-key masking using Symbian's BitBltMasked. This implementation works fine, but relies on a platform specific property. This isn't ideal. Todo: Add SDL_PIXELFORMAT_ARGB4444 to the N-Gage's rendering back-end. [N-Gage] Avoid using BitBltMasked to improve performance. Much better! Write only non-color-key source pixels directly into the destination bitmap preserving existing destination pixels wherever the color key matches. This replaces BitBltMasked and avoids the cost of building an EGray2 mask. [N-Gage] Remove redundant function call since we're not using BitBltMasked() anymore. [N-Gage] Remove SDL_PROP_TEXTURE_NGAGE_COLOR_KEY_NUMBER - Promote SDL_PIXELFORMAT_XRGB4444 to SDL_PIXELFORMAT_ARGB4444 instead. - Remove now unused functions. --- src/render/SDL_render.c | 10 +- src/render/ngage/SDL_render_ngage.c | 17 +- src/render/ngage/SDL_render_ngage.cpp | 258 +++++++++++++++++++----- src/render/ngage/SDL_render_ngage_c.h | 7 +- src/render/ngage/SDL_render_ngage_c.hpp | 5 +- src/render/ngage/SDL_render_ops.cpp | 71 ++++++- src/render/ngage/SDL_render_ops.hpp | 2 + 7 files changed, 307 insertions(+), 63 deletions(-) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 4d76a7405e..5f80fae6d6 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1799,6 +1799,13 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s break; } } + } else if (surface->format == SDL_PIXELFORMAT_XRGB4444) { + for (i = 0; i < renderer->num_texture_formats; ++i) { + if (renderer->texture_formats[i] == SDL_PIXELFORMAT_ARGB4444) { + format = SDL_PIXELFORMAT_ARGB4444; + break; + } + } } } else { // Exact match would be fine @@ -1893,7 +1900,8 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, surface->w); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, surface->h); - texture = SDL_CreateTextureWithProperties(renderer, props); + +texture = SDL_CreateTextureWithProperties(renderer, props); SDL_DestroyProperties(props); if (!texture) { return NULL; diff --git a/src/render/ngage/SDL_render_ngage.c b/src/render/ngage/SDL_render_ngage.c index b3710c39dc..bee541d280 100644 --- a/src/render/ngage/SDL_render_ngage.c +++ b/src/render/ngage/SDL_render_ngage.c @@ -116,6 +116,7 @@ static bool NGAGE_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL renderer->npot_texture_wrap_unsupported = true; SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB4444); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB4444); SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 1024); SDL_SetHintWithPriority(SDL_HINT_RENDER_LINE_METHOD, "2", SDL_HINT_OVERRIDE); @@ -162,6 +163,12 @@ static bool NGAGE_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD texture->internal = data; + // ARGB4444 textures are color-keyed: alpha=0 pixels are transparent. + if (texture->format == SDL_PIXELFORMAT_ARGB4444) { + data->has_color_key = true; + data->mask_dirty = true; + } + return true; } @@ -454,7 +461,7 @@ static bool NGAGE_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, co } const int bytes_per_pixel = 2; - const int bitmap_pitch = texture->w * bytes_per_pixel; + const int bitmap_pitch = NGAGE_GetBitmapScanLineLength(phdata); const Uint8 *src = (const Uint8 *)pixels; dst += rect->y * bitmap_pitch + rect->x * bytes_per_pixel; @@ -466,6 +473,8 @@ static bool NGAGE_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, co dst += bitmap_pitch; } + phdata->mask_dirty = true; + return true; } @@ -483,7 +492,7 @@ static bool NGAGE_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons } const int bytes_per_pixel = 2; - const int bitmap_pitch = texture->w * bytes_per_pixel; + const int bitmap_pitch = NGAGE_GetBitmapScanLineLength(phdata); *pixels = (void *)(data + rect->y * bitmap_pitch + rect->x * bytes_per_pixel); *pitch = bitmap_pitch; @@ -492,6 +501,10 @@ static bool NGAGE_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, cons static void NGAGE_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) { + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; + if (phdata) { + phdata->mask_dirty = true; + } } static bool NGAGE_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp index a4b9a6d0a1..cd312406af 100644 --- a/src/render/ngage/SDL_render_ngage.cpp +++ b/src/render/ngage/SDL_render_ngage.cpp @@ -75,6 +75,10 @@ void NGAGE_DestroyTextureData(NGAGE_TextureData *data) delete data->device; data->device = NULL; } + if (data->mask_bitmap) { + delete data->mask_bitmap; + data->mask_bitmap = NULL; + } delete data->bitmap; data->bitmap = NULL; } @@ -88,6 +92,14 @@ void *NGAGE_GetBitmapDataAddress(NGAGE_TextureData *data) return data->bitmap->DataAddress(); } +int NGAGE_GetBitmapScanLineLength(NGAGE_TextureData *data) +{ + if (!data || !data->bitmap) { + return 0; + } + return (int)CFbsBitmap::ScanLineLength(data->bitmap->SizeInPixels().iWidth, EColor4K); +} + void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count) { gRenderer->DrawLines(verts, count); @@ -150,7 +162,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), iCurrentRenderTarget(0), iPixelBufferA(0), iPixelBufferB(0), iPixelBufferSize(0), iPointsBuffer(0), iPointsBufferSize(0) {} +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), iScratchBitmap(0), iMaskBitmap(0), iPointsBuffer(0), iPointsBufferSize(0) {} CRenderer::~CRenderer() { @@ -159,6 +171,10 @@ CRenderer::~CRenderer() SDL_free(iPixelBufferA); SDL_free(iPixelBufferB); + delete iScratchBitmap; + iScratchBitmap = 0; + delete iMaskBitmap; + iMaskBitmap = 0; delete[] iPointsBuffer; } @@ -326,18 +342,55 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec } 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; - if (!source) { + int sw = srcrect->w; + int sh = srcrect->h; + + // Fast path: render target texture with no color mod. + // BitBlt directly from its bitmap — DataAddress() is unreliable + // for bitmaps that have been drawn into via a CFbsBitGc. + bool no_color_mod = (c->a == 1.f && c->r == 1.f && c->g == 1.f && c->b == 1.f); + float sx, sy; + SDL_GetRenderScale(renderer, &sx, &sy); + bool no_scale = (sx == 1.f && sy == 1.f); + + SDL_BlendMode blend; + SDL_GetTextureBlendMode(texture, &blend); + bool no_color_key = (blend != SDL_BLENDMODE_BLEND); + + if (phdata->gc && no_color_mod && no_scale && no_color_key) { + CFbsBitGc *gc = GetCurrentGc(); + if (gc) { + TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(sw, sh)); + TPoint aDest(dstrect->x, dstrect->y); + gc->BitBlt(aDest, phdata->bitmap, aSource); + } + return true; + } + + // Fast path: color-key with no color mod and no scale. + // Blit directly from the source bitmap into the destination, skipping transparent pixels. + if (no_color_mod && no_scale && !no_color_key && phdata->has_color_key) { + void *tex_data_ck = phdata->bitmap->DataAddress(); + CFbsBitmap *dst_bmp = GetCurrentBitmap(); + if (dst_bmp && tex_data_ck) { + int tex_stride_ck = CFbsBitmap::ScanLineLength(phdata->bitmap->SizeInPixels().iWidth, EColor4K) / 2; + TUint16 *src_base = static_cast(tex_data_ck) + srcrect->y * tex_stride_ck + srcrect->x; + BlitWithAlphaKey(dst_bmp, dstrect->x, dstrect->y, src_base, sw, sh, tex_stride_ck); + } + return true; + } + + int src_pitch = sw * bytes_per_pixel; + int tex_pitch = CFbsBitmap::ScanLineLength(texture->w, EColor4K); + + void *tex_data = phdata->bitmap->DataAddress(); + if (!tex_data) { return false; } - TInt required_size = pitch * h; + TInt required_size = src_pitch * sh; if (required_size > iPixelBufferSize) { void *new_buffer_a = SDL_realloc(iPixelBufferA, required_size); if (!new_buffer_a) { @@ -354,39 +407,82 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec iPixelBufferSize = required_size; } - dest = iPixelBufferA; - - if (c->a != 1.f || c->r != 1.f || c->g != 1.f || c->b != 1.f) { - ApplyColorMod(dest, source, pitch, w, h, texture->color); - - source = dest; + // Ensure scratch bitmap is allocated and large enough. + if (!iScratchBitmap) { + iScratchBitmap = new CFbsBitmap(); + if (!iScratchBitmap) { + return false; + } + } + TSize scratch_size = iScratchBitmap->SizeInPixels(); + if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) { + iScratchBitmap->Reset(); + TInt err = iScratchBitmap->Create(TSize(sw, sh), EColor4K); + if (err != KErrNone) { + return false; + } } - float sx; - float sy; - SDL_GetRenderScale(renderer, &sx, &sy); + // Extract the srcrect region from the texture into buffer A. + { + TUint16 *tex_pixels = (TUint16 *)tex_data; + TUint16 *buf_pixels = (TUint16 *)iPixelBufferA; + int tex_pitch_u16 = tex_pitch / 2; + for (int y = 0; y < sh; ++y) { + TUint16 *src_row = tex_pixels + (srcrect->y + y) * tex_pitch_u16 + srcrect->x; + TUint16 *dst_row = buf_pixels + y * sw; + Mem::Copy(dst_row, src_row, src_pitch); + } + } - if (sx != 1.f || sy != 1.f) { + void *source = iPixelBufferA; + void *dest = iPixelBufferB; + + if (!no_color_mod) { + ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color); + void *tmp = source; + source = dest; + dest = tmp; + } + + if (!no_scale) { 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); - + TFixed center_x = Int2Fix(sw / 2); + TFixed center_y = Int2Fix(sh / 2); + ApplyScale(dest, source, src_pitch, sw, sh, center_x, center_y, scale_x, scale_y); + void *tmp = source; source = dest; + dest = tmp; } - Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + // Copy result into scratch bitmap and blit from there. + // The source texture is never modified. + { + TUint16 *scratch_pixels = (TUint16 *)iScratchBitmap->DataAddress(); + TUint16 *res_pixels = (TUint16 *)source; + int scratch_pitch_u16 = CFbsBitmap::ScanLineLength(iScratchBitmap->SizeInPixels().iWidth, EColor4K) / 2; + + // Always copy all pixels into the scratch bitmap. + for (int y = 0; y < sh; ++y) { + TUint16 *dst_row = scratch_pixels + y * scratch_pitch_u16; + TUint16 *src_row = res_pixels + y * sw; + Mem::Copy(dst_row, src_row, src_pitch); + } - if (phdata->bitmap) { CFbsBitGc *gc = GetCurrentGc(); if (gc) { - TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(srcrect->w, srcrect->h)); + TRect aSource(TPoint(0, 0), TSize(sw, sh)); TPoint aDest(dstrect->x, dstrect->y); - gc->BitBlt(aDest, phdata->bitmap, aSource); + + if (!no_color_key && phdata->has_color_key) { + CFbsBitmap *dst_bmp = GetCurrentBitmap(); + if (dst_bmp) { + BlitWithAlphaKey(dst_bmp, dstrect->x, dstrect->y, res_pixels, sw, sh, sw); + } + } else { + gc->BitBlt(aDest, iScratchBitmap, aSource); + } } } @@ -401,18 +497,19 @@ 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; - if (!source) { + int sw = copydata->srcrect.w; + int sh = copydata->srcrect.h; + int src_pitch = sw * bytes_per_pixel; + int tex_pitch = CFbsBitmap::ScanLineLength(texture->w, EColor4K); + + void *tex_data = phdata->bitmap->DataAddress(); + if (!tex_data) { return false; } - TInt required_size = pitch * h; + TInt required_size = src_pitch * sh; if (required_size > iPixelBufferSize) { void *new_buffer_a = SDL_realloc(iPixelBufferA, required_size); if (!new_buffer_a) { @@ -429,39 +526,96 @@ bool CRenderer::CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, const NGAGE iPixelBufferSize = required_size; } - dest = iPixelBufferA; + // Ensure scratch bitmap is allocated and large enough for the srcrect. + if (!iScratchBitmap) { + iScratchBitmap = new CFbsBitmap(); + if (!iScratchBitmap) { + return false; + } + } + TSize scratch_size = iScratchBitmap->SizeInPixels(); + if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) { + iScratchBitmap->Reset(); + TInt err = iScratchBitmap->Create(TSize(sw, sh), EColor4K); + if (err != KErrNone) { + return false; + } + } + + // Extract the srcrect region from the texture into buffer A. + { + TUint16 *tex_pixels = (TUint16 *)tex_data; + TUint16 *buf_pixels = (TUint16 *)iPixelBufferA; + int tex_pitch_u16 = tex_pitch / 2; + for (int y = 0; y < sh; ++y) { + TUint16 *src_row = tex_pixels + (copydata->srcrect.y + y) * tex_pitch_u16 + copydata->srcrect.x; + TUint16 *dst_row = buf_pixels + y * sw; + Mem::Copy(dst_row, src_row, src_pitch); + } + } + + void *source = iPixelBufferA; + void *dest = iPixelBufferB; if (copydata->flip) { - ApplyFlip(dest, source, pitch, w, h, copydata->flip); + ApplyFlip(dest, source, src_pitch, sw, sh, copydata->flip); + void *tmp = source; source = dest; + dest = tmp; } 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); + ApplyScale(dest, source, src_pitch, sw, sh, copydata->center.x, copydata->center.y, copydata->scale_x, copydata->scale_y); + void *tmp = source; source = dest; + dest = tmp; } if (copydata->angle) { - dest == iPixelBufferA ? dest = iPixelBufferB : dest = iPixelBufferA; - ApplyRotation(dest, source, pitch, w, h, copydata->center.x, copydata->center.y, copydata->angle); + ApplyRotation(dest, source, src_pitch, sw, sh, copydata->center.x, copydata->center.y, copydata->angle); + void *tmp = source; source = dest; + dest = tmp; } 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); + ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color); + void *tmp = source; source = dest; + dest = tmp; } - Mem::Copy(phdata->bitmap->DataAddress(), source, pitch * h); + // Copy the final result into the scratch bitmap and blit from there. + // The source texture is never modified. + { + SDL_BlendMode blend; + SDL_GetTextureBlendMode(texture, &blend); + bool has_color_key = (blend == SDL_BLENDMODE_BLEND); + + TUint16 *scratch_pixels = (TUint16 *)iScratchBitmap->DataAddress(); + TUint16 *res_pixels = (TUint16 *)source; + int scratch_pitch_u16 = CFbsBitmap::ScanLineLength(iScratchBitmap->SizeInPixels().iWidth, EColor4K) / 2; + + // Always copy all pixels into the scratch bitmap. + for (int y = 0; y < sh; ++y) { + TUint16 *dst_row = scratch_pixels + y * scratch_pitch_u16; + TUint16 *src_row = res_pixels + y * sw; + Mem::Copy(dst_row, src_row, src_pitch); + } - if (phdata->bitmap) { CFbsBitGc *gc = GetCurrentGc(); if (gc) { - TRect aSource(TPoint(copydata->srcrect.x, copydata->srcrect.y), TSize(copydata->srcrect.w, copydata->srcrect.h)); + TRect aSource(TPoint(0, 0), TSize(sw, sh)); TPoint aDest(copydata->dstrect.x, copydata->dstrect.y); - gc->BitBlt(aDest, phdata->bitmap, aSource); + + if (has_color_key && phdata->has_color_key) { + CFbsBitmap *dst_bmp = GetCurrentBitmap(); + if (dst_bmp) { + BlitWithAlphaKey(dst_bmp, copydata->dstrect.x, copydata->dstrect.y, res_pixels, sw, sh, sw); + } + } else { + gc->BitBlt(aDest, iScratchBitmap, aSource); + } } } @@ -684,6 +838,14 @@ CFbsBitGc *CRenderer::GetCurrentGc() return iRenderer ? iRenderer->Gc() : NULL; } +CFbsBitmap *CRenderer::GetCurrentBitmap() +{ + if (iCurrentRenderTarget && iCurrentRenderTarget->bitmap) { + return iCurrentRenderTarget->bitmap; + } + return iRenderer ? iRenderer->Bitmap() : NULL; +} + static SDL_Scancode ConvertScancode(int key) { SDL_Keycode keycode; diff --git a/src/render/ngage/SDL_render_ngage_c.h b/src/render/ngage/SDL_render_ngage_c.h index 20f1af17cf..76a3619989 100644 --- a/src/render/ngage/SDL_render_ngage_c.h +++ b/src/render/ngage/SDL_render_ngage_c.h @@ -30,6 +30,7 @@ extern "C" { #endif #include "../SDL_sysrender.h" +#include "SDL3/SDL_render.h" typedef struct NGAGE_RendererData { @@ -63,6 +64,9 @@ typedef struct NGAGE_TextureData CFbsBitmap *bitmap; CFbsBitGc *gc; CFbsDevice *device; + CFbsBitmap *mask_bitmap; + bool has_color_key; + bool mask_dirty; } NGAGE_TextureData; @@ -93,7 +97,8 @@ bool NGAGE_Copy(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect *srcrect, bool NGAGE_CopyEx(SDL_Renderer *renderer, SDL_Texture *texture, NGAGE_CopyExData *copydata); bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int height, const int access); void NGAGE_DestroyTextureData(NGAGE_TextureData *data); -void* NGAGE_GetBitmapDataAddress(NGAGE_TextureData *data); +void *NGAGE_GetBitmapDataAddress(NGAGE_TextureData *data); +int NGAGE_GetBitmapScanLineLength(NGAGE_TextureData *data); void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count); void NGAGE_DrawPoints(NGAGE_Vertex *verts, const int count); void NGAGE_FillRects(NGAGE_Vertex *verts, const int count); diff --git a/src/render/ngage/SDL_render_ngage_c.hpp b/src/render/ngage/SDL_render_ngage_c.hpp index b7776ec589..97e4ff272a 100644 --- a/src/render/ngage/SDL_render_ngage_c.hpp +++ b/src/render/ngage/SDL_render_ngage_c.hpp @@ -49,7 +49,8 @@ class CRenderer : public MDirectScreenAccess // Render target management. void SetRenderTarget(NGAGE_TextureData *aTarget); - CFbsBitGc* GetCurrentGc(); + CFbsBitGc *GetCurrentGc(); + CFbsBitmap *GetCurrentBitmap(); // Event handling. void DisableKeyBlocking(); @@ -98,6 +99,8 @@ class CRenderer : public MDirectScreenAccess void *iPixelBufferA; void *iPixelBufferB; TInt iPixelBufferSize; + CFbsBitmap *iScratchBitmap; + CFbsBitmap *iMaskBitmap; TPoint *iPointsBuffer; TInt iPointsBufferSize; }; diff --git a/src/render/ngage/SDL_render_ops.cpp b/src/render/ngage/SDL_render_ops.cpp index 3b998d8132..51d369d457 100644 --- a/src/render/ngage/SDL_render_ops.cpp +++ b/src/render/ngage/SDL_render_ops.cpp @@ -64,6 +64,36 @@ void ApplyColorMod(void *dest, void *source, int pitch, int width, int height, S } } +void BlitWithAlphaKey(CFbsBitmap *dst, int dst_x, int dst_y, const void *src, int src_width, int src_height, int src_stride_u16) +{ + // Write only non-transparent (alpha != 0) source pixels into the destination bitmap. + // Pixels with alpha nibble == 0 are treated as color-key (transparent) and skipped. + TUint16 *dst_data = static_cast(static_cast(dst->DataAddress())); + const TUint16 *src_pixels = static_cast(src); + TSize dst_size = dst->SizeInPixels(); + int dst_stride_u16 = CFbsBitmap::ScanLineLength(dst_size.iWidth, EColor4K) / 2; + + for (int y = 0; y < src_height; ++y) { + int dy = dst_y + y; + if (dy < 0 || dy >= dst_size.iHeight) { + continue; + } + const TUint16 *src_row = src_pixels + y * src_stride_u16; + TUint16 *dst_row = dst_data + dy * dst_stride_u16; + for (int x = 0; x < src_width; ++x) { + int dx = dst_x + x; + if (dx < 0 || dx >= dst_size.iWidth) { + continue; + } + TUint16 p = src_row[x]; + if (p & 0xF000) { + // Strip the alpha nibble and write the RGB444 pixel. + dst_row[dx] = p & 0x0FFF; + } + } + } +} + void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_FlipMode flip) { TUint16 *src_pixels = static_cast(source); @@ -87,12 +117,19 @@ void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_F // Fast path: horizontal flip only. if (flip == SDL_FLIP_HORIZONTAL) { + int width_minus_1 = width - 1; for (int y = 0; y < height; ++y) { - int dst_row_offset = y * pitch_offset; - int src_row_offset = y * pitch_offset; - int width_minus_1 = width - 1; - for (int x = 0; x < width; ++x) { - dst_pixels[dst_row_offset + x] = src_pixels[src_row_offset + (width_minus_1 - x)]; + int row_offset = y * pitch_offset; + if (dest == source) { + for (int x = 0; x < width / 2; ++x) { + TUint16 tmp = src_pixels[row_offset + x]; + src_pixels[row_offset + x] = src_pixels[row_offset + (width_minus_1 - x)]; + src_pixels[row_offset + (width_minus_1 - x)] = tmp; + } + } else { + for (int x = 0; x < width; ++x) { + dst_pixels[row_offset + x] = src_pixels[row_offset + (width_minus_1 - x)]; + } } } return; @@ -114,11 +151,25 @@ void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_F // Both horizontal and vertical flip int width_minus_1 = width - 1; int height_minus_1 = height - 1; - for (int y = 0; y < height; ++y) { - int dst_row_offset = y * pitch_offset; - int src_row_offset = (height_minus_1 - y) * pitch_offset; - for (int x = 0; x < width; ++x) { - dst_pixels[dst_row_offset + x] = src_pixels[src_row_offset + (width_minus_1 - x)]; + if (dest == source) { + // Swap pixels across the center point. + for (int y = 0; y < (height + 1) / 2; ++y) { + int top_offset = y * pitch_offset; + int bot_offset = (height_minus_1 - y) * pitch_offset; + int x_limit = (y == height_minus_1 - y) ? width / 2 : width; + for (int x = 0; x < x_limit; ++x) { + TUint16 tmp = src_pixels[top_offset + x]; + src_pixels[top_offset + x] = src_pixels[bot_offset + (width_minus_1 - x)]; + src_pixels[bot_offset + (width_minus_1 - x)] = tmp; + } + } + } else { + for (int y = 0; y < height; ++y) { + int dst_row_offset = y * pitch_offset; + int src_row_offset = (height_minus_1 - y) * pitch_offset; + for (int x = 0; x < width; ++x) { + dst_pixels[dst_row_offset + x] = src_pixels[src_row_offset + (width_minus_1 - x)]; + } } } } diff --git a/src/render/ngage/SDL_render_ops.hpp b/src/render/ngage/SDL_render_ops.hpp index 65e92e5bca..08281e95a8 100644 --- a/src/render/ngage/SDL_render_ops.hpp +++ b/src/render/ngage/SDL_render_ops.hpp @@ -23,8 +23,10 @@ #define ngage_video_render_ops_hpp #include <3dtypes.h> +#include void ApplyColorMod(void *dest, void *source, int pitch, int width, int height, SDL_FColor color); +void BlitWithAlphaKey(CFbsBitmap *dst, int dst_x, int dst_y, const void *src, int src_width, int src_height, int src_stride_u16); void ApplyFlip(void *dest, void *source, int pitch, int width, int height, SDL_FlipMode flip); void ApplyRotation(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed angle); void ApplyScale(void *dest, void *source, int pitch, int width, int height, TFixed center_x, TFixed center_y, TFixed scale_x, TFixed scale_y);