[Vulkan] Alpha to coverage improvements

Refactor redundant code, fix 2x MSAA sample mapping and
only declare gl_FragCoord when alpha to coverage can run
This commit is contained in:
Herman S.
2025-11-20 09:54:36 +09:00
parent cba315cf38
commit 792e61f6e2
2 changed files with 77 additions and 79 deletions

View File

@@ -1980,14 +1980,17 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
}
// Fragment coordinates.
// TODO(Triang3l): More conditions - alpha to coverage (if RT 0 is written,
// and there's no early depth / stencil), depth writing in the fragment shader
// TODO(Triang3l): More conditions - depth writing in the fragment shader
// (per-sample if supported).
// Also needed for alpha-to-mask dithering in FBO mode.
// FSI: Always needed for EDRAM offset calculation and depth derivatives.
// param_gen: Needed for PsParamGen calculation.
// FBO alpha-to-coverage: Needed for dithering pattern, but only when
// alpha-to-coverage can actually run (no early fragment tests).
bool need_frag_coord =
edram_fragment_shader_interlock_ || param_gen_needed ||
(!edram_fragment_shader_interlock_ && !is_depth_only_fragment_shader_ &&
current_shader().writes_color_target(0));
current_shader().writes_color_target(0) &&
!IsExecutionModeEarlyFragmentTests());
if (need_frag_coord) {
input_fragment_coordinates_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassInput, type_float4_, "gl_FragCoord");

View File

@@ -3675,103 +3675,98 @@ void SpirvShaderTranslator::FSI_AlphaToMask() {
// 4x MSAA
builder_->setBuildPoint(&block_4x_msaa);
spv::Id coverage_4x = spv::NoResult;
{
FSI_AlphaToMaskSample(true, 0, 0.75f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 1, 0.25f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 2, 0.5f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 3, 1.0f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
if (!edram_fragment_shader_interlock_) {
// Write to gl_SampleMask[0] and discard if zero coverage
id_vector_temp_.clear();
id_vector_temp_.push_back(builder_->makeIntConstant(0));
spv::Id sample_mask_element = builder_->createAccessChain(
spv::StorageClassOutput, output_fragment_sample_mask_,
id_vector_temp_);
spv::Id coverage_int =
builder_->createUnaryOp(spv::OpBitcast, type_int_, coverage_4x);
builder_->createStore(coverage_int, sample_mask_element);
// Discard if coverage is zero
spv::Id coverage_zero =
builder_->createBinOp(spv::OpIEqual, type_bool_, coverage_4x,
builder_->makeUintConstant(0));
SpirvBuilder::IfBuilder coverage_discard_if(
coverage_zero, spv::SelectionControlDontFlattenMask, *builder_);
builder_->createNoResultOp(spv::OpKill);
// OpKill terminates the block, so makeEndIf with false to not branch
coverage_discard_if.makeEndIf(false);
}
}
FSI_AlphaToMaskSample(true, 0, 0.75f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 1, 0.25f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 2, 0.5f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
FSI_AlphaToMaskSample(false, 3, 1.0f, threshold_offset, 1.0f / 16.0f,
coverage_4x);
builder_->createBranch(&block_msaa_mode_merge);
// 2x MSAA
builder_->setBuildPoint(&block_2x_msaa);
spv::Id coverage_2x = spv::NoResult;
{
// Using guest sample indices (0 and 1) for both FSI and non-FSI.
// FSI mode uses guest sample indices (0 and 1).
// FBO mode sample mapping depends on whether native 2x MSAA is supported:
// - Native 2x: host samples 1, 0 (reversed from guest order)
// - 2x as 4x: host samples 0, 3
if (edram_fragment_shader_interlock_) {
// FSI: Use guest indices 0, 1.
FSI_AlphaToMaskSample(true, 0, 0.5f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
FSI_AlphaToMaskSample(false, 1, 1.0f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
if (!edram_fragment_shader_interlock_) {
id_vector_temp_.clear();
id_vector_temp_.push_back(builder_->makeIntConstant(0));
spv::Id sample_mask_element = builder_->createAccessChain(
spv::StorageClassOutput, output_fragment_sample_mask_,
id_vector_temp_);
spv::Id coverage_int =
builder_->createUnaryOp(spv::OpBitcast, type_int_, coverage_2x);
builder_->createStore(coverage_int, sample_mask_element);
spv::Id coverage_zero =
builder_->createBinOp(spv::OpIEqual, type_bool_, coverage_2x,
builder_->makeUintConstant(0));
SpirvBuilder::IfBuilder coverage_discard_if(
coverage_zero, spv::SelectionControlDontFlattenMask, *builder_);
builder_->createNoResultOp(spv::OpKill);
coverage_discard_if.makeEndIf(false);
} else {
// FBO: Account for native 2x vs 2x-as-4x sample mapping.
if (native_2x_msaa_no_attachments_) {
// Native 2x: D3D10.1+ standard - top is 1, bottom is 0.
FSI_AlphaToMaskSample(true, 1, 0.5f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
FSI_AlphaToMaskSample(false, 0, 1.0f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
} else {
// 2x as 4x: Use samples 0 and 3.
FSI_AlphaToMaskSample(true, 0, 0.5f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
FSI_AlphaToMaskSample(false, 3, 1.0f, threshold_offset, 1.0f / 8.0f,
coverage_2x);
}
}
builder_->createBranch(&block_msaa_mode_merge);
builder_->setBuildPoint(&block_msaa_mode_merge);
// Merge coverage from 4x and 2x MSAA paths using PHI.
id_vector_temp_.clear();
id_vector_temp_.reserve(2 * 2);
id_vector_temp_.push_back(coverage_4x);
id_vector_temp_.push_back(block_4x_msaa.getId());
id_vector_temp_.push_back(coverage_2x);
id_vector_temp_.push_back(block_2x_msaa.getId());
spv::Id coverage_msaa_enabled =
builder_->createOp(spv::OpPhi, type_uint_, id_vector_temp_);
builder_->createBranch(&block_msaa_merge);
// MSAA disabled - single sample
builder_->setBuildPoint(&block_msaa_disabled);
spv::Id coverage_1x = spv::NoResult;
{
FSI_AlphaToMaskSample(true, 0, 1.0f, threshold_offset, 1.0f / 4.0f,
coverage_1x);
if (!edram_fragment_shader_interlock_) {
id_vector_temp_.clear();
id_vector_temp_.push_back(builder_->makeIntConstant(0));
spv::Id sample_mask_element = builder_->createAccessChain(
spv::StorageClassOutput, output_fragment_sample_mask_,
id_vector_temp_);
spv::Id coverage_int =
builder_->createUnaryOp(spv::OpBitcast, type_int_, coverage_1x);
builder_->createStore(coverage_int, sample_mask_element);
spv::Id coverage_zero =
builder_->createBinOp(spv::OpIEqual, type_bool_, coverage_1x,
builder_->makeUintConstant(0));
SpirvBuilder::IfBuilder coverage_discard_if(
coverage_zero, spv::SelectionControlDontFlattenMask, *builder_);
builder_->createNoResultOp(spv::OpKill);
coverage_discard_if.makeEndIf(false);
}
}
FSI_AlphaToMaskSample(true, 0, 1.0f, threshold_offset, 1.0f / 4.0f,
coverage_1x);
builder_->createBranch(&block_msaa_merge);
builder_->setBuildPoint(&block_msaa_merge);
// Merge coverage from MSAA enabled and disabled paths using PHI.
id_vector_temp_.clear();
id_vector_temp_.reserve(2 * 2);
id_vector_temp_.push_back(coverage_msaa_enabled);
id_vector_temp_.push_back(block_msaa_mode_merge.getId());
id_vector_temp_.push_back(coverage_1x);
id_vector_temp_.push_back(block_msaa_disabled.getId());
spv::Id coverage_final =
builder_->createOp(spv::OpPhi, type_uint_, id_vector_temp_);
// Write coverage to gl_SampleMask and discard if zero (FBO mode only).
if (!edram_fragment_shader_interlock_) {
// Write to gl_SampleMask[0].
id_vector_temp_.clear();
id_vector_temp_.push_back(builder_->makeIntConstant(0));
spv::Id sample_mask_element = builder_->createAccessChain(
spv::StorageClassOutput, output_fragment_sample_mask_, id_vector_temp_);
spv::Id coverage_int =
builder_->createUnaryOp(spv::OpBitcast, type_int_, coverage_final);
builder_->createStore(coverage_int, sample_mask_element);
// Discard fragment if coverage is zero.
spv::Id coverage_zero =
builder_->createBinOp(spv::OpIEqual, type_bool_, coverage_final,
builder_->makeUintConstant(0));
SpirvBuilder::IfBuilder coverage_discard_if(
coverage_zero, spv::SelectionControlDontFlattenMask, *builder_);
builder_->createNoResultOp(spv::OpKill);
// OpKill terminates the block, so makeEndIf with false to not branch.
coverage_discard_if.makeEndIf(false);
}
builder_->createBranch(&block_alpha_to_mask_merge);
builder_->setBuildPoint(&block_alpha_to_mask_merge);