Skip to content

Commit 79395b2

Browse files
committed
Palette kd-tree: Store color values in leaves
Storing color values directly in leaves improves lookup speed, at the cost of a bit more space and a slightly slower `BuildTree`. Tree size: 360 bytes -> 1119 bytes. ``` Benchmark Time CPU Time Old Time New CPU Old CPU New ----------------------------------------------------------------------------------------------------------------------------------- BM_GenerateBlendedLookupTable_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10 BM_GenerateBlendedLookupTable_mean +0.0116 +0.0117 2245830 2271969 2245300 2271555 BM_GenerateBlendedLookupTable_median +0.0115 +0.0116 2245978 2271854 2245441 2271588 BM_GenerateBlendedLookupTable_stddev -0.3436 -0.6455 659 433 505 179 BM_GenerateBlendedLookupTable_cv -0.3512 -0.6496 0 0 0 0 BM_BuildTree_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10 BM_BuildTree_mean +0.0636 +0.0649 6382 6788 6372 6786 BM_BuildTree_median +0.0649 +0.0652 6375 6788 6371 6787 BM_BuildTree_stddev -0.8562 +0.4121 23 3 2 3 BM_BuildTree_cv -0.8648 +0.3260 0 0 0 0 BM_FindNearestNeighbor_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10 BM_FindNearestNeighbor_mean -0.1665 -0.1662 2555103647 2129742669 2553963618 2129377560 BM_FindNearestNeighbor_median -0.1663 -0.1663 2554516344 2129730092 2553962695 2129360650 BM_FindNearestNeighbor_stddev -0.7871 +0.6518 1986207 422943 254256 419979 BM_FindNearestNeighbor_cv -0.7445 +0.9812 0 0 0 0 OVERALL_GEOMEAN -0.0356 -0.0351 0 0 0 0 ```
1 parent 89aa7f5 commit 79395b2

2 files changed

Lines changed: 58 additions & 41 deletions

File tree

Source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ add_devilutionx_object_library(libdevilutionx_palette_blending
409409
)
410410
target_link_dependencies(libdevilutionx_palette_blending PUBLIC
411411
DevilutionX::SDL
412+
fmt::fmt
412413
libdevilutionx_strings
413414
)
414415

Source/utils/palette_kd_tree.hpp

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <utility>
1010

1111
#include <SDL.h>
12+
#include <fmt/format.h>
1213

1314
#include "utils/static_vector.hpp"
1415
#include "utils/str_cat.hpp"
@@ -21,11 +22,11 @@
2122

2223
namespace devilution {
2324

24-
[[nodiscard]] inline uint32_t GetColorDistance(const SDL_Color &a, const std::array<uint8_t, 3> &b)
25+
[[nodiscard]] inline uint32_t GetColorDistance(const std::array<uint8_t, 3> &a, const std::array<uint8_t, 3> &b)
2526
{
26-
const int diffr = a.r - b[0];
27-
const int diffg = a.g - b[1];
28-
const int diffb = a.b - b[2];
27+
const int diffr = a[0] - b[0];
28+
const int diffg = a[1] - b[1];
29+
const int diffb = a[2] - b[2];
2930
return (diffr * diffr) + (diffg * diffg) + (diffb * diffb);
3031
}
3132

@@ -58,6 +59,8 @@ constexpr size_t PaletteKdTreeDepth = 5;
5859
*/
5960
template <size_t RemainingDepth>
6061
struct PaletteKdTreeNode {
62+
using RGB = std::array<uint8_t, 3>;
63+
6164
static constexpr unsigned Coord = (PaletteKdTreeDepth - RemainingDepth) % 3;
6265

6366
PaletteKdTreeNode<RemainingDepth - 1> left;
@@ -97,7 +100,7 @@ struct PaletteKdTreeNode {
97100
}
98101
}
99102

100-
[[maybe_unused]] void toGraphvizDot(size_t id, std::span<const uint8_t, 256> values, std::string &dot) const
103+
[[maybe_unused]] void toGraphvizDot(size_t id, std::span<const std::pair<RGB, uint8_t>, 256> values, std::string &dot) const
101104
{
102105
StrAppend(dot, " node_", id, " [label=\"");
103106
if (Coord == 0) {
@@ -123,27 +126,36 @@ struct PaletteKdTreeNode {
123126
*/
124127
template <>
125128
struct PaletteKdTreeNode</*RemainingDepth=*/0> {
129+
using RGB = std::array<uint8_t, 3>;
130+
126131
// We use inclusive indices to allow for representing the full [0, 255] range.
127132
// An empty node is represented as [1, 0].
128133
uint8_t valuesBegin;
129134
uint8_t valuesEndInclusive;
130135

131-
[[maybe_unused]] void toGraphvizDot(size_t id, std::span<const uint8_t, 256> values, std::string &dot) const
136+
[[maybe_unused]] void toGraphvizDot(size_t id, std::span<const std::pair<RGB, uint8_t>, 256> values, std::string &dot) const
132137
{
133-
StrAppend(dot, " node_", id, " [shape=box label=\"");
134-
const uint8_t *it = values.data() + valuesBegin;
135-
const uint8_t *const end = values.data() + valuesEndInclusive;
136-
while (it <= end) {
137-
StrAppend(dot, static_cast<int>(*it), ", ");
138-
++it;
139-
}
140-
if (valuesBegin <= valuesEndInclusive) {
141-
dot[dot.size() - 2] = '\"';
142-
dot[dot.size() - 1] = ']';
143-
dot += "\n";
144-
} else {
145-
StrAppend(dot, "\"]\n");
138+
StrAppend(dot, " node_", id, R"( [shape=plain label=<
139+
<table border="0" cellborder="0" cellspacing="0" cellpadding="2" style="ROUNDED">
140+
<tr>)");
141+
const std::pair<RGB, uint8_t> *const end = values.data() + valuesEndInclusive;
142+
for (const std::pair<RGB, uint8_t> *it = values.data() + valuesBegin; it <= end; ++it) {
143+
const auto &[rgb, paletteIndex] = *it;
144+
char hexColor[6];
145+
fmt::format_to(hexColor, "{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]);
146+
StrAppend(dot, R"(<td balign="left" bgcolor="#)", std::string_view(hexColor, 6), "\">");
147+
const bool useWhiteText = rgb[0] + rgb[1] + rgb[2] < 350;
148+
if (useWhiteText) StrAppend(dot, R"(<font color="white">)");
149+
StrAppend(dot,
150+
static_cast<int>(rgb[0]), " ",
151+
static_cast<int>(rgb[1]), " ",
152+
static_cast<int>(rgb[2]), R"(<br/>)",
153+
static_cast<int>(paletteIndex));
154+
if (useWhiteText) StrAppend(dot, "</font>");
155+
StrAppend(dot, "</td>");
146156
}
157+
if (valuesBegin > valuesEndInclusive) StrAppend(dot, "<td></td>");
158+
StrAppend(dot, "</tr>\n </table>>]\n");
147159
}
148160
};
149161

@@ -167,9 +179,8 @@ class PaletteKdTree {
167179
* Colors between skipFrom and skipTo (inclusive) are skipped.
168180
*/
169181
explicit PaletteKdTree(const SDL_Color palette[256], int skipFrom, int skipTo)
170-
: palette_(palette)
171182
{
172-
populatePivots(skipFrom, skipTo);
183+
populatePivots(palette, skipFrom, skipTo);
173184
StaticVector<uint8_t, 256> leafValues[NumLeaves];
174185
for (int i = 0; i < 256; ++i) {
175186
if (i >= skipFrom && i <= skipTo) continue;
@@ -186,7 +197,11 @@ class PaletteKdTree {
186197
} else {
187198
leaf.valuesBegin = static_cast<uint8_t>(totalLen);
188199
leaf.valuesEndInclusive = static_cast<uint8_t>(totalLen - 1 + values.size());
189-
std::copy(values.begin(), values.end(), values_.data() + totalLen);
200+
201+
for (size_t i = 0; i < values.size(); ++i) {
202+
const uint8_t value = values[i];
203+
values_[totalLen + i] = std::make_pair(RGB { palette[value].r, palette[value].g, palette[value].b }, value);
204+
}
190205
totalLen += values.size();
191206
}
192207
}
@@ -206,7 +221,7 @@ class PaletteKdTree {
206221
uint8_t best;
207222
uint32_t bestDiff = std::numeric_limits<uint32_t>::max();
208223
findNearestNeighborVisit(tree_, rgb, bestDiff, best);
209-
return best;
224+
return values_[best].second;
210225
}
211226

212227
[[maybe_unused]] [[nodiscard]] std::string toGraphvizDot() const
@@ -242,16 +257,18 @@ class PaletteKdTree {
242257
}
243258

244259
template <size_t RemainingDepth, size_t N>
245-
void maybeAddToSubdivisionForMedian(
246-
const PaletteKdTreeNode<RemainingDepth> &node, unsigned paletteIndex,
260+
static void maybeAddToSubdivisionForMedian(
261+
const PaletteKdTreeNode<RemainingDepth> &node,
262+
const SDL_Color palette[256], unsigned paletteIndex,
247263
std::span<StaticVector<uint8_t, 256>, N> out)
248264
{
249-
const uint8_t color = node.getColorCoordinate(palette_[paletteIndex]);
265+
const uint8_t color = node.getColorCoordinate(palette[paletteIndex]);
250266
if constexpr (N == 1) {
251267
out[0].emplace_back(color);
252268
} else {
253269
const bool isLeft = color < node.pivot;
254270
maybeAddToSubdivisionForMedian(node.child(isLeft),
271+
palette,
255272
paletteIndex,
256273
isLeft
257274
? out.template subspan<0, N / 2>()
@@ -260,7 +277,7 @@ class PaletteKdTree {
260277
}
261278

262279
template <size_t RemainingDepth, size_t N>
263-
void setPivotsRecursively(
280+
static void setPivotsRecursively(
264281
PaletteKdTreeNode<RemainingDepth> &node,
265282
std::span<StaticVector<uint8_t, 256>, N> values)
266283
{
@@ -273,27 +290,27 @@ class PaletteKdTree {
273290
}
274291

275292
template <size_t TargetDepth>
276-
void populatePivotsForTargetDepth(int skipFrom, int skipTo)
293+
void populatePivotsForTargetDepth(const SDL_Color palette[256], int skipFrom, int skipTo)
277294
{
278295
constexpr size_t NumSubdivisions = 1U << TargetDepth;
279296
std::array<StaticVector<uint8_t, 256>, NumSubdivisions> subdivisions;
280297
const std::span<StaticVector<uint8_t, 256>, NumSubdivisions> subdivisionsSpan { subdivisions };
281298
for (int i = 0; i < 256; ++i) {
282299
if (i >= skipFrom && i <= skipTo) continue;
283-
maybeAddToSubdivisionForMedian(tree_, i, subdivisionsSpan);
300+
maybeAddToSubdivisionForMedian(tree_, palette, i, subdivisionsSpan);
284301
}
285302
setPivotsRecursively(tree_, subdivisionsSpan);
286303
}
287304

288305
template <size_t... TargetDepths>
289-
void populatePivotsImpl(int skipFrom, int skipTo, std::index_sequence<TargetDepths...> intSeq) // NOLINT(misc-unused-parameters)
306+
void populatePivotsImpl(const SDL_Color palette[256], int skipFrom, int skipTo, std::index_sequence<TargetDepths...> intSeq) // NOLINT(misc-unused-parameters)
290307
{
291-
(populatePivotsForTargetDepth<TargetDepths>(skipFrom, skipTo), ...);
308+
(populatePivotsForTargetDepth<TargetDepths>(palette, skipFrom, skipTo), ...);
292309
}
293310

294-
void populatePivots(int skipFrom, int skipTo)
311+
void populatePivots(const SDL_Color palette[256], int skipFrom, int skipTo)
295312
{
296-
populatePivotsImpl(skipFrom, skipTo, std::make_index_sequence<PaletteKdTreeDepth> {});
313+
populatePivotsImpl(palette, skipFrom, skipTo, std::make_index_sequence<PaletteKdTreeDepth> {});
297314
}
298315

299316
template <size_t RemainingDepth>
@@ -307,29 +324,28 @@ class PaletteKdTree {
307324
// to the current best candidate vs the distance to the edge of the half-space represented
308325
// by the node.
309326
if (bestDiff == std::numeric_limits<uint32_t>::max()
310-
|| GetColorDistanceToPlane(node.pivot, coord) < GetColorDistance(palette_[best], rgb)) {
327+
|| GetColorDistanceToPlane(node.pivot, coord) < GetColorDistance(values_[best].first, rgb)) {
311328
findNearestNeighborVisit(node.child(coord >= node.pivot), rgb, bestDiff, best);
312329
}
313330
}
314331

315332
void findNearestNeighborVisit(const PaletteKdTreeNode<0> &node, const RGB &rgb,
316333
uint32_t &bestDiff, uint8_t &best) const
317334
{
318-
const uint8_t *it = values_.data() + node.valuesBegin;
319-
const uint8_t *const end = values_.data() + node.valuesEndInclusive;
335+
const std::pair<RGB, uint8_t> *it = values_.data() + node.valuesBegin;
336+
const std::pair<RGB, uint8_t> *const end = values_.data() + node.valuesEndInclusive;
320337
while (it <= end) {
321-
const uint8_t paletteIndex = *it++;
322-
const uint32_t diff = GetColorDistance(palette_[paletteIndex], rgb);
338+
const auto &[paletteColor, paletteIndex] = *it++;
339+
const uint32_t diff = GetColorDistance(paletteColor, rgb);
323340
if (diff < bestDiff) {
324-
best = paletteIndex;
341+
best = static_cast<uint8_t>(it - values_.data() - 1);
325342
bestDiff = diff;
326343
}
327344
}
328345
}
329346

330-
const SDL_Color *palette_;
331347
PaletteKdTreeNode<PaletteKdTreeDepth> tree_;
332-
std::array<uint8_t, 256> values_;
348+
std::array<std::pair<RGB, uint8_t>, 256> values_;
333349
};
334350

335351
} // namespace devilution

0 commit comments

Comments
 (0)