Browse Source

clx_render: Further simplify outline rendering

1. Switches to uint8_t for coordinate storage.
2. Avoids producing duplicate pixels.
pull/6929/head
Gleb Mazovetskiy 2 years ago
parent
commit
b6347fb194
  1. 109
      Source/engine/render/clx_render.cpp
  2. 7
      Source/utils/algorithm/container.hpp
  3. 51
      Source/utils/static_vector.hpp

109
Source/engine/render/clx_render.cpp

@ -6,15 +6,11 @@
#include "clx_render.hpp"
#include <algorithm>
#include <bitset>
#include <cstdint>
#include "engine/point.hpp"
#include "engine/render/blit_impl.hpp"
#include "engine/render/scrollrt.h"
#include "utils/attributes.h"
#include "utils/clx_decode.hpp"
#include "utils/static_bit_vector.hpp"
#include "utils/static_vector.hpp"
#ifdef DEBUG_CLX
@ -255,9 +251,9 @@ void DoRenderBackwards(
}
constexpr size_t MaxOutlinePixels = 1024;
constexpr size_t MaxOutlineSpriteWidth = 256;
using OutlinePixels = StaticVector<PointOf<int16_t>, MaxOutlinePixels>;
using OutlineRowSolidRuns = StaticVector<std::pair<int16_t, int16_t>, MaxOutlineSpriteWidth / 2 + 1>;
constexpr size_t MaxOutlineSpriteWidth = 253;
using OutlinePixels = StaticVector<PointOf<uint8_t>, MaxOutlinePixels>;
using OutlineRowSolidRuns = StaticVector<std::pair<uint8_t, uint8_t>, MaxOutlineSpriteWidth / 2 + 1>;
struct OutlinePixelsCacheEntry {
OutlinePixels outlinePixels;
@ -267,31 +263,41 @@ struct OutlinePixelsCacheEntry {
OutlinePixelsCacheEntry OutlinePixelsCache;
void PopulateOutlinePixelsForRow(
const StaticBitVector<MaxOutlineSpriteWidth> &rowBelow,
const StaticBitVector<MaxOutlineSpriteWidth> &rowAbove,
const OutlineRowSolidRuns &solidRuns,
int y, OutlinePixels &result)
const OutlineRowSolidRuns &runs,
const bool *DVL_RESTRICT below,
bool *DVL_RESTRICT cur,
bool *DVL_RESTRICT above,
uint8_t y,
OutlinePixels &result)
{
for (auto [xBegin, xEnd] : solidRuns) {
result.emplace_back(static_cast<int16_t>(xBegin - 1), static_cast<int16_t>(y));
for (int xi = xBegin; xi < xEnd; ++xi) {
if (!rowAbove.test(xi)) {
result.emplace_back(static_cast<int16_t>(xi), static_cast<int16_t>(y - 1));
DVL_ASSUME(!runs.empty());
for (const auto &[begin, end] : runs) {
if (!cur[static_cast<uint8_t>(begin - 1)]) {
result.emplace_back(static_cast<uint8_t>(begin - 1), y);
cur[static_cast<uint8_t>(begin - 1)] = true;
}
if (!cur[end]) {
result.emplace_back(end, y);
cur[end] = true;
}
for (uint8_t x = begin; x < end; ++x) {
if (!below[x]) {
result.emplace_back(x, static_cast<uint8_t>(y + 1));
}
if (!rowBelow.test(xi)) {
result.emplace_back(static_cast<int16_t>(xi), static_cast<int16_t>(y + 1));
if (!above[x]) {
result.emplace_back(x, static_cast<uint8_t>(y - 1));
above[x] = true;
}
}
result.emplace_back(static_cast<int16_t>(xEnd), static_cast<int16_t>(y));
}
}
void AppendOutlineRowSolidRuns(int x, int w, OutlineRowSolidRuns &solidRuns)
void AppendOutlineRowSolidRuns(uint8_t x, uint8_t w, OutlineRowSolidRuns &solidRuns)
{
if (solidRuns.empty() || solidRuns.back().second != x) {
solidRuns.emplace_back(x, x + w);
} else {
solidRuns.back().second = static_cast<int16_t>(x + w);
solidRuns.back().second = static_cast<uint8_t>(x + w);
}
}
@ -300,17 +306,14 @@ void GetOutline(ClxSprite sprite, OutlinePixels &result) // NOLINT(readability-f
{
const unsigned width = sprite.width();
assert(width < MaxOutlineSpriteWidth);
StaticBitVector<MaxOutlineSpriteWidth> rows[3] = {
StaticBitVector<MaxOutlineSpriteWidth>(width),
StaticBitVector<MaxOutlineSpriteWidth>(width),
StaticBitVector<MaxOutlineSpriteWidth>(width)
};
StaticBitVector<MaxOutlineSpriteWidth> *rowAbove = &rows[0];
StaticBitVector<MaxOutlineSpriteWidth> *row = &rows[1];
StaticBitVector<MaxOutlineSpriteWidth> *rowBelow = &rows[2];
int x = 0;
int y = sprite.height() - 1;
int x = 1;
auto y = static_cast<uint8_t>(sprite.height());
bool rows[3][MaxOutlineSpriteWidth + 2] = { {}, {}, {} };
bool *rowAbove = rows[0];
bool *row = rows[1];
bool *rowBelow = rows[2];
OutlineRowSolidRuns solidRuns[2];
OutlineRowSolidRuns *solidRunAbove = &solidRuns[0];
@ -319,7 +322,7 @@ void GetOutline(ClxSprite sprite, OutlinePixels &result) // NOLINT(readability-f
const uint8_t *src = sprite.pixelData();
const uint8_t *const end = src + sprite.pixelDataSize();
while (src < end) {
while (x < static_cast<int>(width)) {
while (x <= static_cast<int>(width)) {
const auto v = static_cast<uint8_t>(*src++);
uint8_t w;
if (IsClxOpaque(v)) {
@ -363,36 +366,43 @@ void GetOutline(ClxSprite sprite, OutlinePixels &result) // NOLINT(readability-f
}
for (const auto &[xBegin, xEnd] : *solidRunAbove) {
rowAbove->set(xBegin, xEnd - xBegin);
std::fill(rowAbove + xBegin, rowAbove + xEnd, true);
}
if (!solidRun->empty()) {
PopulateOutlinePixelsForRow(*solidRun, rowBelow, row, rowAbove, static_cast<uint8_t>(y + 1), result);
}
PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y + 1, result);
// (0, 1, 2) => (2, 0, 1)
std::swap(row, rowBelow);
std::swap(row, rowAbove);
rowAbove->reset();
std::fill_n(rowAbove, width, false);
std::swap(solidRunAbove, solidRun);
solidRunAbove->clear();
if (x > static_cast<int>(width)) {
if (x > static_cast<int>(width + 1)) {
// Transparent overrun.
const unsigned numWholeTransparentLines = x / width;
const unsigned numWholeTransparentLines = (x - 1) / width;
if (numWholeTransparentLines > 1) {
PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y, result);
if (!solidRun->empty()) {
PopulateOutlinePixelsForRow(*solidRun, rowBelow, row, rowAbove, y, result);
}
solidRun->clear();
row->reset();
std::fill_n(row, width, false);
}
if (numWholeTransparentLines > 2) rowBelow->reset();
y -= static_cast<int16_t>(numWholeTransparentLines);
x = static_cast<int16_t>(x % width);
if (numWholeTransparentLines > 2) std::fill_n(rowBelow, width, false);
y -= static_cast<uint8_t>(numWholeTransparentLines);
x = static_cast<int>((x - 1) % width) + 1;
} else {
--y;
x = 0;
x = 1;
}
}
rowAbove->reset();
PopulateOutlinePixelsForRow(*rowBelow, *rowAbove, *solidRun, y + 1, result);
std::fill_n(rowAbove, width, false);
if (!solidRun->empty()) {
PopulateOutlinePixelsForRow(*solidRun, rowBelow, row, rowAbove, static_cast<uint8_t>(y + 1), result);
}
}
template <bool SkipColorIndexZero>
@ -412,9 +422,10 @@ template <bool SkipColorIndexZero>
void RenderClxOutline(const Surface &out, Point position, ClxSprite sprite, uint8_t color)
{
UpdateOutlinePixelsCache<SkipColorIndexZero>(sprite);
position.y -= sprite.height() - 1;
if (position.x > 0 && position.x + sprite.width() < out.w()
&& position.y > 0 && position.y + sprite.height() < out.h()) {
--position.x;
position.y -= sprite.height();
if (position.x >= 0 && position.x + sprite.width() < out.w()
&& position.y >= 0 && position.y + sprite.height() < out.h()) {
for (const auto &[x, y] : OutlinePixelsCache.outlinePixels) {
*out.at(position.x + x, position.y + y) = color;
}

7
Source/utils/algorithm/container.hpp

@ -115,4 +115,11 @@ container_internal::Iterator<C> c_lower_bound(C &c, T &&value)
std::forward<T>(value));
}
template <typename C>
container_internal::Iterator<C> c_unique(C &c)
{
return std::unique(container_internal::c_begin(c),
container_internal::c_end(c));
}
} // namespace devilution

51
Source/utils/static_vector.hpp

@ -28,35 +28,18 @@ public:
}
}
[[nodiscard]] const T *begin() const
{
return &(*this)[0];
}
[[nodiscard]] const T *begin() const { return &(*this)[0]; }
[[nodiscard]] T *begin() { return &(*this)[0]; }
[[nodiscard]] const T *end() const
{
return begin() + size_;
}
[[nodiscard]] const T *end() const { return begin() + size_; }
[[nodiscard]] T *end() { return begin() + size_; }
[[nodiscard]] size_t size() const
{
return size_;
}
[[nodiscard]] size_t size() const { return size_; }
[[nodiscard]] bool empty() const
{
return size_ == 0;
}
[[nodiscard]] bool empty() const { return size_ == 0; }
[[nodiscard]] T &back()
{
return (*this)[size_ - 1];
}
[[nodiscard]] const T &back() const
{
return (*this)[size_ - 1];
}
[[nodiscard]] const T &back() const { return (*this)[size_ - 1]; }
[[nodiscard]] T &back() { return (*this)[size_ - 1]; }
template <typename... Args>
T &emplace_back(Args &&...args) // NOLINT(readability-identifier-naming)
@ -65,22 +48,20 @@ public:
return *::new (&data_[size_++]) T(std::forward<Args>(args)...);
}
T &operator[](std::size_t pos)
{
return *data_[pos].ptr();
}
const T &operator[](std::size_t pos) const { return *data_[pos].ptr(); }
T &operator[](std::size_t pos) { return *data_[pos].ptr(); }
const T &operator[](std::size_t pos) const
void erase(const T *begin, const T *end)
{
return *data_[pos].ptr();
for (const T *it = begin; it < end; ++it) {
std::destroy_at(it);
}
size_ -= end - begin;
}
void clear()
{
for (std::size_t pos = 0; pos < size_; ++pos) {
std::destroy_at(data_[pos].ptr());
}
size_ = 0;
erase(begin(), end());
}
~StaticVector()

Loading…
Cancel
Save