Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions appendices/VK_EXT_fragment_coverage_mask.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2026 The Khronos Group Inc.
//
// SPDX-License-Identifier: CC-BY-4.0

include::{generated}/meta/{refprefix}VK_EXT_fragment_coverage_mask.adoc[]

=== Other Extension Metadata

*Last Modified Date*::
2026-06-15
*Contributors*::
- Michal Krol, Broadcom
- Brian Paul, Broadcom
- Roland Scheidegger, Broadcom

=== Description

This extension adds support for the code:FragmentCoverageEXT capability
from the `SPV_EXT_fragment_coverage_mask` SPIR-V extension to Vulkan,
introducing a new fragment shader input built-in,
code:FragmentCoverageMaskEXT, that contains the complete set of samples
covered by a fragment regardless of the fragment shader's execution mode.

The existing code:SampleMask input is masked to only include the bits
corresponding to the sample(s) being processed by the current invocation
when the fragment shader executes in a per-sample shading mode.
This makes it impossible for the shader to determine the entire set of
samples covered by the fragment, which in turn makes it difficult to port
shaders that rely on the equivalent input coverage mask available in other
APIs such as D3D10.

The code:FragmentCoverageMaskEXT built-in always contains the full
coverage mask, equivalent to the input coverage mask available in those
APIs, where each set bit represents a sample covered by the fragment.

include::{generated}/interfaces/VK_EXT_fragment_coverage_mask.adoc[]

=== New SPIR-V Capabilities

* <<spirvenv-capabilities-table-FragmentCoverageEXT,
code:FragmentCoverageEXT>>

=== Version History

* Revision 1, 2026-06-15 (Michal Krol)
** Initial revision (promoted from `VK_MESA_fragment_coverage_mask`)
24 changes: 24 additions & 0 deletions chapters/features.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3098,6 +3098,30 @@ include::{generated}/validity/structs/VkPhysicalDeviceFragmentShaderInterlockFea
--
endif::VK_EXT_fragment_shader_interlock[]

ifdef::VK_EXT_fragment_coverage_mask[]
[open,refpage='VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT',desc='Structure describing the fragment coverage mask feature that can be supported by an implementation',type='structs']
--
The sname:VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT structure is
defined as:

include::{generated}/api/structs/VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT.adoc[]

This structure describes the following feature:

* pname:sType is a elink:VkStructureType value identifying this structure.
* pname:pNext is `NULL` or a pointer to a structure extending this
structure.
* [[features-fragmentCoverageMask]]
pname:fragmentCoverageMask indicates that the implementation supports
the code:FragmentCoverageEXT SPIR-V capability.

:refpage: VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT
include::{chapters}/features.adoc[tag=features]

include::{generated}/validity/structs/VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT.adoc[]
--
endif::VK_EXT_fragment_coverage_mask[]

ifdef::VK_NV_cooperative_matrix[]
[open,refpage='VkPhysicalDeviceCooperativeMatrixFeaturesNV',desc='Structure describing cooperative matrix features that can be supported by an implementation',type='structs']
--
Expand Down
36 changes: 36 additions & 0 deletions chapters/interfaces.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4835,6 +4835,42 @@ the processing of a fragment.
****
--

ifdef::VK_EXT_fragment_coverage_mask[]
[[interfaces-builtin-variables-fragmentcoveragemaskext]]
[open,refpage='FragmentCoverageMaskEXT',desc='Full coverage mask for a fragment',type='builtins']
--
:refpage: FragmentCoverageMaskEXT

code:FragmentCoverageMaskEXT::

Decorating a variable with the code:FragmentCoverageMaskEXT built-in
decoration will make any variable contain the complete set of samples
covered by the fragment, regardless of the fragment shader's execution
mode.
+
Unlike code:SampleMask, the value of code:FragmentCoverageMaskEXT is not
masked to the sample(s) being processed by the current invocation when
the fragment shader is executing in a
<<primsrast-sampleshading,per-sample shading>> mode.
+
A variable decorated with code:FragmentCoverageMaskEXT is an array of
integers.
Bits are mapped to samples in a manner where bit B of mask M
(`FragmentCoverageMaskEXT[M]`) corresponds to sample
[eq]#32 {times} M {plus} B#.

.Valid Usage
****
* The code:FragmentCoverageMaskEXT decoration must: be used only within
the code:Fragment {ExecutionModel}
* The variable decorated with code:FragmentCoverageMaskEXT must: be
declared using the code:Input {StorageClass}
* The variable decorated with code:FragmentCoverageMaskEXT must: be
declared as an array of 32-bit integer values
****
--
endif::VK_EXT_fragment_coverage_mask[]

[[interfaces-builtin-variables-sampleposition]]
[open,refpage='SamplePosition',desc='Position of a shaded sample',type='builtins']
--
Expand Down
113 changes: 113 additions & 0 deletions proposals/VK_EXT_fragment_coverage_mask.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2026 The Khronos Group Inc.
//
// SPDX-License-Identifier: CC-BY-4.0

= VK_EXT_fragment_coverage_mask
:toc: left
:docs: https://docs.vulkan.org/spec/latest/
:refpages: https://docs.vulkan.org/refpages/latest/refpages/source/
:sectnums:

This document describes a proposal for a new fragment shader input
built-in that exposes the complete coverage mask of a fragment, regardless
of the fragment shader's execution mode.


== Problem Statement

The existing +SampleMask+ input built-in is intended to provide the set
of samples that contribute to a fragment.
However, when a fragment shader executes in a per-sample shading mode -
e.g. when a fragment shader input is decorated with +SampleId+ or
+SamplePosition+ - +SampleMask+ is masked to only include the bits
corresponding to the specific sample(s) currently being processed by that
invocation.
In the case of full per-sample shading (one invocation per sample), this
results in only a single bit being set.
When a sample shading rate is specified (e.g. via Vulkan's
*minSampleShading* parameter), an invocation *may* process a subset of
samples, and +SampleMask+ will contain the corresponding subset of
bits.

This constrained behavior makes it impossible for the shader to
determine the entire set of samples covered by the fragment.
Specifically, it is impossible to directly map the functionality of the
input coverage mask in the D3D10 API, making it very difficult to port
some D3D10 shaders to SPIR-V.
Furthermore, when per-sample shading is triggered by a sample shading
rate rather than by the use of +SampleId+ or +SamplePosition+,
those built-in variables are not available, making it impossible to
derive the full coverage mask from +SampleMask+ alone.


== Solution Space

There are a few options for exposing the complete coverage mask:

. Change the semantics of +SampleMask+ to always contain the full
coverage mask.
This is not viable - it would silently break existing shaders that
rely on the current per-invocation behavior.
. Add an execution mode that toggles +SampleMask+ between the
current behavior and the new full-coverage behavior.
This is workable but conflates two semantically distinct values into
a single name, complicating shader code that needs both, and
introducing a global mode where a per-variable distinction would be
cleaner.
. Introduce a new built-in input that always contains the full
coverage mask, leaving +SampleMask+ unchanged.
Selected.

A new built-in is the cleanest mapping to the D3D10 input coverage mask,
keeps the per-sample semantics of +SampleMask+ intact, and lets a
shader access both values in the same invocation if needed.


== Proposal

=== New SPIR-V capability and built-in

A new SPIR-V capability, +FragmentCoverageEXT+, and a new fragment
shader input built-in, +FragmentCoverageMaskEXT+, are introduced by
the `SPV_EXT_fragment_coverage_mask` SPIR-V extension.

A variable in the +Input+ storage class decorated with
+FragmentCoverageMaskEXT+ contains the complete set of samples
covered by the fragment, regardless of the fragment shader's execution
mode.
Bits are mapped to samples in the same manner as +SampleMask+: bit B
of mask M corresponds to sample [eq]#32 {times} M {plus} B#.

Note that when +SampleId+ is available, fragment shaders using
+FragmentCoverageMaskEXT+ can recover the value of +SampleMask+
from +FragmentCoverageMaskEXT+ by computing a bitwise AND with a bit
set at position +SampleId+.

=== New Vulkan feature

[source,c]
----
typedef struct VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT {
VkStructureType sType;
void* pNext;
VkBool32 fragmentCoverageMask;
} VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT;
----

The *fragmentCoverageMask* feature indicates whether the
implementation supports the +FragmentCoverageEXT+ capability and the
+FragmentCoverageMaskEXT+ built-in in fragment shaders consumed by
the implementation.


== Issues

=== How does this extension interact with +PostDepthCoverage+?

*RESOLVED*: +FragmentCoverageMaskEXT+ effectively contains the union
of the +SampleMask+ values across all concurrent per-sample shader
invocations.
If the +PostDepthCoverage+ execution mode is declared (along with
+EarlyFragmentTests+), +FragmentCoverageMaskEXT+ reflects the
coverage after the early fragment tests are applied, mirroring the
behavior +SampleMask+ has under +PostDepthCoverage+.
20 changes: 17 additions & 3 deletions xml/vk.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5990,6 +5990,11 @@ typedef void* <name>MTLSharedEvent_id</name>;
<member><type>VkBool32</type> <name>fragmentShaderPixelInterlock</name></member>
<member><type>VkBool32</type> <name>fragmentShaderShadingRateInterlock</name></member>
</type>
<type category="struct" name="VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT" structextends="VkPhysicalDeviceFeatures2,VkDeviceCreateInfo">
<member values="VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_COVERAGE_MASK_FEATURES_EXT"><type>VkStructureType</type> <name>sType</name></member>
<member optional="true"><type>void</type>* <name>pNext</name></member>
<member><type>VkBool32</type> <name>fragmentCoverageMask</name></member>
</type>
<type category="struct" name="VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures" structextends="VkPhysicalDeviceFeatures2,VkDeviceCreateInfo">
<member values="VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES"><type>VkStructureType</type> <name>sType</name></member>
<member optional="true"><type>void</type>* <name>pNext</name></member>
Expand Down Expand Up @@ -32202,10 +32207,13 @@ endif::VK_KHR_internally_synchronized_queues[]
<feature name="cooperativeMatrixBlockLoads" struct="VkPhysicalDeviceCooperativeMatrix2FeaturesNV" />
</require>
</extension>
<extension name="VK_MESA_fragment_coverage_mask" number="691" type="device" author="MESA" contact="Michal Krol @mjkrol" supported="disabled">
<extension name="VK_EXT_fragment_coverage_mask" number="691" type="device" depends="VK_KHR_get_physical_device_properties2,VK_VERSION_1_1" author="EXT" contact="Michal Krol @mjkrol" supported="vulkan">
<require>
<enum value="0" name="VK_MESA_FRAGMENT_COVERAGE_MASK_SPEC_VERSION" />
<enum value="&quot;VK_MESA_fragment_coverage_mask&quot;" name="VK_MESA_FRAGMENT_COVERAGE_MASK_EXTENSION_NAME" />
<enum value="1" name="VK_EXT_FRAGMENT_COVERAGE_MASK_SPEC_VERSION" />
<enum value="&quot;VK_EXT_fragment_coverage_mask&quot;" name="VK_EXT_FRAGMENT_COVERAGE_MASK_EXTENSION_NAME" />
<enum offset="0" extends="VkStructureType" name="VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_COVERAGE_MASK_FEATURES_EXT" />
<type name="VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT" />
<feature name="fragmentCoverageMask" struct="VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT" />
</require>
</extension>
<extension name="VK_EXT_extension_692" number="692" author="EXT" contact="Jeff Bolz @jeffbolznv" supported="disabled">
Expand Down Expand Up @@ -34031,6 +34039,9 @@ endif::VK_KHR_internally_synchronized_queues[]
<spirvextension name="SPV_EXT_fragment_shader_interlock">
<enable extension="VK_EXT_fragment_shader_interlock" />
</spirvextension>
<spirvextension name="SPV_EXT_fragment_coverage_mask">
<enable extension="VK_EXT_fragment_coverage_mask" />
</spirvextension>
<spirvextension name="SPV_EXT_demote_to_helper_invocation">
<enable version="VK_VERSION_1_3" />
<enable extension="VK_EXT_shader_demote_to_helper_invocation" />
Expand Down Expand Up @@ -34642,6 +34653,9 @@ endif::VK_KHR_internally_synchronized_queues[]
<enable struct="VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT" feature="fragmentShaderShadingRateInterlock" requires="VK_EXT_fragment_shader_interlock" />
<enable struct="VkPhysicalDeviceShadingRateImageFeaturesNV" feature="shadingRateImage" requires="VK_NV_shading_rate_image" />
</spirvcapability>
<spirvcapability name="FragmentCoverageEXT">
<enable struct="VkPhysicalDeviceFragmentCoverageMaskFeaturesEXT" feature="fragmentCoverageMask" requires="VK_EXT_fragment_coverage_mask" />
</spirvcapability>
<spirvcapability name="DemoteToHelperInvocation">
<enable struct="VkPhysicalDeviceVulkan13Features" feature="shaderDemoteToHelperInvocation" requires="VK_VERSION_1_3,VK_EXT_shader_demote_to_helper_invocation" />
<enable struct="VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT" feature="shaderDemoteToHelperInvocation" requires="VK_EXT_shader_demote_to_helper_invocation" />
Expand Down
Loading