[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:
@@ -68,7 +68,7 @@ using namespace ucode;
|
||||
|
||||
DxbcShaderTranslator::DxbcShaderTranslator(
|
||||
ui::GraphicsProvider::GpuVendorID vendor_id, bool bindless_resources_used,
|
||||
bool edram_rov_used, bool gamma_render_target_as_srgb,
|
||||
bool edram_rov_used, bool gamma_render_target_as_unorm8,
|
||||
bool msaa_2x_supported, uint32_t draw_resolution_scale_x,
|
||||
uint32_t draw_resolution_scale_y, bool force_emit_source_map)
|
||||
: a_(shader_code_, statistics_),
|
||||
@@ -76,7 +76,7 @@ DxbcShaderTranslator::DxbcShaderTranslator(
|
||||
vendor_id_(vendor_id),
|
||||
bindless_resources_used_(bindless_resources_used),
|
||||
edram_rov_used_(edram_rov_used),
|
||||
gamma_render_target_as_srgb_(gamma_render_target_as_srgb),
|
||||
gamma_render_target_as_unorm8_(gamma_render_target_as_unorm8),
|
||||
msaa_2x_supported_(msaa_2x_supported),
|
||||
draw_resolution_scale_x_(draw_resolution_scale_x),
|
||||
draw_resolution_scale_y_(draw_resolution_scale_y),
|
||||
@@ -223,9 +223,10 @@ void DxbcShaderTranslator::PopSystemTemp(uint32_t count) {
|
||||
}
|
||||
|
||||
void DxbcShaderTranslator::PWLGammaToLinear(
|
||||
uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp,
|
||||
uint32_t source_temp_component, bool source_pre_saturated, uint32_t temp1,
|
||||
uint32_t temp1_component, uint32_t temp2, uint32_t temp2_component) {
|
||||
dxbc::Assembler& a, uint32_t target_temp, uint32_t target_temp_component,
|
||||
uint32_t source_temp, uint32_t source_temp_component,
|
||||
bool source_pre_saturated, uint32_t temp1, uint32_t temp1_component,
|
||||
uint32_t temp2, uint32_t temp2_component) {
|
||||
// The source is needed only once to begin building the result, so it can be
|
||||
// the same as the destination.
|
||||
assert_true(temp1 != target_temp || temp1_component != target_temp_component);
|
||||
@@ -246,25 +247,25 @@ void DxbcShaderTranslator::PWLGammaToLinear(
|
||||
// Using `source >= threshold` comparisons because the input might have not
|
||||
// been saturated yet, and thus it may be NaN - since it will be saturated to
|
||||
// 0 later, the 0...64/255 case should be selected for it.
|
||||
a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(96.0f / 255.0f));
|
||||
a_.OpIf(true, temp2_src);
|
||||
a.OpGE(temp2_dest, source_src, dxbc::Src::LF(96.0f / 255.0f));
|
||||
a.OpIf(true, temp2_src);
|
||||
// [96/255 ... 1
|
||||
a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(192.0f / 255.0f));
|
||||
a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(8.0f / 1024.0f),
|
||||
dxbc::Src::LF(4.0f / 1024.0f));
|
||||
a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-1024.0f),
|
||||
dxbc::Src::LF(-256.0f));
|
||||
a_.OpElse();
|
||||
a.OpGE(temp2_dest, source_src, dxbc::Src::LF(192.0f / 255.0f));
|
||||
a.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(8.0f / 1024.0f),
|
||||
dxbc::Src::LF(4.0f / 1024.0f));
|
||||
a.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-1024.0f),
|
||||
dxbc::Src::LF(-256.0f));
|
||||
a.OpElse();
|
||||
// 0 ... 96/255)
|
||||
a_.OpGE(temp2_dest, source_src, dxbc::Src::LF(64.0f / 255.0f));
|
||||
a_.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(2.0f / 1024.0f),
|
||||
dxbc::Src::LF(1.0f / 1024.0f));
|
||||
a_.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-64.0f), dxbc::Src::LF(0.0f));
|
||||
a_.OpEndIf();
|
||||
a.OpGE(temp2_dest, source_src, dxbc::Src::LF(64.0f / 255.0f));
|
||||
a.OpMovC(temp1_dest, temp2_src, dxbc::Src::LF(2.0f / 1024.0f),
|
||||
dxbc::Src::LF(1.0f / 1024.0f));
|
||||
a.OpMovC(temp2_dest, temp2_src, dxbc::Src::LF(-64.0f), dxbc::Src::LF(0.0f));
|
||||
a.OpEndIf();
|
||||
|
||||
if (!source_pre_saturated) {
|
||||
// Saturate the input, and flush NaN to 0.
|
||||
a_.OpMov(target_dest, source_src, true);
|
||||
a.OpMov(target_dest, source_src, true);
|
||||
}
|
||||
// linear = gamma * (255 * 1024) * scale + offset
|
||||
// As both 1024 and the scale are powers of 2, and 1024 * scale is not smaller
|
||||
@@ -273,22 +274,22 @@ void DxbcShaderTranslator::PWLGammaToLinear(
|
||||
// gamma * (255 * 1024 * scale) - or the option chosen here, as long as
|
||||
// 1024 is applied before the scale since the scale is < 1 (specifically at
|
||||
// least 1/1024), and it may make very small values denormal.
|
||||
a_.OpMul(target_dest, source_pre_saturated ? source_src : target_src,
|
||||
dxbc::Src::LF(255.0f * 1024.0f));
|
||||
a_.OpMAd(target_dest, target_src, temp1_src, temp2_src);
|
||||
a.OpMul(target_dest, source_pre_saturated ? source_src : target_src,
|
||||
dxbc::Src::LF(255.0f * 1024.0f));
|
||||
a.OpMAd(target_dest, target_src, temp1_src, temp2_src);
|
||||
// linear += trunc(linear * scale)
|
||||
a_.OpMul(temp1_dest, target_src, temp1_src);
|
||||
a_.OpRoundZ(temp1_dest, temp1_src);
|
||||
a_.OpAdd(target_dest, target_src, temp1_src);
|
||||
a.OpMul(temp1_dest, target_src, temp1_src);
|
||||
a.OpRoundZ(temp1_dest, temp1_src);
|
||||
a.OpAdd(target_dest, target_src, temp1_src);
|
||||
// linear *= 1/1023
|
||||
a_.OpMul(target_dest, target_src, dxbc::Src::LF(1.0f / 1023.0f));
|
||||
a.OpMul(target_dest, target_src, dxbc::Src::LF(1.0f / 1023.0f));
|
||||
}
|
||||
|
||||
void DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(
|
||||
uint32_t target_temp, uint32_t target_temp_component, uint32_t source_temp,
|
||||
uint32_t source_temp_component, uint32_t temp_or_target,
|
||||
uint32_t temp_or_target_component, uint32_t temp_non_target,
|
||||
uint32_t temp_non_target_component) {
|
||||
dxbc::Assembler& a, uint32_t target_temp, uint32_t target_temp_component,
|
||||
uint32_t source_temp, uint32_t source_temp_component,
|
||||
uint32_t temp_or_target, uint32_t temp_or_target_component,
|
||||
uint32_t temp_non_target, uint32_t temp_non_target_component) {
|
||||
// The source may be the same as the target, but in this case it can't also be
|
||||
// used as a temporary variable.
|
||||
assert_true(target_temp != source_temp ||
|
||||
@@ -318,28 +319,28 @@ void DxbcShaderTranslator::PreSaturatedLinearToPWLGamma(
|
||||
|
||||
// Get the scale (into temp_or_target) and the offset (into temp_non_target)
|
||||
// for the piece.
|
||||
a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(128.0f / 1023.0f));
|
||||
a_.OpIf(true, temp_non_target_src);
|
||||
a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(128.0f / 1023.0f));
|
||||
a.OpIf(true, temp_non_target_src);
|
||||
// [128/1023 ... 1
|
||||
a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(512.0f / 1023.0f));
|
||||
a_.OpMovC(temp_or_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(1023.0f / 8.0f), dxbc::Src::LF(1023.0f / 4.0f));
|
||||
a_.OpMovC(temp_non_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(128.0f / 255.0f), dxbc::Src::LF(64.0f / 255.0f));
|
||||
a_.OpElse();
|
||||
a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(512.0f / 1023.0f));
|
||||
a.OpMovC(temp_or_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(1023.0f / 8.0f), dxbc::Src::LF(1023.0f / 4.0f));
|
||||
a.OpMovC(temp_non_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(128.0f / 255.0f), dxbc::Src::LF(64.0f / 255.0f));
|
||||
a.OpElse();
|
||||
// 0 ... 128/1023)
|
||||
a_.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(64.0f / 1023.0f));
|
||||
a_.OpMovC(temp_or_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(1023.0f / 2.0f), dxbc::Src::LF(1023.0f));
|
||||
a_.OpMovC(temp_non_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(32.0f / 255.0f), dxbc::Src::LF(0.0f));
|
||||
a_.OpEndIf();
|
||||
a.OpGE(temp_non_target_dest, source_src, dxbc::Src::LF(64.0f / 1023.0f));
|
||||
a.OpMovC(temp_or_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(1023.0f / 2.0f), dxbc::Src::LF(1023.0f));
|
||||
a.OpMovC(temp_non_target_dest, temp_non_target_src,
|
||||
dxbc::Src::LF(32.0f / 255.0f), dxbc::Src::LF(0.0f));
|
||||
a.OpEndIf();
|
||||
|
||||
// gamma = trunc(linear * scale) * (1.0 / 255.0) + offset
|
||||
a_.OpMul(target_dest, source_src, temp_or_target_src);
|
||||
a_.OpRoundZ(target_dest, target_src);
|
||||
a_.OpMAd(target_dest, target_src, dxbc::Src::LF(1.0f / 255.0f),
|
||||
temp_non_target_src);
|
||||
a.OpMul(target_dest, source_src, temp_or_target_src);
|
||||
a.OpRoundZ(target_dest, target_src);
|
||||
a.OpMAd(target_dest, target_src, dxbc::Src::LF(1.0f / 255.0f),
|
||||
temp_non_target_src);
|
||||
}
|
||||
|
||||
void DxbcShaderTranslator::RemapAndConvertVertexIndices(
|
||||
@@ -2136,7 +2137,8 @@ const DxbcShaderTranslator::SystemConstantRdef
|
||||
{"xe_texture_swizzled_signs", ShaderRdefTypeIndex::kUint4Array2,
|
||||
sizeof(uint32_t) * 4 * 2},
|
||||
|
||||
{"xe_textures_resolved", ShaderRdefTypeIndex::kUint, sizeof(uint32_t)},
|
||||
{"xe_textures_resolution_scaled", ShaderRdefTypeIndex::kUint,
|
||||
sizeof(uint32_t)},
|
||||
{"xe_sample_count_log2", ShaderRdefTypeIndex::kUint2,
|
||||
sizeof(uint32_t) * 2},
|
||||
{"xe_alpha_test_reference", ShaderRdefTypeIndex::kFloat, sizeof(float)},
|
||||
|
||||
Reference in New Issue
Block a user