mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
Add mesh shading info to naga IR (#8104)
Co-authored-by: Jim Blandy <jimb@red-bean.com> Co-authored-by: SupaMaggie70Incorporated <85136135+SupaMaggie70Incorporated@users.noreply.github.com>
This commit is contained in:
parent
e7fcb94888
commit
bf9f7524ec
@ -2,13 +2,71 @@
|
||||
|
||||
🧪Experimental🧪
|
||||
|
||||
`wgpu` supports an experimental version of mesh shading. Currently `naga` has no support for mesh shaders beyond recognizing the additional shader stages.
|
||||
`wgpu` supports an experimental version of mesh shading when `Features::EXPERIMENTAL_MESH_SHADER` is enabled.
|
||||
Currently `naga` has no support for parsing or writing mesh shaders.
|
||||
For this reason, all shaders must be created with `Device::create_shader_module_passthrough`.
|
||||
|
||||
**Note**: The features documented here may have major bugs in them and are expected to be subject
|
||||
to breaking changes, suggestions for the API exposed by this should be posted on [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197).
|
||||
|
||||
***This is not*** a thorough explanation of mesh shading and how it works. Those wishing to understand mesh shading more broadly should look elsewhere first.
|
||||
## Mesh shaders overview
|
||||
|
||||
### What are mesh shaders?
|
||||
|
||||
Mesh shaders are a new kind of rasterization pipeline intended to address some of the shortfalls with the vertex shader pipeline. The core idea of mesh shaders is that the GPU decides how to render the many small parts of a scene instead of the CPU issuing a draw call for every small part or issuing an inefficient monolithic draw call for a large part of the scene.
|
||||
|
||||
Mesh shaders are specifically designed to be used with **meshlet rendering**, a technique where every object is split into many subobjects called meshlets that are each rendered with their own parameters. With the standard vertex pipeline, each draw call specifies an exact number of primitives to render and the same parameters for all vertex shaders on an entire object (or even multiple objects). This doesn't leave room for different LODs for different parts of an object, for example a closer part having more detail, nor does it allow culling smaller sections (or primitives) of objects. With mesh shaders, each task workgroup might get assigned to a single object. It can then analyze the different meshlets(sections) of that object, determine which are visible and should actually be rendered, and for those meshlets determine what LOD to use based on the distance from the camera. It can then dispatch a mesh workgroup for each meshlet, with each mesh workgroup then reading the data for that specific LOD of its meshlet, determining which and how many vertices and primitives to output, determining which remaining primitives need to be culled, and passing the resulting primitives to the rasterizer.
|
||||
|
||||
Mesh shaders are most effective in scenes with many polygons. They can allow skipping processing of entire groups of primitives that are facing away from the camera or otherwise occluded, which reduces the number of primitives that need to be processed by more than half in most cases, and they can reduce the number of primitives that need to be processed for more distant objects. Scenes that are not bottlenecked by geometry (perhaps instead by fragment processing or post processing) will not see much benefit from using them.
|
||||
|
||||
Mesh shaders were first shown off in [NVIDIA's asteroids demo](https://www.youtube.com/watch?v=CRfZYJ_sk5E). Now, they form the basis for [Unreal Engine's Nanite](https://www.unrealengine.com/en-US/blog/unreal-engine-5-is-now-available-in-preview#Nanite).
|
||||
|
||||
### Mesh shader pipeline
|
||||
|
||||
With the current pipeline set to a mesh pipeline, a draw command like
|
||||
`render_pass.draw_mesh_tasks(x, y, z)` takes the following steps:
|
||||
|
||||
* If the pipeline has a task shader stage:
|
||||
|
||||
* Dispatch a grid of task shader workgroups, where `x`, `y`, and `z` give
|
||||
the number of workgroups along each axis of the grid. Each task shader
|
||||
workgroup produces a mesh shader workgroup grid size `(mx, my, mz)` and a
|
||||
task payload value `mp`.
|
||||
|
||||
* For each task shader workgroup, dispatch a grid of mesh shader workgroups,
|
||||
where `mx`, `my`, and `mz` give the number of workgroups along each axis
|
||||
of the grid. Pass `mp` to each of these workgroup's mesh shader
|
||||
invocations.
|
||||
|
||||
* Alternatively, if the pipeline does not have a task shader stage:
|
||||
|
||||
* Dispatch a single grid of mesh shader workgroups, where `x`, `y`, and `z`
|
||||
give the number of workgroups along each axis of the grid. These mesh
|
||||
shaders receive no task payload value.
|
||||
|
||||
* Each mesh shader workgroup produces a list of output vertices, and a list of
|
||||
primitives built from those vertices. The workgroup can supply per-primitive
|
||||
values as well, if needed. Each primitive selects its vertices by index, like
|
||||
an indexed draw call, from among the vertices generated by this workgroup.
|
||||
|
||||
Unlike a grid of ordinary compute shader workgroups collaborating to build
|
||||
vertex and index data in common storage buffers, the vertices and primitives
|
||||
produced by a mesh shader workgroup are entirely private to that workgroup,
|
||||
and are not accessible by other workgroups.
|
||||
|
||||
* Primitives produced by a mesh shader workgroup can have a culling flag. If a
|
||||
primitive's culling flag is false, it is skipped during rasterization.
|
||||
|
||||
* The primitives produced by all mesh shader workgroups are then rasterized in
|
||||
the usual way, with each fragment shader invocation handling one pixel.
|
||||
|
||||
Attributes from the vertices produced by the mesh shader workgroup are
|
||||
provided to the fragment shader with interpolation applied as appropriate.
|
||||
|
||||
If the mesh shader workgroup supplied per-primitive values, these are
|
||||
available to each primitive's fragment shader invocations. Per-primitive
|
||||
values are never interpolated; fragment shaders simply receive the values
|
||||
the mesh shader workgroup associated with their primitive.
|
||||
|
||||
## `wgpu` API
|
||||
|
||||
@ -75,36 +133,63 @@ Using any of these features in a `wgsl` program will require adding the `enable
|
||||
Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-specific functionality, such as subgroup operations.
|
||||
|
||||
### Task shader
|
||||
This shader stage can be selected by marking a function with `@task`. Task shaders must return a `vec3<u32>` as their output type. Similar to compute shaders, task shaders run in a workgroup. The output must be uniform across all threads in a workgroup.
|
||||
|
||||
The output of this determines how many workgroups of mesh shaders will be dispatched. Once dispatched, global id variables will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup.
|
||||
A function with the `@task` attribute is a **task shader entry point**. A mesh shader pipeline may optionally specify a task shader entry point, and if it does, mesh draw commands using that pipeline dispatch a **task shader grid** of workgroups running the task shader entry point. Like compute shader dispatches, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the task shader grid as the number of workgroups along each of the grid's three axes.
|
||||
|
||||
If task shaders are marked with `@payload(someVar)`, where `someVar` is global variable declared like `var<workgroup> someVar: <type>`, task shaders may write to `someVar`. This payload is passed to the mesh shader workgroup that is invoked. The mesh shader can skip declaring `@payload` to ignore this input.
|
||||
A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point.
|
||||
|
||||
A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup.
|
||||
|
||||
A task shader entry point must return a `vec3<u32>` value. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section.
|
||||
|
||||
Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid;
|
||||
and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload.
|
||||
|
||||
### Mesh shader
|
||||
This shader stage can be selected by marking a function with `@mesh`. Mesh shaders must not return anything.
|
||||
|
||||
Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, mesh shaders cannot write to this workgroup memory. Declaring `@payload` in a pipeline with no task shader, in a pipeline with a task shader that doesn't declare `@payload`, or in a task shader with an `@payload` that is statically sized and smaller than the mesh shader payload is illegal.
|
||||
A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh shaders must not return anything.
|
||||
|
||||
Mesh shaders must be marked with `@vertex_output(OutputType, numOutputs)`, where `numOutputs` is the maximum number of vertices to be output by a mesh shader, and `OutputType` is the data associated with vertices, similar to a standard vertex shader output.
|
||||
Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes.
|
||||
|
||||
Mesh shaders must also be marked with `@primitive_output(OutputType, numOutputs)`, which is similar to `@vertex_output` except it describes the primitive outputs.
|
||||
If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable, and the sizes must match. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid.
|
||||
|
||||
### Mesh shader outputs
|
||||
If the mesh shader pipeline does not have a task shader entry point, then the mesh shader entry point must not have any `@payload` attribute.
|
||||
|
||||
Primitive outputs from mesh shaders have some additional builtins they can set. These include `@builtin(cull_primitive)`, which must be a boolean value. If this is set to true, then the primitive is skipped during rendering.
|
||||
A mesh shader entry point must have the following attributes:
|
||||
|
||||
Mesh shader primitive outputs must also specify exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`. This determines the output topology of the mesh shader, and must match the output topology of the pipeline descriptor the mesh shader is used with. These must be of type `vec3<u32>`, `vec2<u32>`, and `u32` respectively. When setting this, each of the indices must be less than the number of vertices declared in `setMeshOutputs`.
|
||||
- `@workgroup_size`: this has the same meaning as when it appears on a compute shader entry point.
|
||||
|
||||
Additionally, the `@location` attributes from the vertex and primitive outputs can't overlap.
|
||||
- `@vertex_output(V, NV)`: This indicates that the mesh shader workgroup will generate at most `NV` vertex values, each of type `V`.
|
||||
|
||||
Before setting any vertices or indices, or exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly these numbers of vertices and primitives.
|
||||
- `@primitive_output(P, NP)`: This indicates that the mesh shader workgroup will generate at most `NP` primitives, each of type `P`.
|
||||
|
||||
The mesh shader can write to vertices using the `setVertex(idx: u32, vertex: VertexOutput)` where `VertexOutput` is replaced with the vertex type declared in `@vertex_output`, and `idx` is the index of the vertex to write. Similarly, the mesh shader can write to vertices using `setPrimitive(idx: u32, primitive: PrimitiveOutput)`. These can be written to multiple times, however unsynchronized writes are undefined behavior. The primitives and indices are shared across the entire mesh shader workgroup.
|
||||
Each mesh shader entry point invocation must call the `setMeshOutputs(numVertices: u32, numPrimitives: u32)` builtin function at least once. The values passed by each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) determine how many vertices (values of type `V`) and primitives (values of type `P`) the workgroup must produce. The user can still write past these indices, but they won't be used in the output.
|
||||
|
||||
The `numVertices` and `numPrimitives` arguments must be no greater than `NV` and `NP` from the `@vertex_output` and `@primitive_output` attributes.
|
||||
|
||||
To produce vertex data, the workgroup as a whole must make `numVertices` calls to the `setVertex(i: u32, vertex: V)` builtin function. This establishes `vertex` as the value of the `i`'th vertex, where `i` is less than the maximum number of output vertices in the `@vertex_output` attribute. `V` is the type given in the `@vertex_output` attribute. `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on.
|
||||
|
||||
To produce primitives, the workgroup as a whole must make `numPrimitives` calls to the `setPrimitive(i: u32, primitive: P)` builtin function. This establishes `primitive` as the value of the `i`'th primitive, where `i` is less than the maximum number of output primitives in the `@primitive_output` attribute. `P` is the type given in the `@primitive_output` attribute. `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. The following `@builtin` attributes are allowed:
|
||||
|
||||
- `triangle_indices`, `line_indices`, or `point_index`: The annotated member must be of type `vec3<u32>`, `vec2<u32>`, or `u32`.
|
||||
|
||||
The member's components are indices (or, its value is an index) into the list of vertices generated by this workgroup, identifying the vertices of the primitive to be drawn. These indices must be less than the value of `numVertices` passed to `setMeshOutputs`.
|
||||
|
||||
The type `P` must contain exactly one member with one of these attributes, determining what sort of primitives the mesh shader generates.
|
||||
|
||||
- `cull_primitive`: The annotated member must be of type `bool`. If it is true, then the primitive is skipped during rendering.
|
||||
|
||||
Every member of `P` with a `@location` attribute must either have a `@per_primitive` attribute, or be part of a struct type that appears in the primitive data as a struct member with the `@per_primitive` attribute.
|
||||
|
||||
The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader.
|
||||
|
||||
It is possible to write to the same vertex or primitive index repeatedly. Since the implicit arrays written by `setVertex` and `setPrimitive` are shared by the workgroup, data races on writes to the same index for a given type are undefined behavior.
|
||||
|
||||
### Fragment shader
|
||||
|
||||
Fragment shaders may now be passed the primitive info from a mesh shader the same was as they are passed vertex inputs, for example `fn fs_main(vertex: VertexOutput, primitive: PrimitiveOutput)`. The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline.
|
||||
Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` attribute can be applied to a value directly, such as `@per_primitive @location(1) value: vec4<f32>`, to a struct such as `@per_primitive primitive_input: PrimitiveInput` where `PrimitiveInput` is a struct containing fields decorated with `@location` and `@builtin`, or to members of a struct that are themselves decorated with `@location` or `@builtin`.
|
||||
|
||||
The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. Using `@per_primitive` also requires enabling the mesh shader extension. Additionally, the locations of vertex and primitive input cannot overlap.
|
||||
|
||||
### Full example
|
||||
|
||||
@ -114,9 +199,9 @@ The following is a full example of WGSL shaders that could be used to create a m
|
||||
enable mesh_shading;
|
||||
|
||||
const positions = array(
|
||||
vec4(0.,-1.,0.,1.),
|
||||
vec4(-1.,1.,0.,1.),
|
||||
vec4(1.,1.,0.,1.)
|
||||
vec4(0.,1.,0.,1.),
|
||||
vec4(-1.,-1.,0.,1.),
|
||||
vec4(1.,-1.,0.,1.)
|
||||
);
|
||||
const colors = array(
|
||||
vec4(0.,1.,0.,1.),
|
||||
@ -127,7 +212,7 @@ struct TaskPayload {
|
||||
colorMask: vec4<f32>,
|
||||
visible: bool,
|
||||
}
|
||||
var<workgroup> taskPayload: TaskPayload;
|
||||
var<task_payload> taskPayload: TaskPayload;
|
||||
var<workgroup> workgroupData: f32;
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@ -136,14 +221,12 @@ struct VertexOutput {
|
||||
struct PrimitiveOutput {
|
||||
@builtin(triangle_indices) index: vec3<u32>,
|
||||
@builtin(cull_primitive) cull: bool,
|
||||
@location(1) colorMask: vec4<f32>,
|
||||
@per_primitive @location(1) colorMask: vec4<f32>,
|
||||
}
|
||||
struct PrimitiveInput {
|
||||
@location(1) colorMask: vec4<f32>,
|
||||
@per_primitive @location(1) colorMask: vec4<f32>,
|
||||
}
|
||||
fn test_function(input: u32) {
|
||||
|
||||
}
|
||||
@task
|
||||
@payload(taskPayload)
|
||||
@workgroup_size(1)
|
||||
@ -162,8 +245,6 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati
|
||||
workgroupData = 2.0;
|
||||
var v: VertexOutput;
|
||||
|
||||
test_function(1);
|
||||
|
||||
v.position = positions[0];
|
||||
v.color = colors[0] * taskPayload.colorMask;
|
||||
setVertex(0, v);
|
||||
@ -186,4 +267,4 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati
|
||||
fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4<f32> {
|
||||
return vertex.color * primitive.colorMask;
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@ -64,6 +64,12 @@ struct Args {
|
||||
#[argh(option)]
|
||||
shader_model: Option<ShaderModelArg>,
|
||||
|
||||
/// the SPIR-V version to use if targeting SPIR-V
|
||||
///
|
||||
/// For example, 1.0, 1.4, etc
|
||||
#[argh(option)]
|
||||
spirv_version: Option<SpirvVersionArg>,
|
||||
|
||||
/// the shader stage, for example 'frag', 'vert', or 'compute'.
|
||||
/// if the shader stage is unspecified it will be derived from
|
||||
/// the file extension.
|
||||
@ -189,6 +195,22 @@ impl FromStr for ShaderModelArg {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SpirvVersionArg(u8, u8);
|
||||
|
||||
impl FromStr for SpirvVersionArg {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let dot = s
|
||||
.find(".")
|
||||
.ok_or_else(|| "Missing dot separator".to_owned())?;
|
||||
let major = s[..dot].parse::<u8>().map_err(|e| e.to_string())?;
|
||||
let minor = s[dot + 1..].parse::<u8>().map_err(|e| e.to_string())?;
|
||||
Ok(Self(major, minor))
|
||||
}
|
||||
}
|
||||
|
||||
/// Newtype so we can implement [`FromStr`] for `ShaderSource`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct ShaderStage(naga::ShaderStage);
|
||||
@ -465,6 +487,9 @@ fn run() -> anyhow::Result<()> {
|
||||
if let Some(ref version) = args.metal_version {
|
||||
params.msl.lang_version = version.0;
|
||||
}
|
||||
if let Some(ref version) = args.spirv_version {
|
||||
params.spv_out.lang_version = (version.0, version.1);
|
||||
}
|
||||
params.keep_coordinate_space = args.keep_coordinate_space;
|
||||
|
||||
params.dot.cfg_only = args.dot_cfg_only;
|
||||
|
||||
@ -307,6 +307,25 @@ impl StatementGraph {
|
||||
crate::RayQueryFunction::Terminate => "RayQueryTerminate",
|
||||
}
|
||||
}
|
||||
S::MeshFunction(crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
}) => {
|
||||
self.dependencies.push((id, vertex_count, "vertex_count"));
|
||||
self.dependencies
|
||||
.push((id, primitive_count, "primitive_count"));
|
||||
"SetMeshOutputs"
|
||||
}
|
||||
S::MeshFunction(crate::MeshFunction::SetVertex { index, value }) => {
|
||||
self.dependencies.push((id, index, "index"));
|
||||
self.dependencies.push((id, value, "value"));
|
||||
"SetVertex"
|
||||
}
|
||||
S::MeshFunction(crate::MeshFunction::SetPrimitive { index, value }) => {
|
||||
self.dependencies.push((id, index, "index"));
|
||||
self.dependencies.push((id, value, "value"));
|
||||
"SetPrimitive"
|
||||
}
|
||||
S::SubgroupBallot { result, predicate } => {
|
||||
if let Some(predicate) = predicate {
|
||||
self.dependencies.push((id, predicate, "predicate"));
|
||||
|
||||
@ -623,6 +623,7 @@ impl<W> Writer<'_, W> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: _,
|
||||
} => {
|
||||
if interpolation == Some(Interpolation::Linear) {
|
||||
self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
|
||||
|
||||
@ -139,7 +139,8 @@ impl crate::AddressSpace {
|
||||
| crate::AddressSpace::Uniform
|
||||
| crate::AddressSpace::Storage { .. }
|
||||
| crate::AddressSpace::Handle
|
||||
| crate::AddressSpace::PushConstant => false,
|
||||
| crate::AddressSpace::PushConstant
|
||||
| crate::AddressSpace::TaskPayload => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1300,6 +1301,9 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
crate::AddressSpace::Storage { .. } => {
|
||||
self.write_interface_block(handle, global)?;
|
||||
}
|
||||
crate::AddressSpace::TaskPayload => {
|
||||
self.write_interface_block(handle, global)?;
|
||||
}
|
||||
// A global variable in the `Function` address space is a
|
||||
// contradiction in terms.
|
||||
crate::AddressSpace::Function => unreachable!(),
|
||||
@ -1614,6 +1618,7 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: _,
|
||||
} => (location, interpolation, sampling, blend_src),
|
||||
crate::Binding::BuiltIn(built_in) => {
|
||||
match built_in {
|
||||
@ -1732,6 +1737,7 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src,
|
||||
per_primitive: false,
|
||||
},
|
||||
stage: self.entry_point.stage,
|
||||
options: VaryingOptions::from_writer_options(self.options, output),
|
||||
@ -1873,7 +1879,7 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
writeln!(self.out, ") {{")?;
|
||||
|
||||
if self.options.zero_initialize_workgroup_memory
|
||||
&& ctx.ty.is_compute_entry_point(self.module)
|
||||
&& ctx.ty.is_compute_like_entry_point(self.module)
|
||||
{
|
||||
self.write_workgroup_variables_initialization(&ctx)?;
|
||||
}
|
||||
@ -2669,6 +2675,11 @@ impl<'a, W: Write> Writer<'a, W> {
|
||||
self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)?
|
||||
}
|
||||
Statement::RayQuery { .. } => unreachable!(),
|
||||
Statement::MeshFunction(
|
||||
crate::MeshFunction::SetMeshOutputs { .. }
|
||||
| crate::MeshFunction::SetVertex { .. }
|
||||
| crate::MeshFunction::SetPrimitive { .. },
|
||||
) => unreachable!(),
|
||||
Statement::SubgroupBallot { result, predicate } => {
|
||||
write!(self.out, "{level}")?;
|
||||
let res_name = Baked(result).to_string();
|
||||
@ -5248,6 +5259,15 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s
|
||||
Bi::SubgroupId => "gl_SubgroupID",
|
||||
Bi::SubgroupSize => "gl_SubgroupSize",
|
||||
Bi::SubgroupInvocationId => "gl_SubgroupInvocationID",
|
||||
// mesh
|
||||
// TODO: figure out how to map these to glsl things as glsl treats them as arrays
|
||||
Bi::CullPrimitive
|
||||
| Bi::PointIndex
|
||||
| Bi::LineIndices
|
||||
| Bi::TriangleIndices
|
||||
| Bi::MeshTaskSize => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5263,6 +5283,7 @@ const fn glsl_storage_qualifier(space: crate::AddressSpace) -> Option<&'static s
|
||||
As::Handle => Some("uniform"),
|
||||
As::WorkGroup => Some("shared"),
|
||||
As::PushConstant => Some("uniform"),
|
||||
As::TaskPayload => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -184,6 +184,9 @@ impl crate::BuiltIn {
|
||||
Self::PointSize | Self::ViewIndex | Self::PointCoord | Self::DrawID => {
|
||||
return Err(Error::Custom(format!("Unsupported builtin {self:?}")))
|
||||
}
|
||||
Self::CullPrimitive => "SV_CullPrimitive",
|
||||
Self::PointIndex | Self::LineIndices | Self::TriangleIndices => unimplemented!(),
|
||||
Self::MeshTaskSize => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -507,7 +507,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
|
||||
self.write_wrapped_functions(module, &ctx)?;
|
||||
|
||||
if ep.stage == ShaderStage::Compute {
|
||||
if ep.stage.compute_like() {
|
||||
// HLSL is calling workgroup size "num threads"
|
||||
let num_threads = ep.workgroup_size;
|
||||
writeln!(
|
||||
@ -967,6 +967,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
self.write_type(module, global.ty)?;
|
||||
""
|
||||
}
|
||||
crate::AddressSpace::TaskPayload => unimplemented!(),
|
||||
crate::AddressSpace::Uniform => {
|
||||
// constant buffer declarations are expected to be inlined, e.g.
|
||||
// `cbuffer foo: register(b0) { field1: type1; }`
|
||||
@ -1764,7 +1765,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
module: &Module,
|
||||
) -> bool {
|
||||
self.options.zero_initialize_workgroup_memory
|
||||
&& func_ctx.ty.is_compute_entry_point(module)
|
||||
&& func_ctx.ty.is_compute_like_entry_point(module)
|
||||
&& module.global_variables.iter().any(|(handle, var)| {
|
||||
!func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup
|
||||
})
|
||||
@ -2599,6 +2600,19 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
writeln!(self.out, ".Abort();")?;
|
||||
}
|
||||
},
|
||||
Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
}) => {
|
||||
write!(self.out, "{level}SetMeshOutputCounts(")?;
|
||||
self.write_expr(module, vertex_count, func_ctx)?;
|
||||
write!(self.out, ", ")?;
|
||||
self.write_expr(module, primitive_count, func_ctx)?;
|
||||
write!(self.out, ");")?;
|
||||
}
|
||||
Statement::MeshFunction(
|
||||
crate::MeshFunction::SetVertex { .. } | crate::MeshFunction::SetPrimitive { .. },
|
||||
) => unimplemented!(),
|
||||
Statement::SubgroupBallot { result, predicate } => {
|
||||
write!(self.out, "{level}")?;
|
||||
let name = Baked(result).to_string();
|
||||
@ -3076,7 +3090,8 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
|
||||
crate::AddressSpace::Function
|
||||
| crate::AddressSpace::Private
|
||||
| crate::AddressSpace::WorkGroup
|
||||
| crate::AddressSpace::PushConstant,
|
||||
| crate::AddressSpace::PushConstant
|
||||
| crate::AddressSpace::TaskPayload,
|
||||
)
|
||||
| None => true,
|
||||
Some(crate::AddressSpace::Uniform) => {
|
||||
|
||||
@ -139,11 +139,11 @@ pub enum FunctionType {
|
||||
}
|
||||
|
||||
impl FunctionType {
|
||||
/// Returns true if the function is an entry point for a compute shader.
|
||||
pub fn is_compute_entry_point(&self, module: &crate::Module) -> bool {
|
||||
/// Returns true if the function is an entry point for a compute-like shader.
|
||||
pub fn is_compute_like_entry_point(&self, module: &crate::Module) -> bool {
|
||||
match *self {
|
||||
FunctionType::EntryPoint(index) => {
|
||||
module.entry_points[index as usize].stage == crate::ShaderStage::Compute
|
||||
module.entry_points[index as usize].stage.compute_like()
|
||||
}
|
||||
FunctionType::Function(_) => false,
|
||||
}
|
||||
|
||||
@ -545,6 +545,7 @@ impl Options {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: _,
|
||||
} => match mode {
|
||||
LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
|
||||
LocationMode::FragmentOutput => {
|
||||
@ -703,6 +704,10 @@ impl ResolvedBinding {
|
||||
Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => {
|
||||
return Err(Error::UnsupportedBuiltIn(built_in))
|
||||
}
|
||||
Bi::CullPrimitive => "primitive_culled",
|
||||
// TODO: figure out how to make this written as a function call
|
||||
Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
|
||||
Bi::MeshTaskSize => unreachable!(),
|
||||
};
|
||||
write!(out, "{name}")?;
|
||||
}
|
||||
|
||||
@ -594,7 +594,8 @@ impl crate::AddressSpace {
|
||||
| Self::Private
|
||||
| Self::WorkGroup
|
||||
| Self::PushConstant
|
||||
| Self::Handle => true,
|
||||
| Self::Handle
|
||||
| Self::TaskPayload => true,
|
||||
Self::Function => false,
|
||||
}
|
||||
}
|
||||
@ -607,6 +608,7 @@ impl crate::AddressSpace {
|
||||
// may end up with "const" even if the binding is read-write,
|
||||
// and that should be OK.
|
||||
Self::Storage { .. } => true,
|
||||
Self::TaskPayload => unimplemented!(),
|
||||
// These should always be read-write.
|
||||
Self::Private | Self::WorkGroup => false,
|
||||
// These translate to `constant` address space, no need for qualifiers.
|
||||
@ -623,6 +625,7 @@ impl crate::AddressSpace {
|
||||
Self::Storage { .. } => Some("device"),
|
||||
Self::Private | Self::Function => Some("thread"),
|
||||
Self::WorkGroup => Some("threadgroup"),
|
||||
Self::TaskPayload => Some("object_data"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4060,6 +4063,14 @@ impl<W: Write> Writer<W> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: write emitters for these
|
||||
crate::Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { .. }) => {
|
||||
unimplemented!()
|
||||
}
|
||||
crate::Statement::MeshFunction(
|
||||
crate::MeshFunction::SetVertex { .. }
|
||||
| crate::MeshFunction::SetPrimitive { .. },
|
||||
) => unimplemented!(),
|
||||
crate::Statement::SubgroupBallot { result, predicate } => {
|
||||
write!(self.out, "{level}")?;
|
||||
let name = self.namer.call("");
|
||||
@ -6619,7 +6630,7 @@ template <typename A>
|
||||
LocationMode::Uniform,
|
||||
false,
|
||||
),
|
||||
crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
|
||||
crate::ShaderStage::Task | crate::ShaderStage::Mesh => unimplemented!(),
|
||||
};
|
||||
|
||||
// Should this entry point be modified to do vertex pulling?
|
||||
@ -6686,6 +6697,9 @@ template <typename A>
|
||||
break;
|
||||
}
|
||||
}
|
||||
crate::AddressSpace::TaskPayload => {
|
||||
unimplemented!()
|
||||
}
|
||||
crate::AddressSpace::Function
|
||||
| crate::AddressSpace::Private
|
||||
| crate::AddressSpace::WorkGroup => {}
|
||||
@ -7683,7 +7697,7 @@ mod workgroup_mem_init {
|
||||
fun_info: &valid::FunctionInfo,
|
||||
) -> bool {
|
||||
options.zero_initialize_workgroup_memory
|
||||
&& ep.stage == crate::ShaderStage::Compute
|
||||
&& ep.stage.compute_like()
|
||||
&& module.global_variables.iter().any(|(handle, var)| {
|
||||
!fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup
|
||||
})
|
||||
|
||||
@ -39,6 +39,8 @@ pub enum PipelineConstantError {
|
||||
ValidationError(#[from] WithSpan<ValidationError>),
|
||||
#[error("workgroup_size override isn't strictly positive")]
|
||||
NegativeWorkgroupSize,
|
||||
#[error("max vertices or max primitives is negative")]
|
||||
NegativeMeshOutputMax,
|
||||
}
|
||||
|
||||
/// Compact `module` and replace all overrides with constants.
|
||||
@ -243,6 +245,7 @@ pub fn process_overrides<'a>(
|
||||
for ep in entry_points.iter_mut() {
|
||||
process_function(&mut module, &override_map, &mut layouter, &mut ep.function)?;
|
||||
process_workgroup_size_override(&mut module, &adjusted_global_expressions, ep)?;
|
||||
process_mesh_shader_overrides(&mut module, &adjusted_global_expressions, ep)?;
|
||||
}
|
||||
module.entry_points = entry_points;
|
||||
module.overrides = overrides;
|
||||
@ -296,6 +299,28 @@ fn process_workgroup_size_override(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_mesh_shader_overrides(
|
||||
module: &mut Module,
|
||||
adjusted_global_expressions: &HandleVec<Expression, Handle<Expression>>,
|
||||
ep: &mut crate::EntryPoint,
|
||||
) -> Result<(), PipelineConstantError> {
|
||||
if let Some(ref mut mesh_info) = ep.mesh_info {
|
||||
if let Some(r#override) = mesh_info.max_vertices_override {
|
||||
mesh_info.max_vertices = module
|
||||
.to_ctx()
|
||||
.eval_expr_to_u32(adjusted_global_expressions[r#override])
|
||||
.map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?;
|
||||
}
|
||||
if let Some(r#override) = mesh_info.max_primitives_override {
|
||||
mesh_info.max_primitives = module
|
||||
.to_ctx()
|
||||
.eval_expr_to_u32(adjusted_global_expressions[r#override])
|
||||
.map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a [`Constant`] to `module` for the override `old_h`.
|
||||
///
|
||||
/// Add the new `Constant` to `override_map` and `adjusted_constant_initializers`.
|
||||
@ -835,6 +860,26 @@ fn adjust_stmt(new_pos: &HandleVec<Expression, Handle<Expression>>, stmt: &mut S
|
||||
crate::RayQueryFunction::Terminate => {}
|
||||
}
|
||||
}
|
||||
Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs {
|
||||
ref mut vertex_count,
|
||||
ref mut primitive_count,
|
||||
}) => {
|
||||
adjust(vertex_count);
|
||||
adjust(primitive_count);
|
||||
}
|
||||
Statement::MeshFunction(
|
||||
crate::MeshFunction::SetVertex {
|
||||
ref mut index,
|
||||
ref mut value,
|
||||
}
|
||||
| crate::MeshFunction::SetPrimitive {
|
||||
ref mut index,
|
||||
ref mut value,
|
||||
},
|
||||
) => {
|
||||
adjust(index);
|
||||
adjust(value);
|
||||
}
|
||||
Statement::Break
|
||||
| Statement::Continue
|
||||
| Statement::Kill
|
||||
|
||||
@ -3655,6 +3655,7 @@ impl BlockContext<'_> {
|
||||
} => {
|
||||
self.write_subgroup_gather(mode, argument, result, &mut block)?;
|
||||
}
|
||||
Statement::MeshFunction(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::Stor
|
||||
crate::AddressSpace::Uniform => spirv::StorageClass::Uniform,
|
||||
crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup,
|
||||
crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant,
|
||||
crate::AddressSpace::TaskPayload => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1094,7 +1094,10 @@ impl Writer {
|
||||
super::ZeroInitializeWorkgroupMemoryMode::Polyfill,
|
||||
Some(
|
||||
ref mut interface @ FunctionInterface {
|
||||
stage: crate::ShaderStage::Compute,
|
||||
stage:
|
||||
crate::ShaderStage::Compute
|
||||
| crate::ShaderStage::Mesh
|
||||
| crate::ShaderStage::Task,
|
||||
..
|
||||
},
|
||||
),
|
||||
@ -1991,6 +1994,7 @@ impl Writer {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: _,
|
||||
} => {
|
||||
self.decorate(id, Decoration::Location, &[location]);
|
||||
|
||||
@ -2148,6 +2152,11 @@ impl Writer {
|
||||
)?;
|
||||
BuiltIn::SubgroupLocalInvocationId
|
||||
}
|
||||
Bi::MeshTaskSize
|
||||
| Bi::CullPrimitive
|
||||
| Bi::PointIndex
|
||||
| Bi::LineIndices
|
||||
| Bi::TriangleIndices => unreachable!(),
|
||||
};
|
||||
|
||||
self.decorate(id, Decoration::BuiltIn, &[built_in as u32]);
|
||||
|
||||
@ -207,7 +207,7 @@ impl<W: Write> Writer<W> {
|
||||
Attribute::Stage(ShaderStage::Compute),
|
||||
Attribute::WorkGroupSize(ep.workgroup_size),
|
||||
],
|
||||
ShaderStage::Task | ShaderStage::Mesh => unreachable!(),
|
||||
ShaderStage::Mesh | ShaderStage::Task => unreachable!(),
|
||||
};
|
||||
|
||||
self.write_attributes(&attributes)?;
|
||||
@ -856,6 +856,7 @@ impl<W: Write> Writer<W> {
|
||||
}
|
||||
}
|
||||
Statement::RayQuery { .. } => unreachable!(),
|
||||
Statement::MeshFunction(..) => unreachable!(),
|
||||
Statement::SubgroupBallot { result, predicate } => {
|
||||
write!(self.out, "{level}")?;
|
||||
let res_name = Baked(result).to_string();
|
||||
@ -1822,6 +1823,7 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src: None,
|
||||
per_primitive: _,
|
||||
} => vec![
|
||||
Attribute::Location(location),
|
||||
Attribute::Interpolate(interpolation, sampling),
|
||||
@ -1831,6 +1833,7 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src: Some(blend_src),
|
||||
per_primitive: _,
|
||||
} => vec![
|
||||
Attribute::Location(location),
|
||||
Attribute::BlendSrc(blend_src),
|
||||
|
||||
@ -189,7 +189,12 @@ impl TryToWgsl for crate::BuiltIn {
|
||||
| Bi::PointSize
|
||||
| Bi::DrawID
|
||||
| Bi::PointCoord
|
||||
| Bi::WorkGroupSize => return None,
|
||||
| Bi::WorkGroupSize
|
||||
| Bi::CullPrimitive
|
||||
| Bi::TriangleIndices
|
||||
| Bi::LineIndices
|
||||
| Bi::MeshTaskSize
|
||||
| Bi::PointIndex => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -353,6 +358,7 @@ pub const fn address_space_str(
|
||||
As::WorkGroup => "workgroup",
|
||||
As::Handle => return (None, None),
|
||||
As::Function => "function",
|
||||
As::TaskPayload => return (None, None),
|
||||
}),
|
||||
None,
|
||||
)
|
||||
|
||||
@ -221,6 +221,45 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) {
|
||||
}
|
||||
}
|
||||
|
||||
for entry in &module.entry_points {
|
||||
if let Some(task_payload) = entry.task_payload {
|
||||
module_tracer.global_variables_used.insert(task_payload);
|
||||
}
|
||||
if let Some(ref mesh_info) = entry.mesh_info {
|
||||
module_tracer
|
||||
.types_used
|
||||
.insert(mesh_info.vertex_output_type);
|
||||
module_tracer
|
||||
.types_used
|
||||
.insert(mesh_info.primitive_output_type);
|
||||
if let Some(max_vertices_override) = mesh_info.max_vertices_override {
|
||||
module_tracer
|
||||
.global_expressions_used
|
||||
.insert(max_vertices_override);
|
||||
}
|
||||
if let Some(max_primitives_override) = mesh_info.max_primitives_override {
|
||||
module_tracer
|
||||
.global_expressions_used
|
||||
.insert(max_primitives_override);
|
||||
}
|
||||
}
|
||||
if entry.stage == crate::ShaderStage::Task || entry.stage == crate::ShaderStage::Mesh {
|
||||
// u32 should always be there if the module is valid, as it is e.g. the type of some expressions
|
||||
let u32_type = module
|
||||
.types
|
||||
.iter()
|
||||
.find_map(|tuple| {
|
||||
if tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32) {
|
||||
Some(tuple.0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
module_tracer.types_used.insert(u32_type);
|
||||
}
|
||||
}
|
||||
|
||||
module_tracer.type_expression_tandem();
|
||||
|
||||
// Now that we know what is used and what is never touched,
|
||||
@ -342,6 +381,23 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) {
|
||||
&module_map,
|
||||
&mut reused_named_expressions,
|
||||
);
|
||||
if let Some(ref mut task_payload) = entry.task_payload {
|
||||
module_map.globals.adjust(task_payload);
|
||||
}
|
||||
if let Some(ref mut mesh_info) = entry.mesh_info {
|
||||
module_map.types.adjust(&mut mesh_info.vertex_output_type);
|
||||
module_map
|
||||
.types
|
||||
.adjust(&mut mesh_info.primitive_output_type);
|
||||
if let Some(ref mut max_vertices_override) = mesh_info.max_vertices_override {
|
||||
module_map.global_expressions.adjust(max_vertices_override);
|
||||
}
|
||||
if let Some(ref mut max_primitives_override) = mesh_info.max_primitives_override {
|
||||
module_map
|
||||
.global_expressions
|
||||
.adjust(max_primitives_override);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -117,6 +117,20 @@ impl FunctionTracer<'_> {
|
||||
self.expressions_used.insert(query);
|
||||
self.trace_ray_query_function(fun);
|
||||
}
|
||||
St::MeshFunction(crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
}) => {
|
||||
self.expressions_used.insert(vertex_count);
|
||||
self.expressions_used.insert(primitive_count);
|
||||
}
|
||||
St::MeshFunction(
|
||||
crate::MeshFunction::SetPrimitive { index, value }
|
||||
| crate::MeshFunction::SetVertex { index, value },
|
||||
) => {
|
||||
self.expressions_used.insert(index);
|
||||
self.expressions_used.insert(value);
|
||||
}
|
||||
St::SubgroupBallot { result, predicate } => {
|
||||
if let Some(predicate) = predicate {
|
||||
self.expressions_used.insert(predicate);
|
||||
@ -335,6 +349,26 @@ impl FunctionMap {
|
||||
adjust(query);
|
||||
self.adjust_ray_query_function(fun);
|
||||
}
|
||||
St::MeshFunction(crate::MeshFunction::SetMeshOutputs {
|
||||
ref mut vertex_count,
|
||||
ref mut primitive_count,
|
||||
}) => {
|
||||
adjust(vertex_count);
|
||||
adjust(primitive_count);
|
||||
}
|
||||
St::MeshFunction(
|
||||
crate::MeshFunction::SetVertex {
|
||||
ref mut index,
|
||||
ref mut value,
|
||||
}
|
||||
| crate::MeshFunction::SetPrimitive {
|
||||
ref mut index,
|
||||
ref mut value,
|
||||
},
|
||||
) => {
|
||||
adjust(index);
|
||||
adjust(value);
|
||||
}
|
||||
St::SubgroupBallot {
|
||||
ref mut result,
|
||||
ref mut predicate,
|
||||
|
||||
@ -1377,6 +1377,8 @@ impl Frontend {
|
||||
result: ty.map(|ty| FunctionResult { ty, binding: None }),
|
||||
..Default::default()
|
||||
},
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@ -1446,6 +1448,7 @@ impl Context<'_> {
|
||||
interpolation,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
};
|
||||
location += 1;
|
||||
|
||||
@ -1482,6 +1485,7 @@ impl Context<'_> {
|
||||
interpolation,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
};
|
||||
location += 1;
|
||||
binding
|
||||
|
||||
@ -107,7 +107,7 @@ impl ShaderMetadata {
|
||||
self.version = 0;
|
||||
self.profile = Profile::Core;
|
||||
self.stage = stage;
|
||||
self.workgroup_size = [u32::from(stage == ShaderStage::Compute); 3];
|
||||
self.workgroup_size = [u32::from(stage.compute_like()); 3];
|
||||
self.early_fragment_tests = false;
|
||||
self.extensions.clear();
|
||||
}
|
||||
|
||||
@ -466,6 +466,7 @@ impl Frontend {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: false,
|
||||
},
|
||||
handle,
|
||||
storage,
|
||||
|
||||
@ -44,6 +44,7 @@ impl crate::Binding {
|
||||
interpolation: ref mut interpolation @ None,
|
||||
ref mut sampling,
|
||||
blend_src: _,
|
||||
per_primitive: _,
|
||||
} = *self
|
||||
{
|
||||
match ty.scalar_kind() {
|
||||
|
||||
@ -596,6 +596,8 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
|
||||
workgroup_size: ep.workgroup_size,
|
||||
workgroup_size_overrides: None,
|
||||
function,
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -266,6 +266,7 @@ impl Decoration {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
}),
|
||||
_ => Err(Error::MissingDecoration(spirv::Decoration::Location)),
|
||||
}
|
||||
@ -4660,6 +4661,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
|
||||
| S::Atomic { .. }
|
||||
| S::ImageAtomic { .. }
|
||||
| S::RayQuery { .. }
|
||||
| S::MeshFunction(..)
|
||||
| S::SubgroupBallot { .. }
|
||||
| S::SubgroupCollectiveOperation { .. }
|
||||
| S::SubgroupGather { .. } => {}
|
||||
@ -4941,6 +4943,8 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
|
||||
spirv::ExecutionModel::Vertex => crate::ShaderStage::Vertex,
|
||||
spirv::ExecutionModel::Fragment => crate::ShaderStage::Fragment,
|
||||
spirv::ExecutionModel::GLCompute => crate::ShaderStage::Compute,
|
||||
spirv::ExecutionModel::TaskEXT => crate::ShaderStage::Task,
|
||||
spirv::ExecutionModel::MeshEXT => crate::ShaderStage::Mesh,
|
||||
_ => return Err(Error::UnsupportedExecutionModel(exec_model as u32)),
|
||||
},
|
||||
name,
|
||||
|
||||
@ -1527,6 +1527,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
|
||||
workgroup_size,
|
||||
workgroup_size_overrides,
|
||||
function,
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
});
|
||||
Ok(LoweredGlobalDecl::EntryPoint(
|
||||
ctx.module.entry_points.len() - 1,
|
||||
@ -4069,6 +4071,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive: false,
|
||||
};
|
||||
binding.apply_default_interpolation(&ctx.module.types[ty].inner);
|
||||
Some(binding)
|
||||
|
||||
@ -320,13 +320,21 @@ pub enum ConservativeDepth {
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
||||
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
|
||||
#[allow(missing_docs)] // The names are self evident
|
||||
pub enum ShaderStage {
|
||||
/// A vertex shader, in a render pipeline.
|
||||
Vertex,
|
||||
Fragment,
|
||||
Compute,
|
||||
|
||||
/// A task shader, in a mesh render pipeline.
|
||||
Task,
|
||||
|
||||
/// A mesh shader, in a mesh render pipeline.
|
||||
Mesh,
|
||||
|
||||
/// A fragment shader, in a render pipeline.
|
||||
Fragment,
|
||||
|
||||
/// Compute pipeline shader.
|
||||
Compute,
|
||||
}
|
||||
|
||||
/// Addressing space of variables.
|
||||
@ -363,6 +371,8 @@ pub enum AddressSpace {
|
||||
///
|
||||
/// [`SHADER_FLOAT16`]: crate::valid::Capabilities::SHADER_FLOAT16
|
||||
PushConstant,
|
||||
/// Task shader to mesh shader payload
|
||||
TaskPayload,
|
||||
}
|
||||
|
||||
/// Built-in inputs and outputs.
|
||||
@ -371,37 +381,75 @@ pub enum AddressSpace {
|
||||
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
|
||||
pub enum BuiltIn {
|
||||
/// Written in vertex/mesh shaders, read in fragment shaders
|
||||
Position { invariant: bool },
|
||||
/// Read in task, mesh, vertex, and fragment shaders
|
||||
ViewIndex,
|
||||
// vertex
|
||||
|
||||
/// Read in vertex shaders
|
||||
BaseInstance,
|
||||
/// Read in vertex shaders
|
||||
BaseVertex,
|
||||
/// Written in vertex & mesh shaders
|
||||
ClipDistance,
|
||||
/// Written in vertex & mesh shaders
|
||||
CullDistance,
|
||||
/// Read in vertex shaders
|
||||
InstanceIndex,
|
||||
/// Written in vertex & mesh shaders
|
||||
PointSize,
|
||||
/// Read in vertex shaders
|
||||
VertexIndex,
|
||||
/// Read in vertex & task shaders, or mesh shaders in pipelines without task shaders
|
||||
DrawID,
|
||||
// fragment
|
||||
|
||||
/// Written in fragment shaders
|
||||
FragDepth,
|
||||
/// Read in fragment shaders
|
||||
PointCoord,
|
||||
/// Read in fragment shaders
|
||||
FrontFacing,
|
||||
/// Read in fragment shaders, in the future may written in mesh shaders
|
||||
PrimitiveIndex,
|
||||
/// Read in fragment shaders
|
||||
Barycentric,
|
||||
/// Read in fragment shaders
|
||||
SampleIndex,
|
||||
/// Read or written in fragment shaders
|
||||
SampleMask,
|
||||
// compute
|
||||
|
||||
/// Read in compute, task, and mesh shaders
|
||||
GlobalInvocationId,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
LocalInvocationId,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
LocalInvocationIndex,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
WorkGroupId,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
WorkGroupSize,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
NumWorkGroups,
|
||||
// subgroup
|
||||
|
||||
/// Read in compute, task, and mesh shaders
|
||||
NumSubgroups,
|
||||
/// Read in compute, task, and mesh shaders
|
||||
SubgroupId,
|
||||
/// Read in compute, fragment, task, and mesh shaders
|
||||
SubgroupSize,
|
||||
/// Read in compute, fragment, task, and mesh shaders
|
||||
SubgroupInvocationId,
|
||||
|
||||
/// Written in task shaders
|
||||
MeshTaskSize,
|
||||
/// Written in mesh shaders
|
||||
CullPrimitive,
|
||||
/// Written in mesh shaders
|
||||
PointIndex,
|
||||
/// Written in mesh shaders
|
||||
LineIndices,
|
||||
/// Written in mesh shaders
|
||||
TriangleIndices,
|
||||
}
|
||||
|
||||
/// Number of bytes per scalar.
|
||||
@ -946,6 +994,9 @@ pub enum Binding {
|
||||
|
||||
/// Indexed location.
|
||||
///
|
||||
/// This is a value passed to a [`Fragment`] shader from a [`Vertex`] or
|
||||
/// [`Mesh`] shader.
|
||||
///
|
||||
/// Values passed from the [`Vertex`] stage to the [`Fragment`] stage must
|
||||
/// have their `interpolation` defaulted (i.e. not `None`) by the front end
|
||||
/// as appropriate for that language.
|
||||
@ -959,14 +1010,30 @@ pub enum Binding {
|
||||
/// interpolation must be `Flat`.
|
||||
///
|
||||
/// [`Vertex`]: crate::ShaderStage::Vertex
|
||||
/// [`Mesh`]: crate::ShaderStage::Mesh
|
||||
/// [`Fragment`]: crate::ShaderStage::Fragment
|
||||
Location {
|
||||
location: u32,
|
||||
interpolation: Option<Interpolation>,
|
||||
sampling: Option<Sampling>,
|
||||
|
||||
/// Optional `blend_src` index used for dual source blending.
|
||||
/// See <https://www.w3.org/TR/WGSL/#attribute-blend_src>
|
||||
blend_src: Option<u32>,
|
||||
|
||||
/// Whether the binding is a per-primitive binding for use with mesh shaders.
|
||||
///
|
||||
/// This must be `true` if this binding is a mesh shader primitive output, or such
|
||||
/// an output's corresponding fragment shader input. It must be `false` otherwise.
|
||||
///
|
||||
/// A stage's outputs must all have unique `location` numbers, regardless of
|
||||
/// whether they are per-primitive; a mesh shader's per-vertex and per-primitive
|
||||
/// outputs share the same location numbering space.
|
||||
///
|
||||
/// Per-primitive values are not interpolated at all and are not dependent on the
|
||||
/// vertices or pixel location. For example, it may be used to store a
|
||||
/// non-interpolated normal vector.
|
||||
per_primitive: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@ -1725,10 +1792,12 @@ pub enum Expression {
|
||||
query: Handle<Expression>,
|
||||
committed: bool,
|
||||
},
|
||||
|
||||
/// Result of a [`SubgroupBallot`] statement.
|
||||
///
|
||||
/// [`SubgroupBallot`]: Statement::SubgroupBallot
|
||||
SubgroupBallotResult,
|
||||
|
||||
/// Result of a [`SubgroupCollectiveOperation`] or [`SubgroupGather`] statement.
|
||||
///
|
||||
/// [`SubgroupCollectiveOperation`]: Statement::SubgroupCollectiveOperation
|
||||
@ -2142,6 +2211,8 @@ pub enum Statement {
|
||||
/// The specific operation we're performing on `query`.
|
||||
fun: RayQueryFunction,
|
||||
},
|
||||
/// A mesh shader intrinsic.
|
||||
MeshFunction(MeshFunction),
|
||||
/// Calculate a bitmask using a boolean from each active thread in the subgroup
|
||||
SubgroupBallot {
|
||||
/// The [`SubgroupBallotResult`] expression representing this load's result.
|
||||
@ -2315,6 +2386,12 @@ pub struct EntryPoint {
|
||||
pub workgroup_size_overrides: Option<[Option<Handle<Expression>>; 3]>,
|
||||
/// The entrance function.
|
||||
pub function: Function,
|
||||
/// Information for [`Mesh`] shaders.
|
||||
///
|
||||
/// [`Mesh`]: ShaderStage::Mesh
|
||||
pub mesh_info: Option<MeshStageInfo>,
|
||||
/// The unique global variable used as a task payload from task shader to mesh shader
|
||||
pub task_payload: Option<Handle<GlobalVariable>>,
|
||||
}
|
||||
|
||||
/// Return types predeclared for the frexp, modf, and atomicCompareExchangeWeak built-in functions.
|
||||
@ -2491,6 +2568,66 @@ pub struct DocComments {
|
||||
pub module: Vec<String>,
|
||||
}
|
||||
|
||||
/// The output topology for a mesh shader. Note that mesh shaders don't allow things like triangle-strips.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
||||
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
|
||||
pub enum MeshOutputTopology {
|
||||
/// Outputs individual vertices to be rendered as points.
|
||||
Points,
|
||||
/// Outputs groups of 2 vertices to be renderedas lines .
|
||||
Lines,
|
||||
/// Outputs groups of 3 vertices to be rendered as triangles.
|
||||
Triangles,
|
||||
}
|
||||
|
||||
/// Information specific to mesh shader entry points.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
||||
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
|
||||
#[allow(dead_code)]
|
||||
pub struct MeshStageInfo {
|
||||
/// The type of primitive outputted.
|
||||
pub topology: MeshOutputTopology,
|
||||
/// The maximum number of vertices a mesh shader may output.
|
||||
pub max_vertices: u32,
|
||||
/// If pipeline constants are used, the expressions that override `max_vertices`
|
||||
pub max_vertices_override: Option<Handle<Expression>>,
|
||||
/// The maximum number of primitives a mesh shader may output.
|
||||
pub max_primitives: u32,
|
||||
/// If pipeline constants are used, the expressions that override `max_primitives`
|
||||
pub max_primitives_override: Option<Handle<Expression>>,
|
||||
/// The type used by vertex outputs, i.e. what is passed to `setVertex`.
|
||||
pub vertex_output_type: Handle<Type>,
|
||||
/// The type used by primitive outputs, i.e. what is passed to `setPrimitive`.
|
||||
pub primitive_output_type: Handle<Type>,
|
||||
}
|
||||
|
||||
/// Mesh shader intrinsics
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
||||
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
|
||||
pub enum MeshFunction {
|
||||
/// Sets the number of vertices and primitives that will be outputted.
|
||||
SetMeshOutputs {
|
||||
vertex_count: Handle<Expression>,
|
||||
primitive_count: Handle<Expression>,
|
||||
},
|
||||
/// Sets the output vertex at a given index.
|
||||
SetVertex {
|
||||
index: Handle<Expression>,
|
||||
value: Handle<Expression>,
|
||||
},
|
||||
/// Sets the output primitive at a given index.
|
||||
SetPrimitive {
|
||||
index: Handle<Expression>,
|
||||
value: Handle<Expression>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Shader module.
|
||||
///
|
||||
/// A module is a set of constants, global variables and functions, as well as
|
||||
|
||||
@ -179,6 +179,9 @@ impl super::AddressSpace {
|
||||
crate::AddressSpace::Storage { access } => access,
|
||||
crate::AddressSpace::Handle => Sa::LOAD,
|
||||
crate::AddressSpace::PushConstant => Sa::LOAD,
|
||||
// TaskPayload isn't always writable, but this is checked for elsewhere,
|
||||
// when not using multiple payloads and matching the entry payload is checked.
|
||||
crate::AddressSpace::TaskPayload => Sa::LOAD | Sa::STORE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -628,6 +631,15 @@ pub fn flatten_compose<'arenas>(
|
||||
.take(size)
|
||||
}
|
||||
|
||||
impl super::ShaderStage {
|
||||
pub const fn compute_like(self) -> bool {
|
||||
match self {
|
||||
Self::Vertex | Self::Fragment => false,
|
||||
Self::Compute | Self::Task | Self::Mesh => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix_size() {
|
||||
let module = crate::Module::default();
|
||||
|
||||
@ -36,6 +36,7 @@ pub fn ensure_block_returns(block: &mut crate::Block) {
|
||||
| S::ImageStore { .. }
|
||||
| S::Call { .. }
|
||||
| S::RayQuery { .. }
|
||||
| S::MeshFunction(..)
|
||||
| S::Atomic { .. }
|
||||
| S::ImageAtomic { .. }
|
||||
| S::WorkGroupUniformLoad { .. }
|
||||
|
||||
@ -85,6 +85,25 @@ struct FunctionUniformity {
|
||||
exit: ExitFlags,
|
||||
}
|
||||
|
||||
/// Mesh shader related characteristics of a function.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct FunctionMeshShaderInfo {
|
||||
/// The type of value this function passes to [`SetVertex`], and the
|
||||
/// expression that first established it.
|
||||
///
|
||||
/// [`SetVertex`]: crate::ir::MeshFunction::SetVertex
|
||||
pub vertex_type: Option<(Handle<crate::Type>, Handle<crate::Expression>)>,
|
||||
|
||||
/// The type of value this function passes to [`SetPrimitive`], and the
|
||||
/// expression that first established it.
|
||||
///
|
||||
/// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive
|
||||
pub primitive_type: Option<(Handle<crate::Type>, Handle<crate::Expression>)>,
|
||||
}
|
||||
|
||||
impl ops::BitOr for FunctionUniformity {
|
||||
type Output = Self;
|
||||
fn bitor(self, other: Self) -> Self {
|
||||
@ -302,6 +321,9 @@ pub struct FunctionInfo {
|
||||
/// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in
|
||||
/// validation.
|
||||
diagnostic_filter_leaf: Option<Handle<DiagnosticFilterNode>>,
|
||||
|
||||
/// Mesh shader info for this function and its callees.
|
||||
pub mesh_shader_info: FunctionMeshShaderInfo,
|
||||
}
|
||||
|
||||
impl FunctionInfo {
|
||||
@ -372,6 +394,22 @@ impl FunctionInfo {
|
||||
info.uniformity.non_uniform_result
|
||||
}
|
||||
|
||||
/// Note an entry point's use of `global` not recorded by [`ModuleInfo::process_function`].
|
||||
///
|
||||
/// Most global variable usage should be recorded via [`add_ref_impl`] in the process
|
||||
/// of expression behavior analysis by [`ModuleInfo::process_function`]. But that code
|
||||
/// has no access to entrypoint-specific information, so interface analysis uses this
|
||||
/// function to record global uses there (like task shader payloads).
|
||||
///
|
||||
/// [`add_ref_impl`]: Self::add_ref_impl
|
||||
pub(super) fn insert_global_use(
|
||||
&mut self,
|
||||
global_use: GlobalUse,
|
||||
global: Handle<crate::GlobalVariable>,
|
||||
) {
|
||||
self.global_uses[global.index()] |= global_use;
|
||||
}
|
||||
|
||||
/// Record a use of `expr` for its value.
|
||||
///
|
||||
/// This is used for almost all expression references. Anything
|
||||
@ -482,6 +520,9 @@ impl FunctionInfo {
|
||||
*mine |= *other;
|
||||
}
|
||||
|
||||
// Inherit mesh output types from our callees.
|
||||
self.try_update_mesh_info(&callee.mesh_shader_info)?;
|
||||
|
||||
Ok(FunctionUniformity {
|
||||
result: callee.uniformity.clone(),
|
||||
exit: if callee.may_kill {
|
||||
@ -635,7 +676,8 @@ impl FunctionInfo {
|
||||
// local data is non-uniform
|
||||
As::Function | As::Private => false,
|
||||
// workgroup memory is exclusively accessed by the group
|
||||
As::WorkGroup => true,
|
||||
// task payload memory is very similar to workgroup memory
|
||||
As::WorkGroup | As::TaskPayload => true,
|
||||
// uniform data
|
||||
As::Uniform | As::PushConstant => true,
|
||||
// storage data is only uniform when read-only
|
||||
@ -1113,6 +1155,36 @@ impl FunctionInfo {
|
||||
}
|
||||
FunctionUniformity::new()
|
||||
}
|
||||
S::MeshFunction(func) => {
|
||||
self.available_stages |= ShaderStages::MESH;
|
||||
match &func {
|
||||
// TODO: double check all of this uniformity stuff. I frankly don't fully understand all of it.
|
||||
&crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
} => {
|
||||
let _ = self.add_ref(vertex_count);
|
||||
let _ = self.add_ref(primitive_count);
|
||||
FunctionUniformity::new()
|
||||
}
|
||||
&crate::MeshFunction::SetVertex { index, value }
|
||||
| &crate::MeshFunction::SetPrimitive { index, value } => {
|
||||
let _ = self.add_ref(index);
|
||||
let _ = self.add_ref(value);
|
||||
let ty = self.expressions[value.index()].ty.handle().ok_or(
|
||||
FunctionError::InvalidMeshShaderOutputType(value).with_span(),
|
||||
)?;
|
||||
|
||||
if matches!(func, crate::MeshFunction::SetVertex { .. }) {
|
||||
self.try_update_mesh_vertex_type(ty, value)?;
|
||||
} else {
|
||||
self.try_update_mesh_primitive_type(ty, value)?;
|
||||
};
|
||||
|
||||
FunctionUniformity::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
S::SubgroupBallot {
|
||||
result: _,
|
||||
predicate,
|
||||
@ -1158,6 +1230,72 @@ impl FunctionInfo {
|
||||
}
|
||||
Ok(combined_uniformity)
|
||||
}
|
||||
|
||||
/// Note the type of value passed to [`SetVertex`].
|
||||
///
|
||||
/// Record that this function passed a value of type `ty` as the second
|
||||
/// argument to the [`SetVertex`] builtin function. All calls to
|
||||
/// `SetVertex` must pass the same type, and this must match the
|
||||
/// function's [`vertex_output_type`].
|
||||
///
|
||||
/// [`SetVertex`]: crate::ir::MeshFunction::SetVertex
|
||||
/// [`vertex_output_type`]: crate::ir::MeshStageInfo::vertex_output_type
|
||||
fn try_update_mesh_vertex_type(
|
||||
&mut self,
|
||||
ty: Handle<crate::Type>,
|
||||
value: Handle<crate::Expression>,
|
||||
) -> Result<(), WithSpan<FunctionError>> {
|
||||
if let &Some(ref existing) = &self.mesh_shader_info.vertex_type {
|
||||
if existing.0 != ty {
|
||||
return Err(
|
||||
FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.mesh_shader_info.vertex_type = Some((ty, value));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Note the type of value passed to [`SetPrimitive`].
|
||||
///
|
||||
/// Record that this function passed a value of type `ty` as the second
|
||||
/// argument to the [`SetPrimitive`] builtin function. All calls to
|
||||
/// `SetPrimitive` must pass the same type, and this must match the
|
||||
/// function's [`primitive_output_type`].
|
||||
///
|
||||
/// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive
|
||||
/// [`primitive_output_type`]: crate::ir::MeshStageInfo::primitive_output_type
|
||||
fn try_update_mesh_primitive_type(
|
||||
&mut self,
|
||||
ty: Handle<crate::Type>,
|
||||
value: Handle<crate::Expression>,
|
||||
) -> Result<(), WithSpan<FunctionError>> {
|
||||
if let &Some(ref existing) = &self.mesh_shader_info.primitive_type {
|
||||
if existing.0 != ty {
|
||||
return Err(
|
||||
FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.mesh_shader_info.primitive_type = Some((ty, value));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update this function's mesh shader info, given that it calls `callee`.
|
||||
fn try_update_mesh_info(
|
||||
&mut self,
|
||||
callee: &FunctionMeshShaderInfo,
|
||||
) -> Result<(), WithSpan<FunctionError>> {
|
||||
if let &Some(ref other_vertex) = &callee.vertex_type {
|
||||
self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?;
|
||||
}
|
||||
if let &Some(ref other_primitive) = &callee.primitive_type {
|
||||
self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleInfo {
|
||||
@ -1193,6 +1331,7 @@ impl ModuleInfo {
|
||||
sampling: crate::FastHashSet::default(),
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: fun.diagnostic_filter_leaf,
|
||||
mesh_shader_info: FunctionMeshShaderInfo::default(),
|
||||
};
|
||||
let resolve_context =
|
||||
ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments);
|
||||
@ -1326,6 +1465,7 @@ fn uniform_control_flow() {
|
||||
sampling: crate::FastHashSet::default(),
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: FunctionMeshShaderInfo::default(),
|
||||
};
|
||||
let resolve_context = ResolveContext {
|
||||
constants: &Arena::new(),
|
||||
|
||||
@ -217,6 +217,14 @@ pub enum FunctionError {
|
||||
EmitResult(Handle<crate::Expression>),
|
||||
#[error("Expression not visited by the appropriate statement")]
|
||||
UnvisitedExpression(Handle<crate::Expression>),
|
||||
#[error("Expression {0:?} in mesh shader intrinsic call should be `u32` (is the expression a signed integer?)")]
|
||||
InvalidMeshFunctionCall(Handle<crate::Expression>),
|
||||
#[error("Mesh output types differ from {0:?} to {1:?}")]
|
||||
ConflictingMeshOutputTypes(Handle<crate::Expression>, Handle<crate::Expression>),
|
||||
#[error("Task payload variables differ from {0:?} to {1:?}")]
|
||||
ConflictingTaskPayloadVariables(Handle<crate::Expression>, Handle<crate::Expression>),
|
||||
#[error("Mesh shader output at {0:?} is not a user-defined struct")]
|
||||
InvalidMeshShaderOutputType(Handle<crate::Expression>),
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
@ -1539,6 +1547,41 @@ impl super::Validator {
|
||||
crate::RayQueryFunction::Terminate => {}
|
||||
}
|
||||
}
|
||||
S::MeshFunction(func) => {
|
||||
let ensure_u32 =
|
||||
|expr: Handle<crate::Expression>| -> Result<(), WithSpan<FunctionError>> {
|
||||
let u32_ty = TypeResolution::Value(Ti::Scalar(crate::Scalar::U32));
|
||||
let ty = context
|
||||
.resolve_type_impl(expr, &self.valid_expression_set)
|
||||
.map_err_inner(|source| {
|
||||
FunctionError::Expression {
|
||||
source,
|
||||
handle: expr,
|
||||
}
|
||||
.with_span_handle(expr, context.expressions)
|
||||
})?;
|
||||
if !context.compare_types(&u32_ty, ty) {
|
||||
return Err(FunctionError::InvalidMeshFunctionCall(expr)
|
||||
.with_span_handle(expr, context.expressions));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
match func {
|
||||
crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
} => {
|
||||
ensure_u32(vertex_count)?;
|
||||
ensure_u32(primitive_count)?;
|
||||
}
|
||||
crate::MeshFunction::SetVertex { index, value: _ }
|
||||
| crate::MeshFunction::SetPrimitive { index, value: _ } => {
|
||||
ensure_u32(index)?;
|
||||
// Value is validated elsewhere (since the value type isn't known ahead of time but must match for all calls
|
||||
// in a function or the function's called functions)
|
||||
}
|
||||
}
|
||||
}
|
||||
S::SubgroupBallot { result, predicate } => {
|
||||
stages &= self.subgroup_stages;
|
||||
if !self.capabilities.contains(super::Capabilities::SUBGROUP) {
|
||||
|
||||
@ -233,6 +233,20 @@ impl super::Validator {
|
||||
validate_const_expr(size)?;
|
||||
}
|
||||
}
|
||||
if let Some(task_payload) = entry_point.task_payload {
|
||||
Self::validate_global_variable_handle(task_payload, global_variables)?;
|
||||
}
|
||||
if let Some(ref mesh_info) = entry_point.mesh_info {
|
||||
validate_type(mesh_info.vertex_output_type)?;
|
||||
validate_type(mesh_info.primitive_output_type)?;
|
||||
for ov in mesh_info
|
||||
.max_vertices_override
|
||||
.iter()
|
||||
.chain(mesh_info.max_primitives_override.iter())
|
||||
{
|
||||
validate_const_expr(*ov)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (function_handle, function) in functions.iter() {
|
||||
@ -801,6 +815,22 @@ impl super::Validator {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
crate::Statement::MeshFunction(func) => match func {
|
||||
crate::MeshFunction::SetMeshOutputs {
|
||||
vertex_count,
|
||||
primitive_count,
|
||||
} => {
|
||||
validate_expr(vertex_count)?;
|
||||
validate_expr(primitive_count)?;
|
||||
Ok(())
|
||||
}
|
||||
crate::MeshFunction::SetVertex { index, value }
|
||||
| crate::MeshFunction::SetPrimitive { index, value } => {
|
||||
validate_expr(index)?;
|
||||
validate_expr(value)?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
crate::Statement::SubgroupBallot { result, predicate } => {
|
||||
validate_expr_opt(predicate)?;
|
||||
validate_expr(result)?;
|
||||
|
||||
@ -43,6 +43,8 @@ pub enum GlobalVariableError {
|
||||
StorageAddressSpaceWriteOnlyNotSupported,
|
||||
#[error("Type is not valid for use as a push constant")]
|
||||
InvalidPushConstantType(#[source] PushConstantError),
|
||||
#[error("Task payload must not be zero-sized")]
|
||||
ZeroSizedTaskPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
@ -92,6 +94,12 @@ pub enum VaryingError {
|
||||
},
|
||||
#[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")]
|
||||
InvalidMultiDimensionalSubgroupBuiltIn,
|
||||
#[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")]
|
||||
InvalidPerPrimitive,
|
||||
#[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")]
|
||||
MissingPerPrimitive,
|
||||
#[error("The `MESH_SHADER` capability must be enabled to use per-primitive fragment inputs.")]
|
||||
PerPrimitiveNotAllowed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
@ -123,6 +131,34 @@ pub enum EntryPointError {
|
||||
InvalidIntegerInterpolation { location: u32 },
|
||||
#[error(transparent)]
|
||||
Function(#[from] FunctionError),
|
||||
#[error("mesh shader entry point missing mesh shader attributes")]
|
||||
ExpectedMeshShaderAttributes,
|
||||
#[error("Non mesh shader entry point cannot have mesh shader attributes")]
|
||||
UnexpectedMeshShaderAttributes,
|
||||
#[error("Non mesh/task shader entry point cannot have task payload attribute")]
|
||||
UnexpectedTaskPayload,
|
||||
#[error("Task payload must be declared with `var<task_payload>`")]
|
||||
TaskPayloadWrongAddressSpace,
|
||||
#[error("For a task payload to be used, it must be declared with @payload")]
|
||||
WrongTaskPayloadUsed,
|
||||
#[error("A function can only set vertex and primitive types that correspond to the mesh shader attributes")]
|
||||
WrongMeshOutputType,
|
||||
#[error("Only mesh shader entry points can write to mesh output vertices and primitives")]
|
||||
UnexpectedMeshShaderOutput,
|
||||
#[error("Mesh shader entry point cannot have a return type")]
|
||||
UnexpectedMeshShaderEntryResult,
|
||||
#[error("Task shader entry point must return @builtin(mesh_task_size) vec3<u32>")]
|
||||
WrongTaskShaderEntryResult,
|
||||
#[error("Mesh output type must be a user-defined struct.")]
|
||||
InvalidMeshOutputType,
|
||||
#[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")]
|
||||
InvalidMeshPrimitiveOutputType,
|
||||
#[error("Task shaders must declare a task payload output")]
|
||||
ExpectedTaskPayload,
|
||||
#[error(
|
||||
"The `MESH_SHADER` capability must be enabled to compile mesh shaders and task shaders."
|
||||
)]
|
||||
MeshShaderCapabilityDisabled,
|
||||
}
|
||||
|
||||
fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
|
||||
@ -139,6 +175,13 @@ fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
|
||||
storage_usage
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum MeshOutputType {
|
||||
None,
|
||||
VertexOutput,
|
||||
PrimitiveOutput,
|
||||
}
|
||||
|
||||
struct VaryingContext<'a> {
|
||||
stage: crate::ShaderStage,
|
||||
output: bool,
|
||||
@ -149,6 +192,8 @@ struct VaryingContext<'a> {
|
||||
built_ins: &'a mut crate::FastHashSet<crate::BuiltIn>,
|
||||
capabilities: Capabilities,
|
||||
flags: super::ValidationFlags,
|
||||
mesh_output_type: MeshOutputType,
|
||||
has_task_payload: bool,
|
||||
}
|
||||
|
||||
impl VaryingContext<'_> {
|
||||
@ -202,16 +247,20 @@ impl VaryingContext<'_> {
|
||||
}
|
||||
|
||||
let (visible, type_good) = match built_in {
|
||||
Bi::BaseInstance
|
||||
| Bi::BaseVertex
|
||||
| Bi::InstanceIndex
|
||||
| Bi::VertexIndex
|
||||
| Bi::DrawID => (
|
||||
Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => (
|
||||
self.stage == St::Vertex && !self.output,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::DrawID => (
|
||||
// Always allowed in task/vertex stage. Allowed in mesh stage if there is no task stage in the pipeline.
|
||||
(self.stage == St::Vertex
|
||||
|| self.stage == St::Task
|
||||
|| (self.stage == St::Mesh && !self.has_task_payload))
|
||||
&& !self.output,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::ClipDistance | Bi::CullDistance => (
|
||||
self.stage == St::Vertex && self.output,
|
||||
(self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
|
||||
match *ty_inner {
|
||||
Ti::Array { base, size, .. } => {
|
||||
self.types[base].inner == Ti::Scalar(crate::Scalar::F32)
|
||||
@ -224,7 +273,7 @@ impl VaryingContext<'_> {
|
||||
},
|
||||
),
|
||||
Bi::PointSize => (
|
||||
self.stage == St::Vertex && self.output,
|
||||
(self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::F32),
|
||||
),
|
||||
Bi::PointCoord => (
|
||||
@ -237,10 +286,9 @@ impl VaryingContext<'_> {
|
||||
),
|
||||
Bi::Position { .. } => (
|
||||
match self.stage {
|
||||
St::Vertex => self.output,
|
||||
St::Vertex | St::Mesh => self.output,
|
||||
St::Fragment => !self.output,
|
||||
St::Compute => false,
|
||||
St::Task | St::Mesh => unreachable!(),
|
||||
St::Compute | St::Task => false,
|
||||
},
|
||||
*ty_inner
|
||||
== Ti::Vector {
|
||||
@ -250,9 +298,8 @@ impl VaryingContext<'_> {
|
||||
),
|
||||
Bi::ViewIndex => (
|
||||
match self.stage {
|
||||
St::Vertex | St::Fragment => !self.output,
|
||||
St::Vertex | St::Fragment | St::Task | St::Mesh => !self.output,
|
||||
St::Compute => false,
|
||||
St::Task | St::Mesh => unreachable!(),
|
||||
},
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::I32),
|
||||
),
|
||||
@ -285,7 +332,7 @@ impl VaryingContext<'_> {
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::LocalInvocationIndex => (
|
||||
self.stage == St::Compute && !self.output,
|
||||
self.stage.compute_like() && !self.output,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::GlobalInvocationId
|
||||
@ -293,7 +340,7 @@ impl VaryingContext<'_> {
|
||||
| Bi::WorkGroupId
|
||||
| Bi::WorkGroupSize
|
||||
| Bi::NumWorkGroups => (
|
||||
self.stage == St::Compute && !self.output,
|
||||
self.stage.compute_like() && !self.output,
|
||||
*ty_inner
|
||||
== Ti::Vector {
|
||||
size: Vs::Tri,
|
||||
@ -301,17 +348,48 @@ impl VaryingContext<'_> {
|
||||
},
|
||||
),
|
||||
Bi::NumSubgroups | Bi::SubgroupId => (
|
||||
self.stage == St::Compute && !self.output,
|
||||
self.stage.compute_like() && !self.output,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::SubgroupSize | Bi::SubgroupInvocationId => (
|
||||
match self.stage {
|
||||
St::Compute | St::Fragment => !self.output,
|
||||
St::Compute | St::Fragment | St::Task | St::Mesh => !self.output,
|
||||
St::Vertex => false,
|
||||
St::Task | St::Mesh => unreachable!(),
|
||||
},
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::CullPrimitive => (
|
||||
self.mesh_output_type == MeshOutputType::PrimitiveOutput,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::BOOL),
|
||||
),
|
||||
Bi::PointIndex => (
|
||||
self.mesh_output_type == MeshOutputType::PrimitiveOutput,
|
||||
*ty_inner == Ti::Scalar(crate::Scalar::U32),
|
||||
),
|
||||
Bi::LineIndices => (
|
||||
self.mesh_output_type == MeshOutputType::PrimitiveOutput,
|
||||
*ty_inner
|
||||
== Ti::Vector {
|
||||
size: Vs::Bi,
|
||||
scalar: crate::Scalar::U32,
|
||||
},
|
||||
),
|
||||
Bi::TriangleIndices => (
|
||||
self.mesh_output_type == MeshOutputType::PrimitiveOutput,
|
||||
*ty_inner
|
||||
== Ti::Vector {
|
||||
size: Vs::Tri,
|
||||
scalar: crate::Scalar::U32,
|
||||
},
|
||||
),
|
||||
Bi::MeshTaskSize => (
|
||||
self.stage == St::Task && self.output,
|
||||
*ty_inner
|
||||
== Ti::Vector {
|
||||
size: Vs::Tri,
|
||||
scalar: crate::Scalar::U32,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
if !visible {
|
||||
@ -327,7 +405,11 @@ impl VaryingContext<'_> {
|
||||
interpolation,
|
||||
sampling,
|
||||
blend_src,
|
||||
per_primitive,
|
||||
} => {
|
||||
if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) {
|
||||
return Err(VaryingError::PerPrimitiveNotAllowed);
|
||||
}
|
||||
// Only IO-shareable types may be stored in locations.
|
||||
if !self.type_info[ty.index()]
|
||||
.flags
|
||||
@ -336,6 +418,22 @@ impl VaryingContext<'_> {
|
||||
return Err(VaryingError::NotIOShareableType(ty));
|
||||
}
|
||||
|
||||
// Check whether `per_primitive` is appropriate for this stage and direction.
|
||||
if self.mesh_output_type == MeshOutputType::PrimitiveOutput {
|
||||
// All mesh shader `Location` outputs must be `per_primitive`.
|
||||
if !per_primitive {
|
||||
return Err(VaryingError::MissingPerPrimitive);
|
||||
}
|
||||
} else if self.stage == crate::ShaderStage::Fragment && !self.output {
|
||||
// Fragment stage inputs may be `per_primitive`. We'll only
|
||||
// know if these are correct when the whole mesh pipeline is
|
||||
// created and we're paired with a specific mesh or vertex
|
||||
// shader.
|
||||
} else if per_primitive {
|
||||
// All other `Location` bindings must not be `per_primitive`.
|
||||
return Err(VaryingError::InvalidPerPrimitive);
|
||||
}
|
||||
|
||||
if let Some(blend_src) = blend_src {
|
||||
// `blend_src` is only valid if dual source blending was explicitly enabled,
|
||||
// see https://www.w3.org/TR/WGSL/#extension-dual_source_blending
|
||||
@ -401,9 +499,9 @@ impl VaryingContext<'_> {
|
||||
|
||||
let needs_interpolation = match self.stage {
|
||||
crate::ShaderStage::Vertex => self.output,
|
||||
crate::ShaderStage::Fragment => !self.output,
|
||||
crate::ShaderStage::Compute => false,
|
||||
crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
|
||||
crate::ShaderStage::Fragment => !self.output && !per_primitive,
|
||||
crate::ShaderStage::Compute | crate::ShaderStage::Task => false,
|
||||
crate::ShaderStage::Mesh => self.output,
|
||||
};
|
||||
|
||||
// It doesn't make sense to specify a sampling when `interpolation` is `Flat`, but
|
||||
@ -605,6 +703,14 @@ impl super::Validator {
|
||||
false,
|
||||
),
|
||||
crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false),
|
||||
crate::AddressSpace::TaskPayload => {
|
||||
if !self.capabilities.contains(Capabilities::MESH_SHADER) {
|
||||
return Err(GlobalVariableError::UnsupportedCapability(
|
||||
Capabilities::MESH_SHADER,
|
||||
));
|
||||
}
|
||||
(TypeFlags::DATA | TypeFlags::SIZED, false)
|
||||
}
|
||||
crate::AddressSpace::PushConstant => {
|
||||
if !self.capabilities.contains(Capabilities::PUSH_CONSTANT) {
|
||||
return Err(GlobalVariableError::UnsupportedCapability(
|
||||
@ -637,6 +743,14 @@ impl super::Validator {
|
||||
}
|
||||
}
|
||||
|
||||
if var.space == crate::AddressSpace::TaskPayload {
|
||||
let ty = &gctx.types[var.ty].inner;
|
||||
// HLSL doesn't allow zero sized payloads.
|
||||
if ty.try_size(gctx) == Some(0) {
|
||||
return Err(GlobalVariableError::ZeroSizedTaskPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(init) = var.init {
|
||||
match var.space {
|
||||
crate::AddressSpace::Private | crate::AddressSpace::Function => {}
|
||||
@ -660,12 +774,72 @@ impl super::Validator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate the mesh shader output type `ty`, used as `mesh_output_type`.
|
||||
fn validate_mesh_output_type(
|
||||
&mut self,
|
||||
ep: &crate::EntryPoint,
|
||||
module: &crate::Module,
|
||||
ty: Handle<crate::Type>,
|
||||
mesh_output_type: MeshOutputType,
|
||||
) -> Result<(), WithSpan<EntryPointError>> {
|
||||
if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) {
|
||||
return Err(EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types));
|
||||
}
|
||||
let mut result_built_ins = crate::FastHashSet::default();
|
||||
let mut ctx = VaryingContext {
|
||||
stage: ep.stage,
|
||||
output: true,
|
||||
types: &module.types,
|
||||
type_info: &self.types,
|
||||
location_mask: &mut self.location_mask,
|
||||
blend_src_mask: &mut self.blend_src_mask,
|
||||
built_ins: &mut result_built_ins,
|
||||
capabilities: self.capabilities,
|
||||
flags: self.flags,
|
||||
mesh_output_type,
|
||||
has_task_payload: ep.task_payload.is_some(),
|
||||
};
|
||||
ctx.validate(ep, ty, None)
|
||||
.map_err_inner(|e| EntryPointError::Result(e).with_span())?;
|
||||
if mesh_output_type == MeshOutputType::PrimitiveOutput {
|
||||
let mut num_indices_builtins = 0;
|
||||
if result_built_ins.contains(&crate::BuiltIn::PointIndex) {
|
||||
num_indices_builtins += 1;
|
||||
}
|
||||
if result_built_ins.contains(&crate::BuiltIn::LineIndices) {
|
||||
num_indices_builtins += 1;
|
||||
}
|
||||
if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) {
|
||||
num_indices_builtins += 1;
|
||||
}
|
||||
if num_indices_builtins != 1 {
|
||||
return Err(EntryPointError::InvalidMeshPrimitiveOutputType
|
||||
.with_span_handle(ty, &module.types));
|
||||
}
|
||||
} else if mesh_output_type == MeshOutputType::VertexOutput
|
||||
&& !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false })
|
||||
{
|
||||
return Err(
|
||||
EntryPointError::MissingVertexOutputPosition.with_span_handle(ty, &module.types)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn validate_entry_point(
|
||||
&mut self,
|
||||
ep: &crate::EntryPoint,
|
||||
module: &crate::Module,
|
||||
mod_info: &ModuleInfo,
|
||||
) -> Result<FunctionInfo, WithSpan<EntryPointError>> {
|
||||
if matches!(
|
||||
ep.stage,
|
||||
crate::ShaderStage::Task | crate::ShaderStage::Mesh
|
||||
) && !self.capabilities.contains(Capabilities::MESH_SHADER)
|
||||
{
|
||||
return Err(EntryPointError::MeshShaderCapabilityDisabled.with_span());
|
||||
}
|
||||
if ep.early_depth_test.is_some() {
|
||||
let required = Capabilities::EARLY_DEPTH_TEST;
|
||||
if !self.capabilities.contains(required) {
|
||||
@ -680,7 +854,7 @@ impl super::Validator {
|
||||
}
|
||||
}
|
||||
|
||||
if ep.stage == crate::ShaderStage::Compute {
|
||||
if ep.stage.compute_like() {
|
||||
if ep
|
||||
.workgroup_size
|
||||
.iter()
|
||||
@ -692,10 +866,54 @@ impl super::Validator {
|
||||
return Err(EntryPointError::UnexpectedWorkgroupSize.with_span());
|
||||
}
|
||||
|
||||
match (ep.stage, &ep.mesh_info) {
|
||||
(crate::ShaderStage::Mesh, &None) => {
|
||||
return Err(EntryPointError::ExpectedMeshShaderAttributes.with_span());
|
||||
}
|
||||
(_, &Some(_)) => {
|
||||
return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span());
|
||||
}
|
||||
(_, _) => {}
|
||||
}
|
||||
|
||||
let mut info = self
|
||||
.validate_function(&ep.function, module, mod_info, true)
|
||||
.map_err(WithSpan::into_other)?;
|
||||
|
||||
// Validate the task shader payload.
|
||||
match ep.stage {
|
||||
// Task shaders must produce a payload.
|
||||
crate::ShaderStage::Task => {
|
||||
let Some(handle) = ep.task_payload else {
|
||||
return Err(EntryPointError::ExpectedTaskPayload.with_span());
|
||||
};
|
||||
if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
|
||||
return Err(EntryPointError::TaskPayloadWrongAddressSpace
|
||||
.with_span_handle(handle, &module.global_variables));
|
||||
}
|
||||
info.insert_global_use(GlobalUse::READ | GlobalUse::WRITE, handle);
|
||||
}
|
||||
|
||||
// Mesh shaders may accept a payload.
|
||||
crate::ShaderStage::Mesh => {
|
||||
if let Some(handle) = ep.task_payload {
|
||||
if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
|
||||
return Err(EntryPointError::TaskPayloadWrongAddressSpace
|
||||
.with_span_handle(handle, &module.global_variables));
|
||||
}
|
||||
info.insert_global_use(GlobalUse::READ, handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Other stages must not have a payload.
|
||||
_ => {
|
||||
if let Some(handle) = ep.task_payload {
|
||||
return Err(EntryPointError::UnexpectedTaskPayload
|
||||
.with_span_handle(handle, &module.global_variables));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
use super::ShaderStages;
|
||||
|
||||
@ -703,7 +921,8 @@ impl super::Validator {
|
||||
crate::ShaderStage::Vertex => ShaderStages::VERTEX,
|
||||
crate::ShaderStage::Fragment => ShaderStages::FRAGMENT,
|
||||
crate::ShaderStage::Compute => ShaderStages::COMPUTE,
|
||||
crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
|
||||
crate::ShaderStage::Mesh => ShaderStages::MESH,
|
||||
crate::ShaderStage::Task => ShaderStages::TASK,
|
||||
};
|
||||
|
||||
if !info.available_stages.contains(stage_bit) {
|
||||
@ -725,6 +944,8 @@ impl super::Validator {
|
||||
built_ins: &mut argument_built_ins,
|
||||
capabilities: self.capabilities,
|
||||
flags: self.flags,
|
||||
mesh_output_type: MeshOutputType::None,
|
||||
has_task_payload: ep.task_payload.is_some(),
|
||||
};
|
||||
ctx.validate(ep, fa.ty, fa.binding.as_ref())
|
||||
.map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?;
|
||||
@ -743,6 +964,8 @@ impl super::Validator {
|
||||
built_ins: &mut result_built_ins,
|
||||
capabilities: self.capabilities,
|
||||
flags: self.flags,
|
||||
mesh_output_type: MeshOutputType::None,
|
||||
has_task_payload: ep.task_payload.is_some(),
|
||||
};
|
||||
ctx.validate(ep, fr.ty, fr.binding.as_ref())
|
||||
.map_err_inner(|e| EntryPointError::Result(e).with_span())?;
|
||||
@ -751,11 +974,25 @@ impl super::Validator {
|
||||
{
|
||||
return Err(EntryPointError::MissingVertexOutputPosition.with_span());
|
||||
}
|
||||
if ep.stage == crate::ShaderStage::Mesh {
|
||||
return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span());
|
||||
}
|
||||
// Task shaders must have a single `MeshTaskSize` output, and nothing else.
|
||||
if ep.stage == crate::ShaderStage::Task {
|
||||
let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize)
|
||||
&& result_built_ins.len() == 1
|
||||
&& self.location_mask.is_empty();
|
||||
if !ok {
|
||||
return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
|
||||
}
|
||||
}
|
||||
if !self.blend_src_mask.is_empty() {
|
||||
info.dual_source_blending = true;
|
||||
}
|
||||
} else if ep.stage == crate::ShaderStage::Vertex {
|
||||
return Err(EntryPointError::MissingVertexOutputPosition.with_span());
|
||||
} else if ep.stage == crate::ShaderStage::Task {
|
||||
return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
|
||||
}
|
||||
|
||||
{
|
||||
@ -780,6 +1017,13 @@ impl super::Validator {
|
||||
continue;
|
||||
}
|
||||
|
||||
if var.space == crate::AddressSpace::TaskPayload {
|
||||
if ep.task_payload != Some(var_handle) {
|
||||
return Err(EntryPointError::WrongTaskPayloadUsed
|
||||
.with_span_handle(var_handle, &module.global_variables));
|
||||
}
|
||||
}
|
||||
|
||||
let allowed_usage = match var.space {
|
||||
crate::AddressSpace::Function => unreachable!(),
|
||||
crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY,
|
||||
@ -801,6 +1045,15 @@ impl super::Validator {
|
||||
crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {
|
||||
GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY
|
||||
}
|
||||
crate::AddressSpace::TaskPayload => {
|
||||
GlobalUse::READ
|
||||
| GlobalUse::QUERY
|
||||
| if ep.stage == crate::ShaderStage::Task {
|
||||
GlobalUse::WRITE
|
||||
} else {
|
||||
GlobalUse::empty()
|
||||
}
|
||||
}
|
||||
crate::AddressSpace::PushConstant => GlobalUse::READ,
|
||||
};
|
||||
if !allowed_usage.contains(usage) {
|
||||
@ -820,6 +1073,46 @@ impl super::Validator {
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a `Mesh` entry point, check its vertex and primitive output types.
|
||||
// We verified previously that only mesh shaders can have `mesh_info`.
|
||||
if let &Some(ref mesh_info) = &ep.mesh_info {
|
||||
// Mesh shaders don't return any value. All their results are supplied through
|
||||
// [`SetVertex`] and [`SetPrimitive`] calls.
|
||||
if let Some((used_vertex_type, _)) = info.mesh_shader_info.vertex_type {
|
||||
if used_vertex_type != mesh_info.vertex_output_type {
|
||||
return Err(EntryPointError::WrongMeshOutputType
|
||||
.with_span_handle(mesh_info.vertex_output_type, &module.types));
|
||||
}
|
||||
}
|
||||
if let Some((used_primitive_type, _)) = info.mesh_shader_info.primitive_type {
|
||||
if used_primitive_type != mesh_info.primitive_output_type {
|
||||
return Err(EntryPointError::WrongMeshOutputType
|
||||
.with_span_handle(mesh_info.primitive_output_type, &module.types));
|
||||
}
|
||||
}
|
||||
|
||||
self.validate_mesh_output_type(
|
||||
ep,
|
||||
module,
|
||||
mesh_info.vertex_output_type,
|
||||
MeshOutputType::VertexOutput,
|
||||
)?;
|
||||
self.validate_mesh_output_type(
|
||||
ep,
|
||||
module,
|
||||
mesh_info.primitive_output_type,
|
||||
MeshOutputType::PrimitiveOutput,
|
||||
)?;
|
||||
} else {
|
||||
// This is not a `Mesh` entry point, so ensure that it never tries to produce
|
||||
// vertices or primitives.
|
||||
if info.mesh_shader_info.vertex_type.is_some()
|
||||
|| info.mesh_shader_info.primitive_type.is_some()
|
||||
{
|
||||
return Err(EntryPointError::UnexpectedMeshShaderOutput.with_span());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +188,8 @@ bitflags::bitflags! {
|
||||
const SHADER_FLOAT16_IN_FLOAT32 = 1 << 28;
|
||||
/// Support for fragment shader barycentric coordinates.
|
||||
const SHADER_BARYCENTRICS = 1 << 29;
|
||||
/// Support for task shaders, mesh shaders, and per-primitive fragment inputs
|
||||
const MESH_SHADER = 1 << 30;
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +282,8 @@ bitflags::bitflags! {
|
||||
const VERTEX = 0x1;
|
||||
const FRAGMENT = 0x2;
|
||||
const COMPUTE = 0x4;
|
||||
const MESH = 0x8;
|
||||
const TASK = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -220,9 +220,12 @@ const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
|
||||
use crate::AddressSpace as As;
|
||||
match space {
|
||||
As::Function | As::Private => TypeFlags::ARGUMENT,
|
||||
As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => {
|
||||
TypeFlags::empty()
|
||||
}
|
||||
As::Uniform
|
||||
| As::Storage { .. }
|
||||
| As::Handle
|
||||
| As::PushConstant
|
||||
| As::WorkGroup
|
||||
| As::TaskPayload => TypeFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
functions: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(1),
|
||||
requirements: (""),
|
||||
@ -413,10 +413,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(1),
|
||||
requirements: (""),
|
||||
@ -1591,12 +1595,16 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
entry_points: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(1),
|
||||
requirements: (""),
|
||||
@ -1685,6 +1693,10 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
const_expression_types: [
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
functions: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -1197,10 +1197,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -2523,10 +2527,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -2563,10 +2571,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -2612,10 +2624,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -2655,10 +2671,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -2749,10 +2769,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -2870,10 +2894,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -2922,10 +2950,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -2977,10 +3009,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -3029,10 +3065,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -3084,10 +3124,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -3148,10 +3192,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(2),
|
||||
requirements: (""),
|
||||
@ -3221,10 +3269,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(2),
|
||||
requirements: (""),
|
||||
@ -3297,10 +3349,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -3397,10 +3453,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(1),
|
||||
requirements: (""),
|
||||
@ -3593,12 +3653,16 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
entry_points: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -4290,10 +4354,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -4742,10 +4810,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(0),
|
||||
requirements: (""),
|
||||
@ -4812,6 +4884,10 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
const_expression_types: [
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
functions: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(3),
|
||||
requirements: (""),
|
||||
@ -275,12 +275,16 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
entry_points: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: Some(3),
|
||||
requirements: (""),
|
||||
@ -430,6 +434,10 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
const_expression_types: [],
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
entry_points: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -201,6 +201,10 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
const_expression_types: [
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
entry_points: [
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -184,10 +184,14 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
(
|
||||
flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE"),
|
||||
available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"),
|
||||
uniformity: (
|
||||
non_uniform_result: None,
|
||||
requirements: (""),
|
||||
@ -396,6 +400,10 @@
|
||||
sampling: [],
|
||||
dual_source_blending: false,
|
||||
diagnostic_filter_leaf: None,
|
||||
mesh_shader_info: (
|
||||
vertex_type: None,
|
||||
primitive_type: None,
|
||||
),
|
||||
),
|
||||
],
|
||||
const_expression_types: [],
|
||||
|
||||
@ -196,6 +196,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -266,6 +266,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -974,6 +974,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -984,6 +985,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
@ -994,6 +996,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -1032,6 +1035,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -1252,6 +1252,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -1262,6 +1263,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
@ -1272,6 +1274,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -1310,6 +1313,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -151,6 +151,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
offset: 0,
|
||||
),
|
||||
@ -510,6 +511,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -520,6 +522,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -530,6 +533,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
@ -613,6 +617,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -242,6 +242,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
offset: 0,
|
||||
),
|
||||
@ -616,6 +617,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -626,6 +628,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@ -636,6 +639,7 @@
|
||||
interpolation: None,
|
||||
sampling: None,
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
),
|
||||
],
|
||||
@ -719,6 +723,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -2655,6 +2655,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "foo_frag",
|
||||
@ -2672,6 +2674,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -2848,6 +2851,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "foo_compute",
|
||||
@ -2907,6 +2912,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -2655,6 +2655,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "foo_frag",
|
||||
@ -2672,6 +2674,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -2848,6 +2851,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "foo_compute",
|
||||
@ -2907,6 +2912,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -334,6 +334,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -334,6 +334,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -73,6 +73,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: Some(0),
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [
|
||||
|
||||
@ -73,6 +73,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: Some(0),
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [
|
||||
|
||||
@ -465,6 +465,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -465,6 +465,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -100,6 +100,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -100,6 +100,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -201,6 +201,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -201,6 +201,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -128,6 +128,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -128,6 +128,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -263,6 +263,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -263,6 +263,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -221,6 +221,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -221,6 +221,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -218,6 +218,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "csStore",
|
||||
@ -315,6 +317,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -218,6 +218,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "csStore",
|
||||
@ -315,6 +317,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -190,6 +190,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -190,6 +190,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -394,6 +394,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -416,6 +417,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "vertex_main",
|
||||
@ -452,6 +455,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "compute_main",
|
||||
@ -483,6 +488,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -394,6 +394,7 @@
|
||||
interpolation: Some(Perspective),
|
||||
sampling: Some(Center),
|
||||
blend_src: None,
|
||||
per_primitive: false,
|
||||
)),
|
||||
)),
|
||||
local_variables: [],
|
||||
@ -416,6 +417,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "vertex_main",
|
||||
@ -452,6 +455,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
(
|
||||
name: "compute_main",
|
||||
@ -483,6 +488,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -116,6 +116,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -172,6 +172,8 @@
|
||||
],
|
||||
diagnostic_filter_leaf: None,
|
||||
),
|
||||
mesh_info: None,
|
||||
task_payload: None,
|
||||
),
|
||||
],
|
||||
diagnostic_filters: [],
|
||||
|
||||
@ -1094,6 +1094,8 @@ impl Interface {
|
||||
wgt::ShaderStages::VERTEX => naga::ShaderStage::Vertex,
|
||||
wgt::ShaderStages::FRAGMENT => naga::ShaderStage::Fragment,
|
||||
wgt::ShaderStages::COMPUTE => naga::ShaderStage::Compute,
|
||||
wgt::ShaderStages::MESH => naga::ShaderStage::Mesh,
|
||||
wgt::ShaderStages::TASK => naga::ShaderStage::Task,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -1238,7 +1240,7 @@ impl Interface {
|
||||
}
|
||||
|
||||
// check workgroup size limits
|
||||
if shader_stage == naga::ShaderStage::Compute {
|
||||
if shader_stage.compute_like() {
|
||||
let max_workgroup_size_limits = [
|
||||
self.limits.max_compute_workgroup_size_x,
|
||||
self.limits.max_compute_workgroup_size_y,
|
||||
|
||||
@ -2197,6 +2197,9 @@ impl super::Adapter {
|
||||
if features.contains(wgt::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN) {
|
||||
capabilities.push(spv::Capability::RayQueryPositionFetchKHR)
|
||||
}
|
||||
if features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER) {
|
||||
capabilities.push(spv::Capability::MeshShadingEXT);
|
||||
}
|
||||
if self.private_caps.shader_integer_dot_product {
|
||||
// See <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_integer_dot_product.html#_new_spir_v_capabilities>.
|
||||
capabilities.extend(&[
|
||||
|
||||
@ -231,7 +231,33 @@ impl RenderPass<'_> {
|
||||
self.inner.draw_indexed(indices, base_vertex, instances);
|
||||
}
|
||||
|
||||
/// Draws using a mesh shader pipeline
|
||||
/// Draws using a mesh pipeline.
|
||||
///
|
||||
/// The current pipeline must be a mesh pipeline.
|
||||
///
|
||||
/// If the current pipeline has a task shader, run it with an workgroup for
|
||||
/// every `vec3<u32>(i, j, k)` where `i`, `j`, and `k` are between `0` and
|
||||
/// `group_count_x`, `group_count_y`, and `group_count_z`. The invocation with
|
||||
/// index zero in each group is responsible for determining the mesh shader dispatch.
|
||||
/// Its return value indicates the number of workgroups of mesh shaders to invoke. It also
|
||||
/// passes a payload value for them to consume. Because each task workgroup is essentially
|
||||
/// a mesh shader draw call, mesh workgroups dispatched by different task workgroups
|
||||
/// cannot interact in any way, and `workgroup_id` corresponds to its location in the
|
||||
/// calling specific task shader's dispatch group.
|
||||
///
|
||||
/// If the current pipeline lacks a task shader, run its mesh shader with a
|
||||
/// workgroup for every `vec3<u32>(i, j, k)` where `i`, `j`, and `k` are
|
||||
/// between `0` and `group_count_x`, `group_count_y`, and `group_count_z`.
|
||||
///
|
||||
/// Each mesh shader workgroup outputs a set of vertices and indices for primitives.
|
||||
/// The indices outputted correspond to the vertices outputted by that same workgroup;
|
||||
/// there is no global vertex buffer. These primitives are passed to the rasterizer and
|
||||
/// essentially treated like a vertex shader output, except that the mesh shader may
|
||||
/// choose to cull specific primitives or pass per-primitive non-interpolated values
|
||||
/// to the fragment shader. As such, each primitive is then rendered with the current
|
||||
/// pipeline's fragment shader, if present. Otherwise, [No Color Output mode] is used.
|
||||
///
|
||||
/// [No Color Output mode]: https://www.w3.org/TR/webgpu/#no-color-output
|
||||
pub fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32) {
|
||||
self.inner
|
||||
.draw_mesh_tasks(group_count_x, group_count_y, group_count_z);
|
||||
@ -264,7 +290,7 @@ impl RenderPass<'_> {
|
||||
.draw_indexed_indirect(&indirect_buffer.inner, indirect_offset);
|
||||
}
|
||||
|
||||
/// Draws using a mesh shader pipeline,
|
||||
/// Draws using a mesh pipeline,
|
||||
/// based on the contents of the `indirect_buffer`
|
||||
///
|
||||
/// This is like calling [`RenderPass::draw_mesh_tasks`] but the contents of the call are specified in the `indirect_buffer`.
|
||||
|
||||
@ -152,13 +152,15 @@ static_assertions::assert_impl_all!(FragmentState<'_>: Send, Sync);
|
||||
pub struct TaskState<'a> {
|
||||
/// The compiled shader module for this stage.
|
||||
pub module: &'a ShaderModule,
|
||||
/// The name of the entry point in the compiled shader to use.
|
||||
|
||||
/// The name of the task shader entry point in the shader module to use.
|
||||
///
|
||||
/// If [`Some`], there must be a vertex-stage shader entry point with this name in `module`.
|
||||
/// Otherwise, expect exactly one vertex-stage entry point in `module`, which will be
|
||||
/// selected.
|
||||
/// If [`Some`], there must be a task shader entry point with the given name
|
||||
/// in `module`. Otherwise, there must be exactly one task shader entry
|
||||
/// point in `module`, which will be selected.
|
||||
pub entry_point: Option<&'a str>,
|
||||
/// Advanced options for when this pipeline is compiled
|
||||
|
||||
/// Advanced options for when this pipeline is compiled.
|
||||
///
|
||||
/// This implements `Default`, and for most users can be set to `Default::default()`
|
||||
pub compilation_options: PipelineCompilationOptions<'a>,
|
||||
@ -238,7 +240,43 @@ static_assertions::assert_impl_all!(RenderPipelineDescriptor<'_>: Send, Sync);
|
||||
|
||||
/// Describes a mesh shader (graphics) pipeline.
|
||||
///
|
||||
/// For use with [`Device::create_mesh_pipeline`].
|
||||
/// For use with [`Device::create_mesh_pipeline`]. A mesh pipeline is very much
|
||||
/// like a render pipeline, except that instead of [`RenderPass::draw`] it is
|
||||
/// invoked with [`RenderPass::draw_mesh_tasks`], and instead of a vertex shader
|
||||
/// and a fragment shader:
|
||||
///
|
||||
/// - [`task`] specifies an optional task shader entry point, which determines how
|
||||
/// many groups of mesh shaders to dispatch.
|
||||
///
|
||||
/// - [`mesh`] specifies a mesh shader entry point, which generates groups of
|
||||
/// primitives to draw
|
||||
///
|
||||
/// - [`fragment`] specifies as fragment shader for drawing those primitives,
|
||||
/// just like in an ordinary render pipeline.
|
||||
///
|
||||
/// The key difference is that, whereas a vertex shader is invoked on the
|
||||
/// elements of vertex buffers, the task shader gets to decide how many mesh
|
||||
/// shader workgroups to make, and then each mesh shader workgroup gets to
|
||||
/// decide which primitives it wants to generate, and what their vertex
|
||||
/// attributes are. Task and mesh shaders can use whatever they please as
|
||||
/// inputs, like a compute shader. However, they cannot use specialized vertex
|
||||
/// or index buffers.
|
||||
///
|
||||
/// A mesh pipeline is invoked by [`RenderPass::draw_mesh_tasks`], which looks
|
||||
/// like a compute shader dispatch with [`ComputePass::dispatch_workgroups`]:
|
||||
/// you pass `x`, `y`, and `z` values indicating the number of task shaders to
|
||||
/// invoke in parallel. The output value of the first thread in a task shader
|
||||
/// workgroup determines how many mesh workgroups should be dispatched from there.
|
||||
/// Those mesh workgroups also get a special payload passed from the task shader.
|
||||
///
|
||||
/// If the task shader is omitted, then the (`x`, `y`, `z`) parameters to
|
||||
/// `draw_mesh_tasks` are used to decide how many invocations of the mesh shader
|
||||
/// to invoke directly, without a task payload.
|
||||
///
|
||||
/// [vertex formats]: wgpu_types::VertexFormat
|
||||
/// [`task`]: Self::task
|
||||
/// [`mesh`]: Self::mesh
|
||||
/// [`fragment`]: Self::fragment
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MeshPipelineDescriptor<'a> {
|
||||
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification.
|
||||
@ -263,8 +301,15 @@ pub struct MeshPipelineDescriptor<'a> {
|
||||
///
|
||||
/// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout
|
||||
pub layout: Option<&'a PipelineLayout>,
|
||||
/// The compiled task stage, its entry point, and the color targets.
|
||||
|
||||
/// The mesh pipeline's task shader.
|
||||
///
|
||||
/// If this is `None`, the mesh pipeline has no task shader. Executing a
|
||||
/// mesh drawing command simply dispatches a grid of mesh shaders directly.
|
||||
///
|
||||
/// [`draw_mesh_tasks`]: RenderPass::draw_mesh_tasks
|
||||
pub task: Option<TaskState<'a>>,
|
||||
|
||||
/// The compiled mesh stage and its entry point
|
||||
pub mesh: MeshState<'a>,
|
||||
/// The properties of the pipeline at the primitive assembly and rasterization level.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user