From 02d448267a90445cba7867ba968a665fa316b2b9 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 15 Jun 2022 15:21:38 +0100 Subject: [PATCH] Render fonts as PCX (-420 KiB RAM) Reduces peak memory consuption in game by ~420 KiB. This is because the PCX renderer applies the palette on the fly while rendering, meaning we do not need to duplicate the font for different palettes. This approach is also a bit slower than precomposed palettes (still plenty fast). --- Source/engine/render/pcx_render.cpp | 42 +++++++++++++----------- Source/engine/render/text_render.cpp | 48 ++++++++++++++++------------ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/Source/engine/render/pcx_render.cpp b/Source/engine/render/pcx_render.cpp index 9c7f0f00c..16411722a 100644 --- a/Source/engine/render/pcx_render.cpp +++ b/Source/engine/render/pcx_render.cpp @@ -28,6 +28,8 @@ const uint8_t *SkipRestOfPcxLine(const uint8_t *src, unsigned remainingWidth) template void BlitPcxClipY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, const uint8_t *colorMap, uint8_t transparentColor) { + if (position.y >= out.h()) + return; while (position.y < 0 && srcHeight != 0) { src = SkipRestOfPcxLine(src, srcWidth); ++position.y; @@ -42,17 +44,16 @@ void BlitPcxClipY(const Surface &out, Point position, const uint8_t *src, unsign for (unsigned x = 0; x < srcWidth;) { const uint8_t value = *src++; if (value <= PcxMaxSinglePixel) { - const uint8_t color = UseColorMap ? colorMap[value] : value; - if (!(HasTransparency && color == transparentColor)) { - *dst = color; + if (!(HasTransparency && value == transparentColor)) { + *dst = UseColorMap ? colorMap[value] : value; } ++dst; ++x; } else { const uint8_t runLength = value & PcxRunLengthMask; - const uint8_t color = UseColorMap ? colorMap[*src++] : *src++; + const uint8_t color = *src++; if (!(HasTransparency && color == transparentColor)) { - std::memset(dst, color, runLength); + std::memset(dst, UseColorMap ? colorMap[color] : color, runLength); } dst += runLength; x += runLength; @@ -66,6 +67,8 @@ void BlitPcxClipY(const Surface &out, Point position, const uint8_t *src, unsign template void BlitPcxClipXY(const Surface &out, Point position, const uint8_t *src, unsigned srcWidth, unsigned srcHeight, ClipX clipX, const uint8_t *colorMap, uint8_t transparentColor) { + if (position.y >= out.h() || position.x >= out.w()) + return; while (position.y < 0 && srcHeight != 0) { src = SkipRestOfPcxLine(src, srcWidth); ++position.y; @@ -91,15 +94,16 @@ void BlitPcxClipXY(const Surface &out, Point position, const uint8_t *src, unsig const uint8_t runLength = value & PcxRunLengthMask; if (runLength > remainingLeftClip) { const uint8_t overshoot = runLength - remainingLeftClip; - const uint8_t color = UseColorMap ? colorMap[*src++] : *src++; + const uint8_t originalColor = *src++; + const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor; if (overshoot > remainingWidth) { - if (!(HasTransparency && color == transparentColor)) { + if (!(HasTransparency && originalColor == transparentColor)) { std::memset(dst, color, remainingWidth); } dst += remainingWidth; remainingWidth = 0; } else { - if (!(HasTransparency && color == transparentColor)) { + if (!(HasTransparency && originalColor == transparentColor)) { std::memset(dst, color, overshoot); } dst += overshoot; @@ -107,33 +111,35 @@ void BlitPcxClipXY(const Surface &out, Point position, const uint8_t *src, unsig } remainingLeftClip = 0; break; - } else { - ++src; - remainingLeftClip -= runLength; } + ++src; + remainingLeftClip -= runLength; } } while (remainingWidth > 0) { const uint8_t value = *src++; if (value <= PcxMaxSinglePixel) { - *dst++ = UseColorMap ? colorMap[value] : value; + if (!(HasTransparency && value == transparentColor)) { + *dst = UseColorMap ? colorMap[value] : value; + } + ++dst; --remainingWidth; continue; } const uint8_t runLength = value & PcxRunLengthMask; - const uint8_t color = UseColorMap ? colorMap[*src++] : *src++; + const uint8_t originalColor = *src++; + const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor; if (runLength > remainingWidth) { - if (!(HasTransparency && color == transparentColor)) { + if (!(HasTransparency && originalColor == transparentColor)) { std::memset(dst, color, remainingWidth); } dst += remainingWidth; remainingWidth -= runLength; break; - } else { - if (!(HasTransparency && color == transparentColor)) { - std::memset(dst, color, runLength); - } + } + if (!(HasTransparency && originalColor == transparentColor)) { + std::memset(dst, color, runLength); } dst += runLength; remainingWidth -= runLength; diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index d89da7992..b7f2f1acc 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -17,9 +17,10 @@ #include "engine.h" #include "engine/load_cel.hpp" #include "engine/load_file.hpp" -#include "engine/load_pcx_as_cel.hpp" +#include "engine/load_pcx.hpp" #include "engine/point.hpp" #include "palette.h" +#include "pcx_render.hpp" #include "utils/display.h" #include "utils/language.h" #include "utils/sdl_compat.h" @@ -34,8 +35,8 @@ namespace { constexpr char32_t ZWSP = U'\u200B'; // Zero-width space -using Font = const OwnedCelSpriteWithFrameHeight; -std::unordered_map> Fonts; +using Font = const OwnedPcxSpriteSheet; +std::unordered_map> Fonts; std::unordered_map> FontKerns; std::array FontSizes = { 12, 24, 30, 42, 46, 22 }; @@ -66,6 +67,8 @@ std::array ColorTranslations = { "fonts\\buttonpushed.trn", }; +std::array>, 14> ColorTranslationsData; + GameFontTables GetSizeFromFlags(UiFlags flags) { if (HasAnyOf(flags, UiFlags::FontSize24)) @@ -166,9 +169,9 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) return kerning; } -uint32_t GetFontId(GameFontTables size, text_color color, uint16_t row) +uint32_t GetFontId(GameFontTables size, uint16_t row) { - return (color << 24) | (size << 16) | row; + return (size << 16) | row; } void GetFontPath(GameFontTables size, uint16_t row, char *out) @@ -176,10 +179,14 @@ void GetFontPath(GameFontTables size, uint16_t row, char *out) sprintf(out, "fonts\\%i-%02x.pcx", FontSizes[size], row); } -const OwnedCelSpriteWithFrameHeight *LoadFont(GameFontTables size, text_color color, uint16_t row) +const OwnedPcxSpriteSheet *LoadFont(GameFontTables size, text_color color, uint16_t row) { - const uint32_t fontId = GetFontId(size, color, row); + if (ColorTranslations[color] != nullptr && !ColorTranslationsData[color]) { + ColorTranslationsData[color].emplace(); + LoadFileInMem(ColorTranslations[color], *ColorTranslationsData[color]); + } + const uint32_t fontId = GetFontId(size, row); auto hotFont = Fonts.find(fontId); if (hotFont != Fonts.end()) { return &*hotFont->second; @@ -188,26 +195,25 @@ const OwnedCelSpriteWithFrameHeight *LoadFont(GameFontTables size, text_color co char path[32]; GetFontPath(size, row, &path[0]); - std::optional &font = Fonts[fontId]; + std::optional &font = Fonts[fontId]; constexpr unsigned NumFrames = 256; - font = LoadPcxAssetAsCel(path, NumFrames); + font = LoadPcxSpriteSheetAsset(path, NumFrames, /*transparentColor=*/1); if (!font) { LogError("Error loading font: {}", path); return nullptr; } - if (ColorTranslations[color] != nullptr) { - std::array colorMapping; - LoadFileInMem(ColorTranslations[color], colorMapping); - CelApplyTrans(font->sprite.MutableData(), colorMapping); - } - return &(*font); } -void DrawFont(const Surface &out, Point position, const OwnedCelSpriteWithFrameHeight *font, int frame) +void DrawFont(const Surface &out, Point position, const OwnedPcxSpriteSheet *font, text_color color, int frame) { - CelDrawTo(out, { position.x, static_cast(position.y + font->frameHeight) }, CelSprite { font->sprite }, frame); + PcxSprite glyph = PcxSpriteSheet { *font }.sprite(frame); + if (ColorTranslationsData[color]) { + RenderPcxSpriteWithColorMap(out, glyph, position, *ColorTranslationsData[color]); + } else { + RenderPcxSprite(out, glyph, position); + } } bool IsWhitespace(char32_t c) @@ -387,7 +393,7 @@ int DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &ch continue; } - DrawFont(out, characterPosition, font, frame); + DrawFont(out, characterPosition, font, color, frame); characterPosition.x += (*kerning)[frame] + spacing; } return text.data() - remaining.data(); @@ -646,7 +652,7 @@ uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect, if (HasAnyOf(flags, UiFlags::PentaCursor)) { CelDrawTo(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, *pSPentSpn2Cels, PentSpn2Spin()); } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { - DrawFont(out, characterPosition, LoadFont(size, color, 0), '|'); + DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|'); } return bytesDrawn; @@ -746,7 +752,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA } } - DrawFont(out, characterPosition, font, frame); + DrawFont(out, characterPosition, font, color, frame); characterPosition.x += (*kerning)[frame] + spacing; prev = next; } @@ -754,7 +760,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA if (HasAnyOf(flags, UiFlags::PentaCursor)) { CelDrawTo(out, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, *pSPentSpn2Cels, PentSpn2Spin()); } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { - DrawFont(out, characterPosition, LoadFont(size, color, 0), '|'); + DrawFont(out, characterPosition, LoadFont(size, color, 0), color, '|'); } }