GS/HW: Decompress replacement textures to get alpha min/max

This commit is contained in:
Stenzek 2023-07-11 21:06:27 +10:00 committed by refractionpcsx2
parent a371c5b7d0
commit 8524fbbaf9
6 changed files with 1420 additions and 1 deletions

View File

@ -30,6 +30,7 @@ target_sources(common PRIVATE
Semaphore.cpp
SettingsWrapper.cpp
StringUtil.cpp
TextureDecompress.cpp
Timer.cpp
ThreadPool.cpp
WAVWriter.cpp
@ -94,6 +95,7 @@ target_sources(common PRIVATE
SettingsWrapper.h
StringUtil.h
Timer.h
TextureDecompress.h
Threading.h
ThreadPool.h
TraceLog.h

1147
common/TextureDecompress.cpp Normal file

File diff suppressed because it is too large Load Diff

199
common/TextureDecompress.h Normal file
View File

@ -0,0 +1,199 @@
// See TextureDecompress.cpp for license info.
#pragma once
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
#endif
#include <stdlib.h>
#include <stdint.h>
#include <algorithm>
#include <math.h>
#include <assert.h>
enum BC4Mode
{
BC4_UNORM = 0,
BC4_SNORM = 1
};
enum BC5Mode
{
BC5_UNORM = 0,
BC5_SNORM = 1
};
void DecompressBlockBC1(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC2(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC3(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC4(uint32_t x, uint32_t y, uint32_t stride,
enum BC4Mode mode, const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC5(uint32_t x, uint32_t y, uint32_t stride,
enum BC5Mode mode, const uint8_t* blockStorage, unsigned char* image);
namespace bc7decomp
{
enum eNoClamp { cNoClamp };
template <typename S> inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); }
class color_rgba
{
public:
union
{
uint8_t m_comps[4];
struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
};
inline color_rgba()
{
static_assert(sizeof(*this) == 4, "sizeof(*this) != 4");
}
inline color_rgba(int y)
{
set(y);
}
inline color_rgba(int y, int na)
{
set(y, na);
}
inline color_rgba(int sr, int sg, int sb, int sa)
{
set(sr, sg, sb, sa);
}
inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa)
{
set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa);
}
inline color_rgba& set_noclamp_y(int y)
{
m_comps[0] = (uint8_t)y;
m_comps[1] = (uint8_t)y;
m_comps[2] = (uint8_t)y;
m_comps[3] = (uint8_t)255;
return *this;
}
inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa)
{
m_comps[0] = (uint8_t)sr;
m_comps[1] = (uint8_t)sg;
m_comps[2] = (uint8_t)sb;
m_comps[3] = (uint8_t)sa;
return *this;
}
inline color_rgba &set(int y)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = 255;
return *this;
}
inline color_rgba &set(int y, int na)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = static_cast<uint8_t>(clamp<int>(na, 0, 255));
return *this;
}
inline color_rgba &set(int sr, int sg, int sb, int sa)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
m_comps[3] = static_cast<uint8_t>(clamp<int>(sa, 0, 255));
return *this;
}
inline color_rgba &set_rgb(int sr, int sg, int sb)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
return *this;
}
inline color_rgba &set_rgb(const color_rgba &other)
{
r = other.r;
g = other.g;
b = other.b;
return *this;
}
inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; }
inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; }
inline void clear()
{
m_comps[0] = 0;
m_comps[1] = 0;
m_comps[2] = 0;
m_comps[3] = 0;
}
inline bool operator== (const color_rgba &rhs) const
{
if (m_comps[0] != rhs.m_comps[0]) return false;
if (m_comps[1] != rhs.m_comps[1]) return false;
if (m_comps[2] != rhs.m_comps[2]) return false;
if (m_comps[3] != rhs.m_comps[3]) return false;
return true;
}
inline bool operator!= (const color_rgba &rhs) const
{
return !(*this == rhs);
}
inline bool operator<(const color_rgba &rhs) const
{
for (int i = 0; i < 4; i++)
{
if (m_comps[i] < rhs.m_comps[i])
return true;
else if (m_comps[i] != rhs.m_comps[i])
return false;
}
return false;
}
inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; }
inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; }
inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); }
static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); }
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
};
bool unpack_bc7(const void *pBlock, color_rgba *pPixels);
} // namespace bc7decomp
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -69,6 +69,7 @@
<ClCompile Include="StackWalker.cpp" />
<ClCompile Include="StringUtil.cpp" />
<ClCompile Include="SettingsWrapper.cpp" />
<ClCompile Include="TextureDecompress.cpp" />
<ClCompile Include="Timer.cpp" />
<ClCompile Include="WAVWriter.cpp" />
<ClCompile Include="WindowInfo.cpp" />
@ -141,6 +142,7 @@
<ClInclude Include="RedtapeWindows.h" />
<ClInclude Include="SafeArray.h" />
<ClInclude Include="ThreadPool.h" />
<ClInclude Include="TextureDecompress.h" />
<ClInclude Include="Timer.h" />
<ClInclude Include="WAVWriter.h" />
<ClInclude Include="WindowInfo.h" />

View File

@ -142,6 +142,9 @@
<ClCompile Include="Assertions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TextureDecompress.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AlignedMalloc.h">
@ -354,6 +357,9 @@
<ClInclude Include="Error.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TextureDecompress.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">

View File

@ -21,6 +21,7 @@
#include "common/Path.h"
#include "common/StringUtil.h"
#include "common/ScopedGuard.h"
#include "common/TextureDecompress.h"
#include "Config.h"
#include "Host.h"
@ -116,6 +117,7 @@ namespace GSTextureReplacements
static std::optional<TextureName> ParseReplacementName(const std::string& filename);
static std::string GetGameTextureDirectory();
static std::string GetDumpFilename(const TextureName& name, u32 level);
static void SetReplacementTextureAlphaMinMax(ReplacementTexture& rtex);
static std::optional<ReplacementTexture> LoadReplacementTexture(const TextureName& name, const std::string& filename, bool only_base_image);
static void QueueAsyncReplacementTextureLoad(const TextureName& name, const std::string& filename, bool mipmap, bool cache_only);
static void PrecacheReplacementTextures();
@ -483,6 +485,64 @@ GSTexture* GSTextureReplacements::LookupReplacementTexture(const GSTextureCache:
}
}
void GSTextureReplacements::SetReplacementTextureAlphaMinMax(ReplacementTexture& rtex)
{
if (rtex.format >= GSTexture::Format::BC1 && rtex.format <= GSTexture::Format::BC7)
{
constexpr u32 BC_BLOCK_SIZE = 4;
constexpr u32 BC_BLOCK_BYTES = 16;
const u32 blocks_wide = (rtex.width + (BC_BLOCK_SIZE - 1)) / BC_BLOCK_SIZE;
const u32 blocks_high = (rtex.height + (BC_BLOCK_SIZE - 1)) / BC_BLOCK_SIZE;
GSVector4i minc = GSVector4i::xffffffff();
GSVector4i maxc = GSVector4i::zero();
for (u32 y = 0; y < blocks_high; y++)
{
const u8* block_in = rtex.data.data() + y * rtex.pitch;
alignas(16) u8 block_pixels_out[BC_BLOCK_SIZE * BC_BLOCK_SIZE * sizeof(u32)];
for (u32 x = 0; x < blocks_wide; x++, block_in += BC_BLOCK_BYTES)
{
switch (rtex.format)
{
case GSTexture::Format::BC1:
DecompressBlockBC1(0, 0, sizeof(u32) * BC_BLOCK_SIZE, block_in, block_pixels_out);
break;
case GSTexture::Format::BC2:
DecompressBlockBC2(0, 0, sizeof(u32) * BC_BLOCK_SIZE, block_in, block_pixels_out);
break;
case GSTexture::Format::BC3:
DecompressBlockBC3(0, 0, sizeof(u32) * BC_BLOCK_SIZE, block_in, block_pixels_out);
break;
case GSTexture::Format::BC7:
bc7decomp::unpack_bc7(block_in, reinterpret_cast<bc7decomp::color_rgba*>(block_pixels_out));
break;
}
const u8* out_ptr = block_pixels_out;
for (u32 i = 0; i < ((BC_BLOCK_SIZE * BC_BLOCK_SIZE * sizeof(u32)) / sizeof(GSVector4i)); i++)
{
const GSVector4i v = GSVector4i::load<true>(out_ptr);
out_ptr += sizeof(GSVector4i);
minc = minc.min_u32(v);
maxc = maxc.max_u32(v);
}
}
}
rtex.alpha_minmax =
std::make_pair<u8, u8>(static_cast<u8>(minc.minv_u32() >> 24), static_cast<u8>(maxc.maxv_u32() >> 24));
}
else
{
pxAssert(rtex.format == GSTexture::Format::Color);
rtex.alpha_minmax = GSGetRGBA8AlphaMinMax(rtex.data.data(), rtex.width, rtex.height, rtex.pitch);
}
}
std::optional<GSTextureReplacements::ReplacementTexture> GSTextureReplacements::LoadReplacementTexture(const TextureName& name, const std::string& filename, bool only_base_image)
{
ReplacementTextureLoader loader = GetLoader(filename);
@ -491,9 +551,12 @@ std::optional<GSTextureReplacements::ReplacementTexture> GSTextureReplacements::
ReplacementTexture rtex;
if (!loader(filename.c_str(), &rtex, only_base_image))
{
Console.Warning("Failed to load replacement texture %s", filename.c_str());
return std::nullopt;
}
rtex.alpha_minmax = GSGetRGBA8AlphaMinMax(rtex.data.data(), rtex.width, rtex.height, rtex.pitch);
SetReplacementTextureAlphaMinMax(rtex);
return rtex;
}