mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-10 14:07:25 +00:00
[video_core] Initial implementation on HDR endpoints for ASTC decoding
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill
|
||||
@@ -1269,6 +1269,220 @@ static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount,
|
||||
return SelectPartition(seed, x, y, 0, partitionCount, smallBlock);
|
||||
}
|
||||
|
||||
static constexpr bool IsHDRColorEndpointMode(u32 cem) {
|
||||
switch (cem) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 7:
|
||||
case 11:
|
||||
case 14:
|
||||
case 15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sign-extends the low nbits of value (a 2's complement field packed into
|
||||
// the bottom of an otherwise-unsigned integer), per C.2.15's HDR endpoint
|
||||
// bitfield unpacking.
|
||||
static constexpr s32 SignExtend(s32 value, u32 nbits) {
|
||||
const s32 sign_bit = 1 << (nbits - 1);
|
||||
return (value ^ sign_bit) - sign_bit;
|
||||
}
|
||||
|
||||
struct HDREndpointRGB {
|
||||
s32 r0, g0, b0;
|
||||
s32 r1, g1, b1;
|
||||
};
|
||||
|
||||
// HDR Endpoint Mode 7 (C.2.15): base RGB + scale factor.
|
||||
static void DecodeHDREndpointMode7(u32 v0, u32 v1, u32 v2, u32 v3, s32& r0, s32& g0, s32& b0,
|
||||
s32& r1, s32& g1, s32& b1) {
|
||||
const u32 modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4);
|
||||
|
||||
u32 majcomp;
|
||||
u32 mode;
|
||||
if ((modeval & 0xC) != 0xC) {
|
||||
majcomp = modeval >> 2;
|
||||
mode = modeval & 3;
|
||||
} else if (modeval != 0xF) {
|
||||
majcomp = modeval & 3;
|
||||
mode = 4;
|
||||
} else {
|
||||
majcomp = 0;
|
||||
mode = 5;
|
||||
}
|
||||
|
||||
s32 red = static_cast<s32>(v0 & 0x3f);
|
||||
s32 green = static_cast<s32>(v1 & 0x1f);
|
||||
s32 blue = static_cast<s32>(v2 & 0x1f);
|
||||
s32 scale = static_cast<s32>(v3 & 0x1f);
|
||||
|
||||
const u32 x0 = (v1 >> 6) & 1;
|
||||
const u32 x1 = (v1 >> 5) & 1;
|
||||
const u32 x2 = (v2 >> 6) & 1;
|
||||
const u32 x3 = (v2 >> 5) & 1;
|
||||
const u32 x4 = (v3 >> 7) & 1;
|
||||
const u32 x5 = (v3 >> 6) & 1;
|
||||
const u32 x6 = (v3 >> 5) & 1;
|
||||
|
||||
const u32 ohm = 1u << mode;
|
||||
if (ohm & 0x30)
|
||||
green |= static_cast<s32>(x0 << 6);
|
||||
if (ohm & 0x3A)
|
||||
green |= static_cast<s32>(x1 << 5);
|
||||
if (ohm & 0x30)
|
||||
blue |= static_cast<s32>(x2 << 6);
|
||||
if (ohm & 0x3A)
|
||||
blue |= static_cast<s32>(x3 << 5);
|
||||
if (ohm & 0x3D)
|
||||
scale |= static_cast<s32>(x6 << 5);
|
||||
if (ohm & 0x2D)
|
||||
scale |= static_cast<s32>(x5 << 6);
|
||||
if (ohm & 0x04)
|
||||
scale |= static_cast<s32>(x4 << 7);
|
||||
if (ohm & 0x3B)
|
||||
red |= static_cast<s32>(x4 << 6);
|
||||
if (ohm & 0x04)
|
||||
red |= static_cast<s32>(x3 << 6);
|
||||
if (ohm & 0x10)
|
||||
red |= static_cast<s32>(x5 << 7);
|
||||
if (ohm & 0x0F)
|
||||
red |= static_cast<s32>(x2 << 7);
|
||||
if (ohm & 0x05)
|
||||
red |= static_cast<s32>(x1 << 8);
|
||||
if (ohm & 0x0A)
|
||||
red |= static_cast<s32>(x0 << 8);
|
||||
if (ohm & 0x05)
|
||||
red |= static_cast<s32>(x0 << 9);
|
||||
if (ohm & 0x02)
|
||||
red |= static_cast<s32>(x6 << 9);
|
||||
if (ohm & 0x01)
|
||||
red |= static_cast<s32>(x3 << 10);
|
||||
if (ohm & 0x02)
|
||||
red |= static_cast<s32>(x5 << 10);
|
||||
|
||||
static constexpr int shamts[6] = {1, 1, 2, 3, 4, 5};
|
||||
const s32 shamt = shamts[mode];
|
||||
red <<= shamt;
|
||||
green <<= shamt;
|
||||
blue <<= shamt;
|
||||
scale <<= shamt;
|
||||
|
||||
if (mode != 5) {
|
||||
green = red - green;
|
||||
blue = red - blue;
|
||||
}
|
||||
|
||||
if (majcomp == 1)
|
||||
std::swap(red, green);
|
||||
if (majcomp == 2)
|
||||
std::swap(red, blue);
|
||||
|
||||
r1 = std::clamp(red, 0, 0xFFF);
|
||||
g1 = std::clamp(green, 0, 0xFFF);
|
||||
b1 = std::clamp(blue, 0, 0xFFF);
|
||||
|
||||
r0 = std::clamp(red - scale, 0, 0xFFF);
|
||||
g0 = std::clamp(green - scale, 0, 0xFFF);
|
||||
b0 = std::clamp(blue - scale, 0, 0xFFF);
|
||||
}
|
||||
|
||||
// HDR Endpoint Mode 11 (C.2.15): direct RGB pair. Shared by modes 11, 14 and 15,
|
||||
// which all decode their RGB the same way and only differ in how alpha is filled in.
|
||||
static HDREndpointRGB DecodeHDREndpointMode11(u32 v0, u32 v1, u32 v2, u32 v3, u32 v4, u32 v5) {
|
||||
const u32 majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6);
|
||||
if (majcomp == 3) {
|
||||
HDREndpointRGB result;
|
||||
result.r0 = static_cast<s32>(v0 << 4);
|
||||
result.g0 = static_cast<s32>(v2 << 4);
|
||||
result.b0 = static_cast<s32>((v4 & 0x7f) << 5);
|
||||
result.r1 = static_cast<s32>(v1 << 4);
|
||||
result.g1 = static_cast<s32>(v3 << 4);
|
||||
result.b1 = static_cast<s32>((v5 & 0x7f) << 5);
|
||||
return result;
|
||||
}
|
||||
|
||||
const u32 mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5);
|
||||
s32 va = static_cast<s32>(v0 | ((v1 & 0x40) << 2));
|
||||
s32 vb0 = static_cast<s32>(v2 & 0x3f);
|
||||
s32 vb1 = static_cast<s32>(v3 & 0x3f);
|
||||
s32 vc = static_cast<s32>(v1 & 0x3f);
|
||||
s32 vd0 = static_cast<s32>(v4 & 0x7f);
|
||||
s32 vd1 = static_cast<s32>(v5 & 0x7f);
|
||||
|
||||
static constexpr int dbitstab[8] = {7, 6, 7, 6, 5, 6, 5, 6};
|
||||
vd0 = SignExtend(vd0, dbitstab[mode]);
|
||||
vd1 = SignExtend(vd1, dbitstab[mode]);
|
||||
|
||||
const u32 x0 = (v2 >> 6) & 1;
|
||||
const u32 x1 = (v3 >> 6) & 1;
|
||||
const u32 x2 = (v4 >> 6) & 1;
|
||||
const u32 x3 = (v5 >> 6) & 1;
|
||||
const u32 x4 = (v4 >> 5) & 1;
|
||||
const u32 x5 = (v5 >> 5) & 1;
|
||||
|
||||
const u32 ohm = 1u << mode;
|
||||
if (ohm & 0xA4)
|
||||
va |= static_cast<s32>(x0 << 9);
|
||||
if (ohm & 0x08)
|
||||
va |= static_cast<s32>(x2 << 9);
|
||||
if (ohm & 0x50)
|
||||
va |= static_cast<s32>(x4 << 9);
|
||||
if (ohm & 0x50)
|
||||
va |= static_cast<s32>(x5 << 10);
|
||||
if (ohm & 0xA0)
|
||||
va |= static_cast<s32>(x1 << 10);
|
||||
if (ohm & 0xC0)
|
||||
va |= static_cast<s32>(x2 << 11);
|
||||
if (ohm & 0x04)
|
||||
vc |= static_cast<s32>(x1 << 6);
|
||||
if (ohm & 0xE8)
|
||||
vc |= static_cast<s32>(x3 << 6);
|
||||
if (ohm & 0x20)
|
||||
vc |= static_cast<s32>(x2 << 7);
|
||||
if (ohm & 0x5B)
|
||||
vb0 |= static_cast<s32>(x0 << 6);
|
||||
if (ohm & 0x5B)
|
||||
vb1 |= static_cast<s32>(x1 << 6);
|
||||
if (ohm & 0x12)
|
||||
vb0 |= static_cast<s32>(x2 << 7);
|
||||
if (ohm & 0x12)
|
||||
vb1 |= static_cast<s32>(x3 << 7);
|
||||
|
||||
// NOTE: the published spec text says "modeval >> 1" here, but no "modeval" is defined
|
||||
// in this decode (that name belongs to Mode 7's unrelated decode) -- substituting the
|
||||
// "mode" computed just above reproduces exactly Table C.2.23's per-mode shift amounts
|
||||
// (3,3,2,2,1,1,0,0 for modes 0..7), so this is a spec transcription error, not a real
|
||||
// "modeval" this function forgot to compute.
|
||||
const s32 shamt = (static_cast<s32>(mode) >> 1) ^ 3;
|
||||
va <<= shamt;
|
||||
vb0 <<= shamt;
|
||||
vb1 <<= shamt;
|
||||
vc <<= shamt;
|
||||
vd0 <<= shamt;
|
||||
vd1 <<= shamt;
|
||||
|
||||
HDREndpointRGB result;
|
||||
result.r1 = std::clamp(va, 0, 0xFFF);
|
||||
result.g1 = std::clamp(va - vb0, 0, 0xFFF);
|
||||
result.b1 = std::clamp(va - vb1, 0, 0xFFF);
|
||||
|
||||
result.r0 = std::clamp(va - vc, 0, 0xFFF);
|
||||
result.g0 = std::clamp(va - vb0 - vc - vd0, 0, 0xFFF);
|
||||
result.b0 = std::clamp(va - vb1 - vc - vd1, 0, 0xFFF);
|
||||
|
||||
if (majcomp == 1) {
|
||||
std::swap(result.r0, result.g0);
|
||||
std::swap(result.r1, result.g1);
|
||||
} else if (majcomp == 2) {
|
||||
std::swap(result.r0, result.b0);
|
||||
std::swap(result.r1, result.b1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Section C.2.14
|
||||
static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
|
||||
u32 colorEndpointMode) {
|
||||
@@ -1382,8 +1596,85 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
|
||||
ep2.ClampByte();
|
||||
} break;
|
||||
|
||||
case 2: {
|
||||
READ_UINT_VALUES(2)
|
||||
u32 y0, y1;
|
||||
if (v[1] >= v[0]) {
|
||||
y0 = v[0] << 4;
|
||||
y1 = v[1] << 4;
|
||||
} else {
|
||||
y0 = (v[1] << 4) + 8;
|
||||
y1 = (v[0] << 4) - 8;
|
||||
}
|
||||
ep1 = Pixel(0x780, y0, y0, y0);
|
||||
ep2 = Pixel(0x780, y1, y1, y1);
|
||||
} break;
|
||||
|
||||
case 3: {
|
||||
READ_UINT_VALUES(2)
|
||||
u32 y0, d;
|
||||
if (v[0] & 0x80) {
|
||||
y0 = ((v[1] & 0xE0) << 4) | ((v[0] & 0x7F) << 2);
|
||||
d = (v[1] & 0x1F) << 2;
|
||||
} else {
|
||||
y0 = ((v[1] & 0xF0) << 4) | ((v[0] & 0x7F) << 1);
|
||||
d = (v[1] & 0x0F) << 1;
|
||||
}
|
||||
const u32 y1 = (std::min)(y0 + d, 0xFFFU);
|
||||
ep1 = Pixel(0x780, y0, y0, y0);
|
||||
ep2 = Pixel(0x780, y1, y1, y1);
|
||||
} break;
|
||||
|
||||
case 7: {
|
||||
READ_UINT_VALUES(4)
|
||||
s32 r0, g0, b0, r1, g1, b1;
|
||||
DecodeHDREndpointMode7(v[0], v[1], v[2], v[3], r0, g0, b0, r1, g1, b1);
|
||||
ep1 = Pixel(0x780, r0, g0, b0);
|
||||
ep2 = Pixel(0x780, r1, g1, b1);
|
||||
} break;
|
||||
|
||||
case 11: {
|
||||
READ_UINT_VALUES(6)
|
||||
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
|
||||
ep1 = Pixel(0x780, rgb.r0, rgb.g0, rgb.b0);
|
||||
ep2 = Pixel(0x780, rgb.r1, rgb.g1, rgb.b1);
|
||||
} break;
|
||||
|
||||
case 14: {
|
||||
READ_UINT_VALUES(8)
|
||||
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
|
||||
// Only mode with LDR (8-bit UNORM)-interpreted alpha; left as-is (0-255).
|
||||
ep1 = Pixel(v[6], rgb.r0, rgb.g0, rgb.b0);
|
||||
ep2 = Pixel(v[7], rgb.r1, rgb.g1, rgb.b1);
|
||||
} break;
|
||||
|
||||
case 15: {
|
||||
READ_UINT_VALUES(8)
|
||||
const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]);
|
||||
const u32 mode = ((v[6] >> 7) & 1) | ((v[7] >> 6) & 2);
|
||||
s32 a6 = static_cast<s32>(v[6] & 0x7F);
|
||||
s32 a7 = static_cast<s32>(v[7] & 0x7F);
|
||||
s32 alpha0, alpha1;
|
||||
if (mode == 3) {
|
||||
alpha0 = a6 << 5;
|
||||
alpha1 = a7 << 5;
|
||||
} else {
|
||||
a6 |= (a7 << (mode + 1)) & 0x780;
|
||||
a7 &= (0x3F >> mode);
|
||||
a7 ^= 0x20 >> mode;
|
||||
a7 -= 0x20 >> mode;
|
||||
a6 <<= (4 - mode);
|
||||
a7 <<= (4 - mode);
|
||||
a7 += a6;
|
||||
alpha0 = a6;
|
||||
alpha1 = std::clamp(a7, 0, 0xFFF);
|
||||
}
|
||||
ep1 = Pixel(alpha0, rgb.r0, rgb.g0, rgb.b0);
|
||||
ep2 = Pixel(alpha1, rgb.r1, rgb.g1, rgb.b1);
|
||||
} break;
|
||||
|
||||
default:
|
||||
assert(false && "Unsupported color endpoint mode (is it HDR?)");
|
||||
assert(false && "Unsupported color endpoint mode");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1414,6 +1705,39 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
|
||||
}
|
||||
}
|
||||
|
||||
static float HalfToFloat(u16 h) {
|
||||
const u32 sign = static_cast<u32>(h & 0x8000) << 16;
|
||||
u32 exp = (h & 0x7C00) >> 10;
|
||||
u32 mant = h & 0x3FF;
|
||||
u32 bits;
|
||||
if (exp == 0) {
|
||||
if (mant == 0) {
|
||||
bits = sign;
|
||||
} else {
|
||||
s32 e = 127 - 15 + 1;
|
||||
while ((mant & 0x400) == 0) {
|
||||
mant <<= 1;
|
||||
--e;
|
||||
}
|
||||
mant &= 0x3FF;
|
||||
bits = sign | (static_cast<u32>(e) << 23) | (mant << 13);
|
||||
}
|
||||
} else if (exp == 0x1F) {
|
||||
bits = sign | 0x7F800000 | (mant << 13);
|
||||
} else {
|
||||
bits = sign | ((exp - 15 + 127) << 23) | (mant << 13);
|
||||
}
|
||||
float result;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static u16 HalfToClampedByte(u16 half_bits) {
|
||||
const float value = HalfToFloat(half_bits);
|
||||
const float clamped = std::clamp(value, 0.0f, 1.0f);
|
||||
return static_cast<u16>(clamped * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
|
||||
for (u32 j = 0; j < blockHeight; j++) {
|
||||
for (u32 i = 0; i < blockWidth; i++) {
|
||||
@@ -1631,22 +1955,49 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
|
||||
Pixel p;
|
||||
for (u32 c = 0; c < 4; c++) {
|
||||
u32 C0 = endpoints[partition][0].Component(c);
|
||||
C0 = ReplicateByteTo16(C0);
|
||||
u32 C1 = endpoints[partition][1].Component(c);
|
||||
C1 = ReplicateByteTo16(C1);
|
||||
|
||||
u32 plane = 0;
|
||||
if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) {
|
||||
plane = 1;
|
||||
}
|
||||
|
||||
u32 weight = weights[plane][j * blockWidth + i];
|
||||
u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
|
||||
if (C == 65535) {
|
||||
p.Component(c) = 255;
|
||||
|
||||
// Mode 14 is RGB-HDR but keeps an LDR (8-bit UNORM)-interpreted alpha
|
||||
// (component 0 here, see Pixel::A()) -- the only HDR mode with this split.
|
||||
const bool is_hdr = IsHDRColorEndpointMode(colorEndpointMode[partition]) &&
|
||||
!(colorEndpointMode[partition] == 14 && c == 0);
|
||||
|
||||
if (is_hdr) {
|
||||
// Endpoints are raw 12-bit pseudo-logarithmic values; shift left 4 bits
|
||||
// to become 16-bit before interpolating, per C.2.19.
|
||||
C0 <<= 4;
|
||||
C1 <<= 4;
|
||||
const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
|
||||
const u32 E = (C & 0xF800) >> 11;
|
||||
const u32 M = C & 0x7FF;
|
||||
u32 Mt;
|
||||
if (M < 512) {
|
||||
Mt = 3 * M;
|
||||
} else if (M >= 1536) {
|
||||
Mt = 5 * M - 2048;
|
||||
} else {
|
||||
Mt = 4 * M - 512;
|
||||
}
|
||||
const u32 Cf = (E << 10) + (Mt >> 3);
|
||||
// +Inf/NaN clamps to the largest finite FP16 value (0x7BFF).
|
||||
const u16 half_bits = (Cf >= 0x7C00) ? u16{0x7BFF} : static_cast<u16>(Cf);
|
||||
p.Component(c) = HalfToClampedByte(half_bits);
|
||||
} else {
|
||||
double Cf = static_cast<double>(C);
|
||||
p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5);
|
||||
C0 = ReplicateByteTo16(C0);
|
||||
C1 = ReplicateByteTo16(C1);
|
||||
const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64;
|
||||
if (C == 65535) {
|
||||
p.Component(c) = 255;
|
||||
} else {
|
||||
double Cf = static_cast<double>(C);
|
||||
p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user