tu: Enable alpha-to-coverage emulation

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/39335>
This commit is contained in:
Connor Abbott 2025-12-31 15:04:54 -05:00 committed by Marge Bot
parent ec37fed52b
commit e1fd5a4a57
9 changed files with 150 additions and 0 deletions

View file

@ -38,6 +38,7 @@ libtu_files = files(
'tu_image.cc',
'tu_knl.cc',
'tu_lrz.cc',
'tu_nir_lower_demote_samples.cc',
'tu_nir_lower_multiview.cc',
'tu_nir_lower_ray_query.cc',
'tu_pass.cc',

View file

@ -1830,6 +1830,7 @@ static const driOptionDescription tu_dri_options[] = {
DRI_CONF_TU_USE_TEX_COORD_ROUND_NEAREST_EVEN_MODE(false)
DRI_CONF_TU_IGNORE_FRAG_DEPTH_DIRECTION(false)
DRI_CONF_TU_ENABLE_SOFTFLOAT32(false)
DRI_CONF_TU_EMULATE_ALPHA_TO_COVERAGE(false)
DRI_CONF_SECTION_END
};
@ -1860,6 +1861,8 @@ tu_init_dri_options(struct tu_instance *instance)
driQueryOptionb(&instance->dri_options, "tu_ignore_frag_depth_direction");
instance->enable_softfloat32 =
driQueryOptionb(&instance->dri_options, "tu_enable_softfloat32");
instance->emulate_alpha_to_coverage =
driQueryOptionb(&instance->dri_options, "tu_emulate_alpha_to_coverage");
}
static uint32_t instance_count = 0;

View file

@ -229,6 +229,12 @@ struct tu_instance
* However we don't want native Vulkan apps using this.
*/
bool enable_softfloat32;
/* The hardware implementation of alpha-to-coverage gives visually poor
* results for many games. Set this option to enable it in the shader
* instead.
*/
bool emulate_alpha_to_coverage;
};
VK_DEFINE_HANDLE_CASTS(tu_instance, vk.base, VkInstance,
VK_OBJECT_TYPE_INSTANCE)

View file

@ -0,0 +1,88 @@
/*
* Copyright © 2025 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "tu_shader.h"
#include "nir/nir_builder.h"
/* Lower demote_samples to a write to gl_SampleMask. Take into account
* existing writes to gl_SampleMask.
*/
bool
tu_nir_lower_demote_samples(nir_shader *nir)
{
nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
nir_variable *sample_mask = NULL;
nir_builder _b = nir_builder_create(entrypoint), *b = &_b;
bool progress = false;
uint32_t sample_mask_driver_location = ~0;
nir_foreach_block (block, entrypoint) {
nir_foreach_instr_safe (instr, block) {
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic == nir_intrinsic_store_output) {
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
if (sem.location != FRAG_RESULT_SAMPLE_MASK)
continue;
} else if (intrin->intrinsic != nir_intrinsic_demote_samples) {
continue;
}
if (!sample_mask) {
sample_mask = nir_local_variable_create(entrypoint,
glsl_uint_type(),
"sample_mask");
/* Initialize sample_mask to ~0 (all samples) */
b->cursor = nir_before_impl(entrypoint);
nir_store_var(b, sample_mask, nir_imm_int(b, ~0), 0x1);
}
b->cursor = nir_before_instr(instr);
if (intrin->intrinsic == nir_intrinsic_demote_samples) {
/* For each demote_samples, remove the samples from sample_mask */
nir_def *to_demote = intrin->src[0].ssa;
nir_store_var(b, sample_mask,
nir_iand(b, nir_load_var(b, sample_mask),
nir_inot(b, to_demote)), 0x1);
} else if (intrin->intrinsic == nir_intrinsic_store_output) {
/* If there is an existing write to SampleMask, AND it with
* sample_mask and remove it.
*/
nir_def *old_mask = intrin->src[0].ssa;
nir_store_var(b, sample_mask,
nir_iand(b, nir_load_var(b, sample_mask),
old_mask), 0x1);
sample_mask_driver_location = nir_intrinsic_base(intrin);
}
nir_instr_remove(instr);
progress = true;
}
}
if (progress) {
if (sample_mask_driver_location == ~0)
sample_mask_driver_location = nir->num_outputs++;
/* Finally, at the end insert a write to SampleMask. */
b->cursor = nir_after_impl(entrypoint);
nir_store_output(b, nir_load_var(b, sample_mask),
nir_imm_int(b, 0),
.base = sample_mask_driver_location,
.io_semantics = {
.location = FRAG_RESULT_SAMPLE_MASK
});
}
return nir_progress(progress, entrypoint, nir_metadata_control_flow);
}

View file

@ -1850,6 +1850,18 @@ tu_pipeline_builder_compile_shaders(struct tu_pipeline_builder *builder,
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) {
keys[MESA_SHADER_FRAGMENT].custom_resolve =
builder->graphics_state.rp->custom_resolve;
if (builder->device->physical_device->instance->emulate_alpha_to_coverage) {
keys[MESA_SHADER_FRAGMENT].emulate_alpha_to_coverage = true;
/* Don't emulate if we know it won't be enabled. */
if (builder->graphics_state.ms &&
!BITSET_TEST(builder->graphics_state.dynamic,
MESA_VK_DYNAMIC_MS_ALPHA_TO_COVERAGE_ENABLE)) {
if (!builder->graphics_state.ms->alpha_to_coverage_enable)
keys[MESA_SHADER_FRAGMENT].emulate_alpha_to_coverage = false;
}
}
}
if (builder->create_flags &
@ -3278,6 +3290,8 @@ tu6_emit_blend(struct tu_cs *cs,
{
bool rop_reads_dst = cb->logic_op_enable && tu_logic_op_reads_dst((VkLogicOp)cb->logic_op);
enum a3xx_rop_code rop = tu6_rop((VkLogicOp)cb->logic_op);
if (cs->device->physical_device->instance->emulate_alpha_to_coverage)
alpha_to_coverage_enable = false;
uint32_t blend_enable_mask = 0;
for (unsigned i = 0; i < cb->attachment_count; i++) {

View file

@ -1533,6 +1533,20 @@ tu_nir_lower_view_to_zero(nir_shader *shader)
lower_view_to_zero, NULL);
}
static bool
lower_alpha_to_coverage(nir_shader *shader)
{
nir_builder b = nir_builder_create(nir_shader_get_entrypoint(shader));
b.cursor = nir_before_cf_list(&nir_shader_get_entrypoint(shader)->body);
nir_def *a2c_enabled =
nir_ine_imm(&b, nir_load_alpha_to_coverage_enable_ir3(&b), 0);
NIR_PASS(_, shader, nir_lower_alpha_to_coverage, false, a2c_enabled);
NIR_PASS(_, shader, tu_nir_lower_demote_samples);
return true;
}
static void
shared_type_info(const struct glsl_type *type, unsigned *size, unsigned *align)
{
@ -3073,6 +3087,9 @@ tu_shader_create(struct tu_device *dev,
ir3_nir_lower_io(nir);
if (key->emulate_alpha_to_coverage)
lower_alpha_to_coverage(nir);
struct ir3_const_allocations const_allocs = {};
NIR_PASS(_, nir, tu_lower_io, dev, shader, layout,
key->read_only_input_attachments, key->dynamic_renderpass,

View file

@ -129,6 +129,7 @@ struct tu_shader_key {
bool robust_uniform_access2;
bool lower_view_index_to_device_index;
bool custom_resolve;
bool emulate_alpha_to_coverage;
enum ir3_wavesize_option api_wavesize, real_wavesize;
};
@ -143,6 +144,9 @@ tu_nir_lower_multiview(nir_shader *nir, uint32_t mask, struct tu_device *dev);
bool
tu_nir_lower_ray_queries(nir_shader *nir);
bool
tu_nir_lower_demote_samples(nir_shader *nir);
nir_shader *
tu_spirv_to_nir(struct tu_device *dev,
void *mem_ctx,

View file

@ -1388,6 +1388,16 @@ TODO: document the other workarounds.
default, the latter is used through this option.
-->
<option name="tu_use_tex_coord_round_nearest_even_mode" value="true" />
<!--
The hardware algorithm for alpha-to-coverage seems to
consistently produce bad results compared to other vendors.
Always enabling emulation would potentially regress performance
when it is dynamically enabled and off, but both both DXVK and
vkd3d-proton are known to compile pipelines with
alpha-to-coverage statically disabled.
-->
<option name="tu_emulate_alpha_to_coverage" value="true" />
</engine>
<engine engine_name_match="vkd3d">
<option name="tu_enable_softfloat32" value="true" />
@ -1406,6 +1416,9 @@ TODO: document the other workarounds.
<application name="Creed: Rise to Glory" executable="Creed-Win64-Shipping.exe">
<option name="tu_ignore_frag_depth_direction" value="true" />
</application>
<application name="Half-Life: Alyx" application_name_match="hlvr">
<option name="tu_emulate_alpha_to_coverage" value="true" />
</application>
</device>
<device driver="asahi">

View file

@ -688,6 +688,10 @@
DRI_CONF_OPT_B(tu_enable_softfloat32, def, \
"Enable softfloat emulation for float32 denormals")
#define DRI_CONF_TU_EMULATE_ALPHA_TO_COVERAGE(def) \
DRI_CONF_OPT_B(tu_emulate_alpha_to_coverage, def, \
"Enable emulation of alpha-to-coverage")
/**
* \brief Honeykrisp specific configuration options
*/