[GPU] 8-bit PWL gamma RT as linear 16-bit UNorm on the host
With render target HLE, directly store linear values as R16G16B16A16_UNORM without gamma conversion, as this format provides more than enough bits (need at least 11 per component due to the maximum scale being 2^3 in the piecewise linear gamma curve) to represent linear values without precision loss. This makes blending work correctly in linear space, improving quality of transparency, lighting passes, and fixing issues such as transparent parts of impact and footstep decals in 4D5307E6 being bright instead. The new behavior is enabled by default, as it hugely improves the accuracy of emulation of this format, that is pretty commonplace in Xbox 360 games, with likely just a small GPU memory and bandwidth usage increase, compared to the alternatives that were previously available on the HLE RB path. It's currently implemented only on Direct3D 12, as most of the current GPU emulation code is planned to be phased out and redone, and no methods other than 8-bit with pre-conversion were implemented on Vulkan previously. To implement on Vulkan later, same conversion as in the Direct3D 12 implementation will need to be done in ownership transfer and resolve shaders. Currently it's somewhat inconvenient to decouple the conversion functions in `SpirvShaderTranslator` from an instance of the translator due to vector constant usage. Later, simpler SPIR-V generation functions may be added (`spv::Builder` usage in general is overly verbose). The previously default method (8-bit storage with pre-conversion in shaders and incorrect blending) can be re-enabled by setting the "gamma_render_target_as_unorm16" configuration option to `false`. This may be useful if the game, for instance, switches between 8_8_8_8_GAMMA and 8_8_8_8 formats for the same data frequently, as switching will result in EDRAM range ownership transfer data copying now. Also, the old path is preserved for Vulkan devices not supporting R16G16B16A16_UNORM with blending. The other workaround that was available previously, replacing the PWL encoding with host hardware sRGB with linear-space blending in render target management and in texture fetching, was also inherently inaccurate in many ways (especially when games have their own PWL encoding math, like 4541080F that displayed incorrect colors on the loading screen), and required tracking of the encoding needed for ranges in the memory. The sRGB workaround therefore was deleted in this commit, greatly simplifying the code in the parts of render target, texture and memory management and shader generation that were involved in it.
This commit is contained in:
@@ -455,7 +455,7 @@ bool D3D12RenderTargetCache::Initialize() {
|
||||
if (path_ == Path::kHostRenderTargets) {
|
||||
// Host render targets.
|
||||
|
||||
gamma_render_target_as_srgb_ = cvars::gamma_render_target_as_srgb;
|
||||
gamma_render_target_as_unorm16_ = cvars::gamma_render_target_as_unorm16;
|
||||
|
||||
depth_float24_round_ = cvars::depth_float24_round;
|
||||
depth_float24_convert_in_pixel_shader_ =
|
||||
@@ -498,6 +498,17 @@ bool D3D12RenderTargetCache::Initialize() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msaa_2x_supported_ && gamma_render_target_as_unorm16_) {
|
||||
multisample_quality_levels.Format = DXGI_FORMAT_R16G16B16A16_UNORM;
|
||||
multisample_quality_levels.NumQualityLevels = 0;
|
||||
if (FAILED(device->CheckFeatureSupport(
|
||||
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
|
||||
&multisample_quality_levels,
|
||||
sizeof(multisample_quality_levels))) ||
|
||||
!multisample_quality_levels.NumQualityLevels) {
|
||||
msaa_2x_supported_ = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msaa_2x_supported_ = false;
|
||||
}
|
||||
@@ -1013,8 +1024,8 @@ bool D3D12RenderTargetCache::Initialize() {
|
||||
} else if (path_ == Path::kPixelShaderInterlock) {
|
||||
// Pixel shader interlock (rasterizer-ordered view).
|
||||
|
||||
// Blending is done in linear space directly in shaders.
|
||||
gamma_render_target_as_srgb_ = false;
|
||||
// Piecewise linear gamma is 8-bit with programmable blending.
|
||||
gamma_render_target_as_unorm16_ = false;
|
||||
|
||||
// Always true float24 depth rounded to the nearest even.
|
||||
depth_float24_round_ = true;
|
||||
@@ -1774,12 +1785,10 @@ DXGI_FORMAT D3D12RenderTargetCache::GetColorResourceDXGIFormat(
|
||||
// compression.
|
||||
switch (format) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
if (gamma_render_target_as_srgb_) {
|
||||
// Can toggle between UNORM and UNORM_SRGB for the same data.
|
||||
return DXGI_FORMAT_R8G8B8A8_TYPELESS;
|
||||
}
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
return gamma_render_target_as_unorm16_ ? DXGI_FORMAT_R16G16B16A16_UNORM
|
||||
: DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
|
||||
return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
@@ -1812,11 +1821,6 @@ DXGI_FORMAT D3D12RenderTargetCache::GetColorResourceDXGIFormat(
|
||||
DXGI_FORMAT D3D12RenderTargetCache::GetColorDrawDXGIFormat(
|
||||
xenos::ColorRenderTargetFormat format) const {
|
||||
switch (format) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
return gamma_render_target_as_srgb_ ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||
: DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case xenos::ColorRenderTargetFormat::k_16_16:
|
||||
return DXGI_FORMAT_R16G16_SNORM;
|
||||
case xenos::ColorRenderTargetFormat::k_16_16_16_16:
|
||||
@@ -1910,6 +1914,10 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat(
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12RenderTargetCache::IsGammaFormatHostStorageSeparate() const {
|
||||
return gamma_render_target_as_unorm16_;
|
||||
}
|
||||
|
||||
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||
RenderTargetKey key) {
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
@@ -1990,7 +1998,6 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||
}
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE descriptor_draw_handle =
|
||||
descriptor_draw.GetHandle();
|
||||
ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_srgb;
|
||||
ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_load_separate;
|
||||
ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_srv_stencil;
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc;
|
||||
@@ -2049,23 +2056,6 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||
}
|
||||
device->CreateRenderTargetView(resource.Get(), &rtv_desc,
|
||||
descriptor_draw_handle);
|
||||
// sRGB drawing RTV.
|
||||
switch (key.GetColorFormat()) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
if (gamma_render_target_as_srgb_) {
|
||||
descriptor_draw_srgb = descriptor_pool.AllocateDescriptor();
|
||||
if (!descriptor_draw_srgb.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
rtv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
device->CreateRenderTargetView(resource.Get(), &rtv_desc,
|
||||
descriptor_draw_srgb.GetHandle());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Ownership transfer RTV.
|
||||
DXGI_FORMAT load_format =
|
||||
GetColorOwnershipTransferDXGIFormat(key.GetColorFormat());
|
||||
@@ -2086,9 +2076,8 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||
|
||||
return new D3D12RenderTarget(
|
||||
key, resource.Get(), std::move(descriptor_draw),
|
||||
std::move(descriptor_draw_srgb), std::move(descriptor_load_separate),
|
||||
std::move(descriptor_srv), std::move(descriptor_srv_stencil),
|
||||
resource_state);
|
||||
std::move(descriptor_load_separate), std::move(descriptor_srv),
|
||||
std::move(descriptor_srv_stencil), resource_state);
|
||||
}
|
||||
|
||||
bool D3D12RenderTargetCache::IsHostDepthEncodingDifferent(
|
||||
@@ -3429,15 +3418,25 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
|
||||
if (dest_is_64bpp) {
|
||||
// Handle construction of 64bpp color, either from two 32-bit samples in r0
|
||||
// and r1, or from one 64bpp sample in r1. Using r2.x as temporary when
|
||||
// and r1, or from one 64bpp sample in r1. Using r2.xy as temporary when
|
||||
// needed.
|
||||
// If color_packed_in_r0x_and_r1x, use the generic path for combining two
|
||||
// 32-bit samples - as raw in r0.x and r1.x - into the destination.
|
||||
bool color_packed_in_r0x_and_r1x = false;
|
||||
if (source_is_color) {
|
||||
switch (source_color_format) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
|
||||
// 8_8_8_8_GAMMA is represented by linear stored in
|
||||
// R16G16B16A16_UNORM.
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
for (uint32_t j = 0; j < 3; ++j) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, i, j, i, j,
|
||||
2, 0, 2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8: {
|
||||
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),
|
||||
@@ -3588,7 +3587,14 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
switch (source_color_format) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
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_stencil_bit) {
|
||||
if (source_color_format ==
|
||||
xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, 1, 0, 1, 0,
|
||||
2, 0, 2, 1);
|
||||
}
|
||||
a.OpMAd(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(1, dxbc::Src::kXXXX),
|
||||
dxbc::Src::LF(255.0f), dxbc::Src::LF(0.5f));
|
||||
a.OpFToU(dxbc::Dest::R(1, 0b0001),
|
||||
@@ -3598,9 +3604,30 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
xenos::ColorRenderTargetFormat::k_8_8_8_8 ||
|
||||
dest_color_format ==
|
||||
xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA)) {
|
||||
// Same format - passthrough.
|
||||
// Same format - only perform color space conversion.
|
||||
if (dest_color_format != source_color_format) {
|
||||
if (dest_color_format ==
|
||||
xenos::ColorRenderTargetFormat::k_8_8_8_8) {
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(
|
||||
a, 1, i, 1, i, 2, 0, 2, 1);
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PWLGammaToLinear(a, 1, i, 1, i, true, 2,
|
||||
0, 2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
a.OpMov(dxbc::Dest::O(0), dxbc::Src::R(1));
|
||||
} else if (mode.output == TransferOutput::kDepth) {
|
||||
if (source_color_format ==
|
||||
xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) {
|
||||
for (uint32_t i = 1; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(
|
||||
a, 1, i, 1, i, 2, 0, 2, 1);
|
||||
}
|
||||
}
|
||||
// When need only depth, not stencil, skip the red component.
|
||||
a.OpMAd(dxbc::Dest::R(
|
||||
1, osgn_parameter_index_sv_stencil_ref != UINT32_MAX
|
||||
@@ -3625,6 +3652,10 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
dxbc::Src::R(1, dxbc::Src::kYYYY));
|
||||
} else {
|
||||
color_packed_in_r1x = true;
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, 1, i, 1, i,
|
||||
2, 0, 2, 1);
|
||||
}
|
||||
a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f),
|
||||
dxbc::Src::LF(0.5f));
|
||||
a.OpFToU(dxbc::Dest::R(1), dxbc::Src::R(1));
|
||||
@@ -3768,8 +3799,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
// this is the end of the shader.
|
||||
if (color_packed_in_r1x) {
|
||||
switch (dest_color_format) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8: {
|
||||
a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(8),
|
||||
dxbc::Src::LU(0, 8, 16, 24),
|
||||
dxbc::Src::R(1, dxbc::Src::kXXXX));
|
||||
@@ -3777,6 +3807,26 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||
a.OpMul(dxbc::Dest::O(0), dxbc::Src::R(1),
|
||||
dxbc::Src::LF(1.0f / 255.0f));
|
||||
} break;
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
|
||||
// 8_8_8_8_GAMMA is represented by linear stored in
|
||||
// R16G16B16A16_UNORM.
|
||||
a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(8),
|
||||
dxbc::Src::LU(0, 8, 16, 24),
|
||||
dxbc::Src::R(1, dxbc::Src::kXXXX));
|
||||
a.OpUToF(dxbc::Dest::R(1), dxbc::Src::R(1));
|
||||
a.OpMul(dxbc::Dest::R(1, 0b0111), dxbc::Src::R(1),
|
||||
dxbc::Src::LF(1.0f / 255.0f));
|
||||
a.OpMul(dxbc::Dest::O(0, 0b1000), dxbc::Src::R(1),
|
||||
dxbc::Src::LF(1.0f / 255.0f));
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PWLGammaToLinear(a, 1, i, 1, i, true, 0,
|
||||
0, 0, 1);
|
||||
}
|
||||
// TODO(Triang3l): The `mov` can be eliminated by passing the
|
||||
// destination to `PWLGammaToLinear` as `dxbc::Dest` rather than
|
||||
// just the register index.
|
||||
a.OpMov(dxbc::Dest::O(0, 0b0111), dxbc::Src::R(1));
|
||||
} break;
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: {
|
||||
a.OpUBFE(dxbc::Dest::R(1), dxbc::Src::LU(10, 10, 10, 2),
|
||||
@@ -5357,13 +5407,24 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
|
||||
float color_clear_value[4] = {};
|
||||
bool clear_via_drawing = false;
|
||||
switch (dest_rt_key.GetColorFormat()) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8: {
|
||||
for (uint32_t j = 0; j < 4; ++j) {
|
||||
color_clear_value[j] =
|
||||
((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF);
|
||||
}
|
||||
} break;
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
|
||||
// 8_8_8_8_GAMMA is represented by linear stored in
|
||||
// R16G16B16A16_UNORM.
|
||||
for (uint32_t j = 0; j < 4; ++j) {
|
||||
color_clear_value[j] =
|
||||
((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF);
|
||||
}
|
||||
for (uint32_t j = 0; j < 3; ++j) {
|
||||
color_clear_value[j] =
|
||||
xenos::PWLGammaToLinear(color_clear_value[j]);
|
||||
}
|
||||
} break;
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: {
|
||||
for (uint32_t j = 0; j < 3; ++j) {
|
||||
@@ -5500,17 +5561,6 @@ void D3D12RenderTargetCache::SetCommandListRenderTargets(
|
||||
sizeof(current_command_list_render_targets_))) {
|
||||
are_current_command_list_render_targets_valid_ = false;
|
||||
}
|
||||
uint32_t render_targets_are_srgb;
|
||||
if (gamma_render_target_as_srgb_) {
|
||||
render_targets_are_srgb = last_update_accumulated_color_targets_are_gamma();
|
||||
if (are_current_command_list_render_targets_srgb_ !=
|
||||
render_targets_are_srgb) {
|
||||
are_current_command_list_render_targets_srgb_ = render_targets_are_srgb;
|
||||
are_current_command_list_render_targets_valid_ = false;
|
||||
}
|
||||
} else {
|
||||
render_targets_are_srgb = 0;
|
||||
}
|
||||
if (!are_current_command_list_render_targets_valid_) {
|
||||
std::memcpy(current_command_list_render_targets_,
|
||||
depth_and_color_render_targets,
|
||||
@@ -5537,10 +5587,7 @@ void D3D12RenderTargetCache::SetCommandListRenderTargets(
|
||||
: null_rtv_descriptor_ss_.GetHandle();
|
||||
}
|
||||
auto& d3d12_rt = *static_cast<const D3D12RenderTarget*>(render_target);
|
||||
rtv_handles[rtv_count++] =
|
||||
(render_targets_are_srgb & (uint32_t(1) << i))
|
||||
? d3d12_rt.descriptor_draw_srgb().GetHandle()
|
||||
: d3d12_rt.descriptor_draw().GetHandle();
|
||||
rtv_handles[rtv_count++] = d3d12_rt.descriptor_draw().GetHandle();
|
||||
}
|
||||
command_processor_.GetDeferredCommandList().D3DOMSetRenderTargets(
|
||||
rtv_count, rtv_handles, FALSE,
|
||||
@@ -6227,7 +6274,6 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
|
||||
} else {
|
||||
switch (key.GetColorFormat()) {
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
if (!source_is_uint) {
|
||||
a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f),
|
||||
dxbc::Src::LF(0.5f));
|
||||
@@ -6239,6 +6285,22 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
|
||||
dxbc::Src::R(1, dxbc::Src::kXXXX));
|
||||
}
|
||||
break;
|
||||
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
// 8_8_8_8_GAMMA is represented by linear stored in R16G16B16A16_UNORM.
|
||||
assert_false(source_is_uint);
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(a, 1, i, 1, i, 0,
|
||||
0, 0, 1);
|
||||
}
|
||||
a.OpMAd(dxbc::Dest::R(1), dxbc::Src::R(1), dxbc::Src::LF(255.0f),
|
||||
dxbc::Src::LF(0.5f));
|
||||
a.OpFToU(dxbc::Dest::R(1), dxbc::Src::R(1));
|
||||
for (uint32_t i = 1; i < 4; ++i) {
|
||||
a.OpBFI(dxbc::Dest::R(1, 0b0001), dxbc::Src::LU(8),
|
||||
dxbc::Src::LU(i * 8), dxbc::Src::R(1).Select(i),
|
||||
dxbc::Src::R(1, dxbc::Src::kXXXX));
|
||||
}
|
||||
break;
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
|
||||
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
|
||||
if (!source_is_uint) {
|
||||
|
||||
Reference in New Issue
Block a user