diff --git a/src/freedreno/vulkan/meson.build b/src/freedreno/vulkan/meson.build index 4495cbe0495..d8c17e6df8d 100644 --- a/src/freedreno/vulkan/meson.build +++ b/src/freedreno/vulkan/meson.build @@ -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', diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc index f260e794d19..ba66d092496 100644 --- a/src/freedreno/vulkan/tu_device.cc +++ b/src/freedreno/vulkan/tu_device.cc @@ -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; diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h index 592519e97b7..9b58475ba0d 100644 --- a/src/freedreno/vulkan/tu_device.h +++ b/src/freedreno/vulkan/tu_device.h @@ -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) diff --git a/src/freedreno/vulkan/tu_nir_lower_demote_samples.cc b/src/freedreno/vulkan/tu_nir_lower_demote_samples.cc new file mode 100644 index 00000000000..4f6e470aa9f --- /dev/null +++ b/src/freedreno/vulkan/tu_nir_lower_demote_samples.cc @@ -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); +} + diff --git a/src/freedreno/vulkan/tu_pipeline.cc b/src/freedreno/vulkan/tu_pipeline.cc index f3f7507e2da..b20b4bf4cff 100644 --- a/src/freedreno/vulkan/tu_pipeline.cc +++ b/src/freedreno/vulkan/tu_pipeline.cc @@ -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++) { diff --git a/src/freedreno/vulkan/tu_shader.cc b/src/freedreno/vulkan/tu_shader.cc index b04f6f919b5..c06619b56ff 100644 --- a/src/freedreno/vulkan/tu_shader.cc +++ b/src/freedreno/vulkan/tu_shader.cc @@ -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, diff --git a/src/freedreno/vulkan/tu_shader.h b/src/freedreno/vulkan/tu_shader.h index 23b653c4e9c..02f23aa1b21 100644 --- a/src/freedreno/vulkan/tu_shader.h +++ b/src/freedreno/vulkan/tu_shader.h @@ -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, diff --git a/src/util/00-mesa-defaults.conf b/src/util/00-mesa-defaults.conf index 51453f6aaaa..fe39dc305eb 100644 --- a/src/util/00-mesa-defaults.conf +++ b/src/util/00-mesa-defaults.conf @@ -1388,6 +1388,16 @@ TODO: document the other workarounds. default, the latter is used through this option. -->