Browse Source
Extract palette blending into a library and adds a basic test and a basic benchmark.pull/8024/head
11 changed files with 280 additions and 115 deletions
@ -0,0 +1,116 @@ |
|||||||
|
#include "utils/palette_blending.hpp" |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <limits> |
||||||
|
|
||||||
|
#include <SDL.h> |
||||||
|
|
||||||
|
namespace devilution { |
||||||
|
|
||||||
|
// This array is read from a lot on every frame.
|
||||||
|
// We do not use `std::array` here to improve debug build performance.
|
||||||
|
// In a debug build, `std::array` accesses are function calls.
|
||||||
|
uint8_t paletteTransparencyLookup[256][256]; |
||||||
|
|
||||||
|
#if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT |
||||||
|
uint16_t paletteTransparencyLookupBlack16[65536]; |
||||||
|
#endif |
||||||
|
|
||||||
|
namespace { |
||||||
|
|
||||||
|
struct RGB { |
||||||
|
uint8_t r; |
||||||
|
uint8_t g; |
||||||
|
uint8_t b; |
||||||
|
}; |
||||||
|
|
||||||
|
uint8_t FindBestMatchForColor(SDL_Color palette[256], RGB color, int skipFrom, int skipTo) |
||||||
|
{ |
||||||
|
uint8_t best; |
||||||
|
uint32_t bestDiff = std::numeric_limits<uint32_t>::max(); |
||||||
|
for (int i = 0; i < 256; i++) { |
||||||
|
if (i >= skipFrom && i <= skipTo) |
||||||
|
continue; |
||||||
|
const int diffr = palette[i].r - color.r; |
||||||
|
const int diffg = palette[i].g - color.g; |
||||||
|
const int diffb = palette[i].b - color.b; |
||||||
|
const uint32_t diff = diffr * diffr + diffg * diffg + diffb * diffb; |
||||||
|
|
||||||
|
if (bestDiff > diff) { |
||||||
|
best = i; |
||||||
|
bestDiff = diff; |
||||||
|
} |
||||||
|
} |
||||||
|
return best; |
||||||
|
} |
||||||
|
|
||||||
|
RGB BlendColors(const SDL_Color &a, const SDL_Color &b) |
||||||
|
{ |
||||||
|
return RGB { |
||||||
|
.r = static_cast<uint8_t>((static_cast<int>(a.r) + static_cast<int>(b.r)) / 2), |
||||||
|
.g = static_cast<uint8_t>((static_cast<int>(a.g) + static_cast<int>(b.g)) / 2), |
||||||
|
.b = static_cast<uint8_t>((static_cast<int>(a.b) + static_cast<int>(b.b)) / 2), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GenerateBlendedLookupTable(SDL_Color palette[256], int skipFrom, int skipTo) |
||||||
|
{ |
||||||
|
for (unsigned i = 0; i < 256; i++) { |
||||||
|
for (unsigned j = 0; j < 256; j++) { |
||||||
|
if (i == j) { // No need to calculate transparency between 2 identical colors
|
||||||
|
paletteTransparencyLookup[i][j] = j; |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (i > j) { // Half the blends will be mirror identical ([i][j] is the same as [j][i]), so simply copy the existing combination.
|
||||||
|
paletteTransparencyLookup[i][j] = paletteTransparencyLookup[j][i]; |
||||||
|
continue; |
||||||
|
} |
||||||
|
const uint8_t best = FindBestMatchForColor(palette, BlendColors(palette[i], palette[j]), skipFrom, skipTo); |
||||||
|
paletteTransparencyLookup[i][j] = best; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT |
||||||
|
for (unsigned i = 0; i < 256; ++i) { |
||||||
|
for (unsigned j = 0; j < 256; ++j) { |
||||||
|
const uint16_t index = i | (j << 8U); |
||||||
|
paletteTransparencyLookupBlack16[index] = paletteTransparencyLookup[0][i] | (paletteTransparencyLookup[0][j] << 8); |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void UpdateBlendedLookupTableSingleColor(unsigned i, SDL_Color palette[256], int skipFrom, int skipTo) |
||||||
|
{ |
||||||
|
// Update blended transparency, but only for the color that was updated
|
||||||
|
for (unsigned j = 0; j < 256; j++) { |
||||||
|
if (i == j) { // No need to calculate transparency between 2 identical colors
|
||||||
|
paletteTransparencyLookup[i][j] = j; |
||||||
|
continue; |
||||||
|
} |
||||||
|
const uint8_t best = FindBestMatchForColor(palette, BlendColors(palette[i], palette[j]), skipFrom, skipTo); |
||||||
|
paletteTransparencyLookup[i][j] = paletteTransparencyLookup[j][i] = best; |
||||||
|
} |
||||||
|
|
||||||
|
#if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT |
||||||
|
UpdateTransparencyLookupBlack16(i, i); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
#if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT |
||||||
|
void UpdateTransparencyLookupBlack16(unsigned from, unsigned to) |
||||||
|
{ |
||||||
|
for (unsigned i = from; i <= to; i++) { |
||||||
|
for (unsigned j = 0; j < 256; j++) { |
||||||
|
const uint16_t index = i | (j << 8U); |
||||||
|
const uint16_t reverseIndex = j | (i << 8U); |
||||||
|
paletteTransparencyLookupBlack16[index] = paletteTransparencyLookup[0][i] | (paletteTransparencyLookup[0][j] << 8); |
||||||
|
paletteTransparencyLookupBlack16[reverseIndex] = paletteTransparencyLookup[0][j] | (paletteTransparencyLookup[0][i] << 8); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
} // namespace devilution
|
||||||
@ -0,0 +1,43 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
#include <SDL.h> |
||||||
|
|
||||||
|
namespace devilution { |
||||||
|
|
||||||
|
/** Lookup table for transparency */ |
||||||
|
extern uint8_t paletteTransparencyLookup[256][256]; |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate lookup table for transparency |
||||||
|
* |
||||||
|
* This is based of the same technique found in Quake2. |
||||||
|
* |
||||||
|
* To mimic 50% transparency we figure out what colors in the existing palette are the best match for the combination of any 2 colors. |
||||||
|
* We save this into a lookup table for use during rendering. |
||||||
|
* |
||||||
|
* @param palette The colors to operate on |
||||||
|
* @param skipFrom Do not use colors between this index and skipTo |
||||||
|
* @param skipTo Do not use colors between skipFrom and this index |
||||||
|
*/ |
||||||
|
void GenerateBlendedLookupTable(SDL_Color palette[256], int skipFrom, int skipTo); |
||||||
|
|
||||||
|
void UpdateBlendedLookupTableSingleColor(unsigned i, SDL_Color palette[256], int skipFrom, int skipTo); |
||||||
|
|
||||||
|
#if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT |
||||||
|
/**
|
||||||
|
* A lookup table from black for a pair of colors. |
||||||
|
* |
||||||
|
* For a pair of colors i and j, the index `i | (j << 8)` contains |
||||||
|
* `paletteTransparencyLookup[0][i] | (paletteTransparencyLookup[0][j] << 8)`. |
||||||
|
* |
||||||
|
* On big-endian platforms, the indices are encoded as `j | (i << 8)`, while the |
||||||
|
* value order remains the same. |
||||||
|
*/ |
||||||
|
extern uint16_t paletteTransparencyLookupBlack16[65536]; |
||||||
|
|
||||||
|
void UpdateTransparencyLookupBlack16(unsigned from, unsigned to); |
||||||
|
#endif |
||||||
|
|
||||||
|
} // namespace devilution
|
||||||
@ -0,0 +1,37 @@ |
|||||||
|
#include "utils/palette_blending.hpp" |
||||||
|
|
||||||
|
#include <SDL.h> |
||||||
|
#include <benchmark/benchmark.h> |
||||||
|
|
||||||
|
namespace devilution { |
||||||
|
namespace { |
||||||
|
|
||||||
|
void GeneratePalette(SDL_Color palette[256]) |
||||||
|
{ |
||||||
|
for (unsigned j = 0; j < 4; ++j) { |
||||||
|
for (unsigned i = 0; i < 64; ++i) { |
||||||
|
palette[j * 64 + i].r = i * std::max(j, 1U); |
||||||
|
palette[j * 64 + i].g = i * j; |
||||||
|
palette[j * 64 + i].b = i * 2; |
||||||
|
#ifndef USE_SDL1 |
||||||
|
palette[j * 64 + i].a = SDL_ALPHA_OPAQUE; |
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void BM_GenerateBlendedLookupTable(benchmark::State &state) |
||||||
|
{ |
||||||
|
SDL_Color palette[256]; |
||||||
|
GeneratePalette(palette); |
||||||
|
for (auto _ : state) { |
||||||
|
GenerateBlendedLookupTable(palette, /*skipFrom=*/-1, /*skipTo=*/-1); |
||||||
|
int result = paletteTransparencyLookup[17][98]; |
||||||
|
benchmark::DoNotOptimize(result); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BENCHMARK(BM_GenerateBlendedLookupTable); |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace devilution
|
||||||
@ -0,0 +1,64 @@ |
|||||||
|
#include "utils/palette_blending.hpp" |
||||||
|
|
||||||
|
#include <algorithm> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
#include <SDL.h> |
||||||
|
#include <gmock/gmock.h> |
||||||
|
#include <gtest/gtest.h> |
||||||
|
|
||||||
|
#include "utils/str_cat.hpp" |
||||||
|
|
||||||
|
void PrintTo(const SDL_Color &color, std::ostream *os) |
||||||
|
{ |
||||||
|
*os << "(" |
||||||
|
<< static_cast<int>(color.r) << ", " |
||||||
|
<< static_cast<int>(color.g) << ", " |
||||||
|
<< static_cast<int>(color.b) << ")"; |
||||||
|
} |
||||||
|
|
||||||
|
namespace devilution { |
||||||
|
namespace { |
||||||
|
|
||||||
|
MATCHER_P3(ColorIs, r, g, b, |
||||||
|
StrCat(negation ? "isn't" : "is", " (", r, ", ", g, ", ", b, ")")) |
||||||
|
{ |
||||||
|
return arg.r == r && arg.g == g && arg.b == b; |
||||||
|
} |
||||||
|
|
||||||
|
void GeneratePalette(SDL_Color palette[256]) |
||||||
|
{ |
||||||
|
for (unsigned j = 0; j < 4; ++j) { |
||||||
|
for (unsigned i = 0; i < 64; ++i) { |
||||||
|
palette[j * 64 + i].r = i * std::max(j, 1U); |
||||||
|
palette[j * 64 + i].g = i * j; |
||||||
|
palette[j * 64 + i].b = i * 2; |
||||||
|
#ifndef USE_SDL1 |
||||||
|
palette[j * 64 + i].a = SDL_ALPHA_OPAQUE; |
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(GenerateBlendedLookupTableTest, BasicTest) |
||||||
|
{ |
||||||
|
SDL_Color palette[256]; |
||||||
|
GeneratePalette(palette); |
||||||
|
|
||||||
|
GenerateBlendedLookupTable(palette, /*skipFrom=*/-1, /*skipTo=*/-1); |
||||||
|
|
||||||
|
EXPECT_THAT(palette[17], ColorIs(17, 0, 34)); |
||||||
|
EXPECT_THAT(palette[150], ColorIs(44, 44, 44)); |
||||||
|
EXPECT_THAT(palette[86], ColorIs(22, 22, 44)); |
||||||
|
EXPECT_EQ(paletteTransparencyLookup[17][150], 86); |
||||||
|
EXPECT_EQ(paletteTransparencyLookup[150][17], 86); |
||||||
|
|
||||||
|
EXPECT_THAT(palette[27], ColorIs(27, 0, 54)); |
||||||
|
EXPECT_THAT(palette[130], ColorIs(4, 4, 4)); |
||||||
|
EXPECT_THAT(palette[15], ColorIs(15, 0, 30)); |
||||||
|
EXPECT_EQ(paletteTransparencyLookup[27][130], 15); |
||||||
|
EXPECT_EQ(paletteTransparencyLookup[130][27], 15); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace devilution
|
||||||
Loading…
Reference in new issue