Files
devilutionX/Source/engine/render/pcx_render.cpp
T
Gleb Mazovetskiy 02d448267a 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).
2022-06-15 16:59:59 +02:00

189 lines
6.1 KiB
C++

#include "engine/render/pcx_render.hpp"
#include <algorithm>
#include <cstring>
#include "engine/render/common_impl.h"
#include "utils/log.hpp"
namespace devilution {
namespace {
constexpr uint8_t PcxMaxSinglePixel = 0xBF;
constexpr uint8_t PcxRunLengthMask = 0x3F;
const uint8_t *SkipRestOfPcxLine(const uint8_t *src, unsigned remainingWidth)
{
while (remainingWidth > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
--remainingWidth;
} else {
remainingWidth -= value & PcxRunLengthMask;
++src;
}
}
return src;
}
template <bool UseColorMap, bool HasTransparency>
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;
--srcHeight;
}
srcHeight = static_cast<unsigned>(std::min<int>(out.h() - position.y, srcHeight));
const auto dstSkip = static_cast<unsigned>(out.pitch() - srcWidth);
const unsigned srcSkip = srcWidth % 2;
uint8_t *dst = &out[position];
for (unsigned y = 0; y < srcHeight; y++) {
for (unsigned x = 0; x < srcWidth;) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
if (!(HasTransparency && value == transparentColor)) {
*dst = UseColorMap ? colorMap[value] : value;
}
++dst;
++x;
} else {
const uint8_t runLength = value & PcxRunLengthMask;
const uint8_t color = *src++;
if (!(HasTransparency && color == transparentColor)) {
std::memset(dst, UseColorMap ? colorMap[color] : color, runLength);
}
dst += runLength;
x += runLength;
}
}
dst += dstSkip;
src += srcSkip;
}
}
template <bool UseColorMap, bool HasTransparency>
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;
--srcHeight;
}
srcHeight = static_cast<unsigned>(std::min<int>(out.h() - position.y, srcHeight));
position.x += static_cast<int>(clipX.left);
const auto dstSkip = static_cast<unsigned>(out.pitch() - clipX.width);
const unsigned srcSkip = srcWidth % 2;
uint8_t *dst = &out[position];
for (unsigned y = 0; y < srcHeight; y++) {
// Skip initial src if clipping on the left.
// Handles overshoot, i.e. when the RLE segment goes into the unclipped area.
auto remainingWidth = clipX.width;
auto remainingLeftClip = clipX.left;
while (remainingLeftClip > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
--remainingLeftClip;
} else {
const uint8_t runLength = value & PcxRunLengthMask;
if (runLength > remainingLeftClip) {
const uint8_t overshoot = runLength - remainingLeftClip;
const uint8_t originalColor = *src++;
const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor;
if (overshoot > remainingWidth) {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, remainingWidth);
}
dst += remainingWidth;
remainingWidth = 0;
} else {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, overshoot);
}
dst += overshoot;
remainingWidth -= overshoot;
}
remainingLeftClip = 0;
break;
}
++src;
remainingLeftClip -= runLength;
}
}
while (remainingWidth > 0) {
const uint8_t value = *src++;
if (value <= PcxMaxSinglePixel) {
if (!(HasTransparency && value == transparentColor)) {
*dst = UseColorMap ? colorMap[value] : value;
}
++dst;
--remainingWidth;
continue;
}
const uint8_t runLength = value & PcxRunLengthMask;
const uint8_t originalColor = *src++;
const uint8_t color = UseColorMap ? colorMap[originalColor] : originalColor;
if (runLength > remainingWidth) {
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, remainingWidth);
}
dst += remainingWidth;
remainingWidth -= runLength;
break;
}
if (!(HasTransparency && originalColor == transparentColor)) {
std::memset(dst, color, runLength);
}
dst += runLength;
remainingWidth -= runLength;
}
src = SkipRestOfPcxLine(src, clipX.right + remainingWidth);
dst += dstSkip;
src += srcSkip;
}
}
template <bool UseColorMap>
void BlitPcxSprite(const Surface &out, Point position, PcxSprite sprite, const uint8_t *colorMap)
{
const ClipX clipX = CalculateClipX(position.x, sprite.width(), out);
if (clipX.width <= 0)
return;
if (static_cast<unsigned>(clipX.width) == sprite.width()) {
if (sprite.transparentColor()) {
BlitPcxClipY<UseColorMap, /*HasTransparency=*/true>(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, *sprite.transparentColor());
} else {
BlitPcxClipY<UseColorMap, /*HasTransparency=*/false>(out, position, sprite.data(), sprite.width(), sprite.height(), colorMap, 0);
}
} else {
if (sprite.transparentColor()) {
BlitPcxClipXY<UseColorMap, /*HasTransparency=*/true>(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, *sprite.transparentColor());
} else {
BlitPcxClipXY<UseColorMap, /*HasTransparency=*/false>(out, position, sprite.data(), sprite.width(), sprite.height(), clipX, colorMap, 0);
}
}
}
} // namespace
void RenderPcxSprite(const Surface &out, PcxSprite sprite, Point position)
{
BlitPcxSprite</*UseColorMap=*/false>(out, position, sprite, /*colorMap=*/nullptr);
}
void RenderPcxSpriteWithColorMap(const Surface &out, PcxSprite sprite, Point position, const std::array<uint8_t, 256> &colorMap)
{
BlitPcxSprite</*UseColorMap=*/true>(out, position, sprite, colorMap.data());
}
} // namespace devilution