mirror of
https://github.com/libretro/devilutionX.git
synced 2026-11-10 17:20:56 -10:00
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).
189 lines
6.1 KiB
C++
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
|