GPU/HW: Further tweaks to replacement alpha handling

We can't simply clear the alpha channel unconditionally here, because that
would result in any black pixels with zero alpha being transparency-culled.

Instead, we set it to a minimum value (2/255 in case of rounding error, I
don't trust drivers here) so that transparent polygons in the source still
set bit 15 to zero in the framebuffer, but are not transparency-culled.

Silent Hill needs it to be zero, I'm not aware of anything that needs
specific values yet. If it did, we'd need a different dumping technique.
This commit is contained in:
Stenzek 2025-01-23 12:12:40 +10:00
parent 9113a6e6a6
commit df8822760a
No known key found for this signature in database
3 changed files with 44 additions and 34 deletions

View File

@ -732,8 +732,8 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode, GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode,
GPUTextureFilter texture_filtering, bool upscaled, bool msaa, bool per_sample_shading, bool uv_limits, GPUTextureFilter texture_filtering, bool upscaled, bool msaa, bool per_sample_shading, bool uv_limits,
bool force_round_texcoords, bool true_color, bool dithering, bool scaled_dithering, bool disable_color_perspective, bool force_round_texcoords, bool true_color, bool dithering, bool scaled_dithering, bool disable_color_perspective,
bool interlacing, bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth, bool interlacing, bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth, bool rov_depth_test,
bool rov_depth_test, bool rov_depth_write) const bool rov_depth_write) const
{ {
DebugAssert(!true_color || !dithering); // Should not be doing dithering+true color. DebugAssert(!true_color || !dithering); // Should not be doing dithering+true color.
@ -1834,10 +1834,12 @@ std::string GPU_HW_ShaderGen::GenerateBoxSampleDownsampleFragmentShader(u32 fact
return ss.str(); return ss.str();
} }
std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitransparent, bool bilinear_filter) const std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool replacement, bool semitransparent,
bool bilinear_filter) const
{ {
std::stringstream ss; std::stringstream ss;
WriteHeader(ss); WriteHeader(ss);
DefineMacro(ss, "REPLACEMENT", replacement);
DefineMacro(ss, "SEMITRANSPARENT", semitransparent); DefineMacro(ss, "SEMITRANSPARENT", semitransparent);
DefineMacro(ss, "BILINEAR_FILTER", bilinear_filter); DefineMacro(ss, "BILINEAR_FILTER", bilinear_filter);
DeclareUniformBuffer(ss, {"float4 u_texture_size"}, true); DeclareUniformBuffer(ss, {"float4 u_texture_size"}, true);
@ -1864,6 +1866,7 @@ std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitr
// Bilinearly interpolate. // Bilinearly interpolate.
float2 weights = abs(texel_top_left); float2 weights = abs(texel_top_left);
float4 color = lerp(lerp(s00, s10, weights.x), lerp(s01, s11, weights.x), weights.y); float4 color = lerp(lerp(s00, s10, weights.x), lerp(s01, s11, weights.x), weights.y);
float orig_alpha = float(color.a > 0.0);
#if !SEMITRANSPARENT #if !SEMITRANSPARENT
// Compute alpha from how many texels aren't pixel color 0000h. // Compute alpha from how many texels aren't pixel color 0000h.
@ -1881,23 +1884,34 @@ std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitr
#endif #endif
#else #else
float4 color = SAMPLE_TEXTURE_LEVEL(samp0, v_tex0, 0.0); float4 color = SAMPLE_TEXTURE_LEVEL(samp0, v_tex0, 0.0);
float orig_alpha = color.a;
#endif #endif
o_col0.rgb = color.rgb; o_col0.rgb = color.rgb;
// Alpha processing. // Alpha processing.
#if SEMITRANSPARENT #if REPLACEMENT
// Map anything not 255 to 1 for semitransparent, otherwise zero for opaque. #if SEMITRANSPARENT
o_col0.a = (color.a <= 0.95f) ? 1.0f : 0.0f; // Map anything not 255 to 1 for semitransparent, otherwise zero for opaque.
o_col0.a = VECTOR_EQ(color, float4(0.0, 0.0, 0.0, 0.0)) ? 0.0f : o_col0.a; o_col0.a = (color.a <= 0.95f) ? 1.0f : 0.0f;
#else o_col0.a = VECTOR_EQ(color, float4(0.0, 0.0, 0.0, 0.0)) ? 0.0f : o_col0.a;
// Map anything with an alpha below 0.5 to transparent. #else
// Leave (0,0,0,0) as 0000 for opaque replacements for cutout alpha. // Map anything with an alpha below 0.5 to transparent.
o_col0.rgb = lerp(o_col0.rgb, float3(0.0, 0.0, 0.0), float(color.a < 0.5)); // Leave (0,0,0,0) as 0000 for opaque replacements for cutout alpha.
float alpha = float(color.a >= 0.5);
o_col0.rgb = lerp(float3(0.0, 0.0, 0.0), o_col0.rgb, alpha);
// Clear alpha channel. This is the value for bit15 in the framebuffer. // We can't simply clear the alpha channel unconditionally here, because that
// Silent Hill needs it to be zero, I'm not aware of anything that needs // would result in any black pixels with zero alpha being transparency-culled.
// specific values yet. If it did, we'd need a different dumping technique. // Instead, we set it to a minimum value (2/255 in case of rounding error, I
o_col0.a = 0.0; // don't trust drivers here) so that transparent polygons in the source still
// set bit 15 to zero in the framebuffer, but are not transparency-culled.
// Silent Hill needs it to be zero, I'm not aware of anything that needs
// specific values yet. If it did, we'd need a different dumping technique.
o_col0.a = lerp(0.0, 2.0 / 255.0, alpha);
#endif
#else
// Preserve original bit 15 for non-replacements.
o_col0.a = orig_alpha;
#endif #endif
} }
)"; )";

View File

@ -44,7 +44,8 @@ public:
std::string GenerateAdaptiveDownsampleCompositeFragmentShader() const; std::string GenerateAdaptiveDownsampleCompositeFragmentShader() const;
std::string GenerateBoxSampleDownsampleFragmentShader(u32 factor) const; std::string GenerateBoxSampleDownsampleFragmentShader(u32 factor) const;
std::string GenerateReplacementMergeFragmentShader(bool semitransparent, bool bilinear_filter) const; std::string GenerateReplacementMergeFragmentShader(bool replacement, bool semitransparent,
bool bilinear_filter) const;
private: private:
void WriteColorConversionFunctions(std::stringstream& ss) const; void WriteColorConversionFunctions(std::stringstream& ss) const;

View File

@ -862,7 +862,7 @@ bool GPUTextureCache::CompilePipelines(Error* error)
std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader( std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(), GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateReplacementMergeFragmentShader(false, s_state.config.replacement_scale_linear_filter)); shadergen.GenerateReplacementMergeFragmentShader(false, false, s_state.config.replacement_scale_linear_filter));
if (!fs) if (!fs)
return false; return false;
GL_OBJECT_NAME(fs, "Replacement upscale shader"); GL_OBJECT_NAME(fs, "Replacement upscale shader");
@ -871,21 +871,18 @@ bool GPUTextureCache::CompilePipelines(Error* error)
return false; return false;
GL_OBJECT_NAME(s_state.replacement_upscale_pipeline, "Replacement upscale pipeline"); GL_OBJECT_NAME(s_state.replacement_upscale_pipeline, "Replacement upscale pipeline");
if (s_state.config.replacement_scale_linear_filter) fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
{ shadergen.GenerateReplacementMergeFragmentShader(true, false, false));
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), if (!fs)
shadergen.GenerateReplacementMergeFragmentShader(false, false)); return false;
if (!fs) GL_OBJECT_NAME(fs, "Replacement draw shader");
return false; plconfig.fragment_shader = fs.get();
GL_OBJECT_NAME(fs, "Replacement draw shader"); if (!(s_state.replacement_draw_pipeline = g_gpu_device->CreatePipeline(plconfig)))
plconfig.fragment_shader = fs.get(); return false;
if (!(s_state.replacement_draw_pipeline = g_gpu_device->CreatePipeline(plconfig))) GL_OBJECT_NAME(s_state.replacement_draw_pipeline, "Replacement draw pipeline");
return false;
GL_OBJECT_NAME(s_state.replacement_draw_pipeline, "Replacement draw pipeline");
}
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateReplacementMergeFragmentShader(true, false)); shadergen.GenerateReplacementMergeFragmentShader(true, true, false));
if (!fs) if (!fs)
return false; return false;
GL_OBJECT_NAME(fs, "Replacement semitransparent draw shader"); GL_OBJECT_NAME(fs, "Replacement semitransparent draw shader");
@ -3669,10 +3666,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
g_gpu_device->SetTextureSampler(0, si.texture, g_gpu_device->SetTextureSampler(0, si.texture,
s_state.config.replacement_scale_linear_filter ? g_gpu_device->GetLinearSampler() : s_state.config.replacement_scale_linear_filter ? g_gpu_device->GetLinearSampler() :
g_gpu_device->GetNearestSampler()); g_gpu_device->GetNearestSampler());
g_gpu_device->SetPipeline(si.invert_alpha ? g_gpu_device->SetPipeline(si.invert_alpha ? s_state.replacement_semitransparent_draw_pipeline.get() :
s_state.replacement_semitransparent_draw_pipeline.get() : s_state.replacement_draw_pipeline.get());
(s_state.replacement_draw_pipeline ? s_state.replacement_draw_pipeline.get() :
s_state.replacement_upscale_pipeline.get()));
g_gpu_device->Draw(3, 0); g_gpu_device->Draw(3, 0);
} }