[D3D12] Fix gamma_unorm16 render target transfer shaders

Gamma stored as R16G16B16A16_UNORM is 64bpp in host memory but
represents a single 32bpp pixel, unlike true 64bpp formats which pack
two 32bpp pixels together. Separate dest_is_gamma_unorm16 from
dest_is_64bpp to avoid incorrect coordinate transforms.

Add output paths for gamma_unorm16 destinations:
- gamma→gamma: direct passthrough (already linear in R16G16B16A16)
- k_8_8_8_8→gamma: convert gamma-encoded source to linear
- Cross-format packed EDRAM: midpoint encoding to survive UNORM16
  quantization round-trip through PreSaturatedLinearToPWLGamma

Fixes red bleeding in Halo Reach, image halving in Just Cause 2,
Portal not rendering, and black rock/plant textures in Halo Reach.
This commit is contained in:
Herman S.
2026-01-20 14:51:56 +09:00
parent 65d42843b6
commit 2912da02fe

View File

@@ -2064,6 +2064,12 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
xenos::DepthRenderTargetFormat(key.dest_resource_format);
bool dest_is_64bpp =
dest_is_color && xenos::IsColorRenderTargetFormat64bpp(dest_color_format);
// Whether dest is gamma stored as R16G16B16A16_UNORM (64bpp host storage but
// 32bpp coordinate addressing).
bool dest_is_gamma_unorm16 =
dest_is_color &&
dest_color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA &&
gamma_render_target_as_unorm16_;
xenos::ColorRenderTargetFormat source_color_format =
xenos::ColorRenderTargetFormat(key.source_resource_format);
@@ -3315,7 +3321,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
a.OpMov(dxbc::Dest::OStencilRef(), dxbc::Src::R(1, dxbc::Src::kXXXX));
}
if (dest_is_64bpp) {
if (dest_is_64bpp || dest_is_gamma_unorm16) {
// Handle construction of 64bpp color, either from two 32-bit samples in r0
// and r1, or from one 64bpp sample in r1. Using r2.xy as temporary when
// needed.
@@ -3327,6 +3333,10 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
// 8_8_8_8_GAMMA is represented by linear stored in
// R16G16B16A16_UNORM.
if (dest_is_gamma_unorm16) {
a.OpMov(dxbc::Dest::O(0), dxbc::Src::R(1));
break;
}
for (uint32_t i = 0; i < 2; ++i) {
for (uint32_t j = 0; j < 3; ++j) {
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, i, j, i, j,
@@ -3336,6 +3346,15 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
}
[[fallthrough]];
case xenos::ColorRenderTargetFormat::k_8_8_8_8: {
if (dest_is_gamma_unorm16) {
// Convert gamma-encoded source to linear for gamma_unorm16 dest.
for (uint32_t j = 0; j < 3; ++j) {
DxbcShaderTranslator::PWLGammaToLinear(a, 1, j, 1, j, true, 2, 0,
2, 1);
}
a.OpMov(dxbc::Dest::O(0), dxbc::Src::R(1));
break;
}
color_packed_in_r0x_and_r1x = true;
for (uint32_t i = 0; i < 2; ++i) {
a.OpMAd(dxbc::Dest::R(i), dxbc::Src::R(i), dxbc::Src::LF(255.0f),
@@ -3468,6 +3487,75 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
if (dest_color_format == xenos::ColorRenderTargetFormat::k_32_32_FLOAT) {
a.OpMov(dxbc::Dest::O(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpMov(dxbc::Dest::O(0, 0b0010), dxbc::Src::R(1, dxbc::Src::kXXXX));
} else if (dest_is_gamma_unorm16) {
// For gamma_unorm16, only r1.x has a valid packed 32-bit EDRAM
// dword (one pixel, since gamma_unorm16 is 32bpp Xenos / 64bpp
// host). Reinterpret as k_8_8_8_8_GAMMA (4 gamma-encoded bytes)
// and convert to linear float for R16G16B16A16_UNORM output.
//
// Use midpoint encoding to survive the UNORM16 quantization
// round-trip through PreSaturatedLinearToPWLGamma (which uses
// trunc()). Storing the exact PWLGammaToLinear result puts values
// at the lower boundary of the valid range, where UNORM16
// quantization can push them below threshold causing +/-1 byte
// errors that corrupt cross-format EDRAM reinterpretation.
//
// For gamma byte B, the midpoint linear value is:
// Piece 0 (B < 64): F = (B + 0.5) / 1023.0
// Piece 1 (64<=B<96): F = (B - 31.5) / 511.5
// Piece 2 (96<=B<192): F = (B - 63.5) / 255.75
// Piece 3 (B >= 192): F = (B - 127.5) / 127.875
// Using MAd form: F = B * recip + offset.
// Extract 4 bytes: r1.xyzw = [R, G, B, A] as uint.
a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(8, 8, 8, 8),
dxbc::Src::LU(0, 8, 16, 24),
dxbc::Src::R(1, dxbc::Src::kXXXX));
// Alpha: o0.w = float(A) / 255.0 (no gamma conversion).
a.OpUToF(dxbc::Dest::R(0, 0b1000), dxbc::Src::R(1, dxbc::Src::kWWWW));
a.OpMul(dxbc::Dest::O(0, 0b1000), dxbc::Src::R(0, dxbc::Src::kWWWW),
dxbc::Src::LF(1.0f / 255.0f));
// RGB: per-channel midpoint encoding using r0.xy as (recip,
// offset) and r2.x as comparison temp.
for (uint32_t j = 0; j < 3; ++j) {
// Default to piece 0.
a.OpMov(dxbc::Dest::R(0, 0b0001), dxbc::Src::LF(1.0f / 1023.0f));
a.OpMov(dxbc::Dest::R(0, 0b0010), dxbc::Src::LF(0.5f / 1023.0f));
// Piece 1: byte >= 64.
a.OpUGE(dxbc::Dest::R(2, 0b0001), dxbc::Src::R(1).Select(j),
dxbc::Src::LU(64));
a.OpMovC(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(1.0f / 511.5f),
dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpMovC(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(-31.5f / 511.5f),
dxbc::Src::R(0, dxbc::Src::kYYYY));
// Piece 2: byte >= 96.
a.OpUGE(dxbc::Dest::R(2, 0b0001), dxbc::Src::R(1).Select(j),
dxbc::Src::LU(96));
a.OpMovC(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(1.0f / 255.75f),
dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpMovC(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(-63.5f / 255.75f),
dxbc::Src::R(0, dxbc::Src::kYYYY));
// Piece 3: byte >= 192.
a.OpUGE(dxbc::Dest::R(2, 0b0001), dxbc::Src::R(1).Select(j),
dxbc::Src::LU(192));
a.OpMovC(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(1.0f / 127.875f),
dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpMovC(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::LF(-127.5f / 127.875f),
dxbc::Src::R(0, dxbc::Src::kYYYY));
// F = float(byte) * recip + offset.
a.OpUToF(dxbc::Dest::R(2, 0b0001), dxbc::Src::R(1).Select(j));
a.OpMAd(dxbc::Dest::O(0, 1 << j), dxbc::Src::R(2, dxbc::Src::kXXXX),
dxbc::Src::R(0, dxbc::Src::kXXXX),
dxbc::Src::R(0, dxbc::Src::kYYYY));
}
} else {
for (uint32_t i = 0; i < 2; ++i) {
a.OpUBFE(dxbc::Dest::O(0, 0b11 << (i * 2)), dxbc::Src::LU(16),