Browse Source

[video_core] Initial implementation on HDR endpoints for ASTC decoding

lsfg-android
CamilleLaVey 4 weeks ago
parent
commit
55627c3896
  1. 371
      src/video_core/textures/astc.cpp

371
src/video_core/textures/astc.cpp

@ -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);
}
}
}

Loading…
Cancel
Save