[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:
Triang3l
2026-01-17 18:09:42 +03:00
parent f2fabfdf04
commit cec9ca0ef2
34 changed files with 3072 additions and 3188 deletions

View File

@@ -49,7 +49,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
public:
DxbcShaderTranslator(ui::GraphicsProvider::GpuVendorID vendor_id,
bool bindless_resources_used, bool edram_rov_used,
bool gamma_render_target_as_srgb = false,
bool gamma_render_target_as_unorm8 = false,
bool msaa_2x_supported = true,
uint32_t draw_resolution_scale_x = 1,
uint32_t draw_resolution_scale_y = 1,
@@ -310,9 +310,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
// components of each of the 32 used texture fetch constants.
uint32_t texture_swizzled_signs[8];
// Whether the contents of each texture in fetch constants comes from a
// resolve operation.
uint32_t textures_resolved;
// Whether each texture in fetch constants contains resolution-scaled data.
uint32_t textures_resolution_scaled;
// Log2 of X and Y sample size. Used for alpha to mask, and for MSAA with
// ROV, this is used for EDRAM address calculation.
uint32_t sample_count_log2[2];
@@ -419,7 +418,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
kTextureSwizzledSigns,
kTexturesResolved,
kTexturesResolutionScaled,
kSampleCountLog2,
kAlphaTestReference,
@@ -572,6 +571,26 @@ class DxbcShaderTranslator : public ShaderTranslator {
uint32_t temp2_temp_component,
bool remap_to_0_to_0_5);
// Converts one scalar from piecewise linear gamma to linear. The target may
// be the same as the source, the temporary variables must be different. If
// the source is not pre-saturated, saturation will be done internally.
static void PWLGammaToLinear(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);
// Converts one scalar, which must be saturated before calling this function,
// from linear to piecewise linear gamma. The target may be the same as either
// the source or as temp_or_target, but not as both (and temp_or_target may
// not be the same as the source). temp_non_target must be different.
static void PreSaturatedLinearToPWLGamma(
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);
protected:
void Reset() override;
@@ -683,24 +702,6 @@ class DxbcShaderTranslator : public ShaderTranslator {
// inactive.
void ExportToMemory(uint8_t export_eM);
// Converts one scalar from piecewise linear gamma to linear. The target may
// be the same as the source, the temporary variables must be different. If
// the source is not pre-saturated, saturation will be done internally.
void 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);
// Converts one scalar, which must be saturated before calling this function,
// from linear to piecewise linear gamma. The target may be the same as either
// the source or as temp_or_target, but not as both (and temp_or_target may
// not be the same as the source). temp_non_target must be different.
void 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);
bool IsSampleRate() const {
assert_true(is_pixel_shader());
return DSV_IsWritingFloat24Depth() && !current_shader().writes_depth();
@@ -971,8 +972,9 @@ class DxbcShaderTranslator : public ShaderTranslator {
bool edram_rov_used_;
// Whether with RTV-based output-merger, k_8_8_8_8_GAMMA render targets are
// represented as host sRGB.
bool gamma_render_target_as_srgb_;
// represented as host 8-bit unsigned normalized, and require conversion in
// translated shaders.
bool gamma_render_target_as_unorm8_;
// Whether 2x MSAA is emulated using real 2x MSAA rather than two samples of
// 4x MSAA.