////
- Copyright (c) 2023, Holochip Corporation
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////
= Mesh Shader Culling

ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/mesh_shader_culling[Khronos Vulkan samples github repository].
endif::[]


image::./images/mesh_shader_culling.png[Mesh Shader Culling]

== Overview

This sample demonstrates how to incorporate the Vulkan extension https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_EXT_mesh_shader[`VK_EXT_mesh_shader`], and introduces per primitive culling in a mesh shader.

== Contents

1) <<enabling-mesh-shading,enable the mesh shader extension>>  2) <<creating-pipeline,create a mesh shading graphic pipeline>>  3) <<mesh-shader,generate a simple mesh using meshlets>>  4) <<mesh-shader-culling,establish a basic cull logic for the meshlets.>>

== Enabling mesh shading

To enable the mesh shading feature, the following  extensions are required: (NB: `VK_API_VERSION_1_1` is required as a base requirement)

1) `VK_KHR_SPIRV_1_4_EXTENSION_NAME` 2) `VK_EXT_MESH_SHADER_EXTENSION_NAME` 3) `VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME`

To enable task shaders and mesh shaders enable the  following flags from the `VkPhysicalDeviceMeshShaderFeaturesEXT` feature.
1) `taskShader` 2) `meshShader`

== Pipeline Creation

When working with Mesh shader pipelines, Vertex Input  State and Input Assembly state are ignored.
This is  because the mesh pipeline has the responsibility of  defining/creating the vertex information that the  standard fragment pipeline utilizes.

The mesh pipeline can create its own vertices as is  done in this sample.
Or it can receive them from the  application the same way one would for compute shaders  when working with models.

Thus, we disable the `pVertexInputState` and `pInputAssemblyState` by setting them to NULL.

== Linking resources

In this sample, we use a UBO (Uniform Buffer Object) to  set the settings for the culling.

[,cpp]
----
	struct UBO
	{
		float cull_center_x   = 0.0f;
		float cull_center_y   = 0.0f;
		float cull_radius     = 1.75f;
		float meshlet_density = 2.0f;
	} ubo_cull{};
----

* `cull_center_x` and `cull_center_y` determines  the translation of the cull mask
* ``cull_radius``defines the size of the cull mask.
* `meshlet_density` defines the total number of meshlets used for the sample.

== Task Shader

A task shader is an optional but recommended stage responsible for launching mesh shaders.
It has two purposes:

* Decide how many mesh shaders to launch in the workgroup.
* Create a task payload that mesh shaders will have read-only access to.
 ** taskPayloadSharedEXT type variable in GLSL can exist at most once it gets implicitly used by EmitMeshTasksEXT  and behaves like shared memory in the task shader and like a read-only SSBO in a mesh shader.

In general, one should use task shaders anytime you use the mesh pipeline.
While it is "optional,"  their use is  strongly encouraged to get the most out of the mesh shading pipeline.
The only time one wouldn't use them is for  very simplistic scenes such as rendering a single triangle found in the link:../mesh_shading[mesh shader sample].

[,glsl]
----
// Example of the data shared with its associated mesh shader:
// 1) define some structure if more than one variable data sharing is desired:
// Please note: GPU vendors recommend to use as little task payload as possible, eg. by packing the data to fewer bits etc.
struct SharedData
{
    vec4  positionTransformation;
    int   N;
    int   meshletsNumber;
    float subDimension;
    float cullRadius;
};
// 2) use the following variable with a storage class specifier to "establish the connection"
taskPayloadSharedEXT SharedData sharedData;
----

Once a variable is defined with taskPayloadSharedEXT in the task shader, it will be shared with the mesh shader when  EmitMeshTasksEXT is called.

In general, the Mesh pipeline refers to a new pipeline  which replaces everything before the fragment shader  with an (optional) task shading stage that can call other mesh  shading stages.

* A task shader (optional) is used to launch mesh shader workgroup(s)
* A mesh shader has the responsibility to generate  primitives and vertices.

More details about emitting a mesh task can be found in the attached article:

https://www.khronos.org/blog/mesh-shading-for-vulkan[Mesh Shading For Vulkan] https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/[Introduction to Turing Mesh Shaders]

GPU manufactures have recommended best practices for  their hardware in setting the work group and mesh size  number.
Further reading can be found here:

* https://zeux.io/2023/01/16/meshlet-size-tradeoffs/[Meshlet Size tradeoffs]
* NVIDIA - https://on-demand.gputechconf.com/gtc-eu/2018/pdf/e8515-mesh-shaders-in-turing.pdf[Mesh Shaders in Turing]
* AMD - https://gpuopen.com/wp-content/uploads/slides/AMD_RDNA2_DirectX12_Ultimate_SamplerFeedbackMeshShaders.pdf[Sampler feedback ultimate in Mesh shaders]
* https://timur.hu/blog/2022/mesh-and-task-shaders[Timur's blog]

== Mesh Shader

Task and mesh shaders are executed in workgroups similar to compute shaders.
Each task shader workgroup can launch  many mesh shader workgroups.
Each mesh shader workgroup is responsible for generating vertices and primitives.
The  API allows for any logic, but a typical application it is recommended to organize this around meshlets, which are a  small group of vertices and primitives.
Typically, each task shader invocation processes a group of meshlets and  each mesh shader workgroup processes one meshlet.
The vertices and primitives generation process can be found in the following code:

[,glsl]
----
// Vertices:
gl_MeshVerticesEXT[k * 4 + 0].gl_Position = vec4(2.0 * sharedData.subDimension * unitVertex_0, 0.0f, 1.0f) + sharedData.positionTransformation + displacement;
gl_MeshVerticesEXT[k * 4 + 1].gl_Position = vec4(2.0 * sharedData.subDimension * unitVertex_1, 0.0f, 1.0f) + sharedData.positionTransformation + displacement;
gl_MeshVerticesEXT[k * 4 + 2].gl_Position = vec4(2.0 * sharedData.subDimension * unitVertex_2, 0.0f, 1.0f) + sharedData.positionTransformation + displacement;
gl_MeshVerticesEXT[k * 4 + 3].gl_Position = vec4(2.0 * sharedData.subDimension * unitVertex_3, 0.0f, 1.0f) + sharedData.positionTransformation + displacement;
// Indices
gl_PrimitiveTriangleIndicesEXT[k * 2 + 0] = unitPrimitive_0 + k * uvec3(4);
gl_PrimitiveTriangleIndicesEXT[k * 2 + 1] = unitPrimitive_1 + k * uvec3(4);
// Assigning the color output:
vec3 color = vec3(1.0f, 0.0f, 0.0f) * (k + 1) / sharedData.meshletsNumber;
outColor[k * 4 + 0] = color;
outColor[k * 4 + 1] = color;
outColor[k * 4 + 2] = color;
outColor[k * 4 + 3] = color;
----

More details of meshlets generation can be found in the attached article:

https://developer.nvidia.com/blog/using-mesh-shaders-for-professional-graphics/[Using Mesh Shaders for Professional Graphics]

== Per-primitive culling

This sample uses a simple per-primitive cull functionality from the mesh  shader.
The intention in mesh shading is to only generate geometry that is relevant to the scene.

In this sample, a circular visual zone is centered at the origin, with an adjustable radius, controlled by the gui.
When a primitive moves out of the visual zone, its generation process will be skipped.

[,glsl]
----
// the actual position of each meshlet:
vec4 position = displacement + sharedData.positionTransformation;
float squareRadius = position.x * position.x + position.y * position.y;
// Cull Logic: only if the meshlet center position is within the view circle defined by the cull radius,
// then the meshlet will be generated.
if (squareRadius < sharedData.cullRadius * sharedData.cullRadius)
{
    // Generating meshlets
}
----

Please note that per mesh culling should be done in the task shader and used to prevent mesh shaders from even  launching.
The simplistic culling method demonstrated here is not the most ideal use of culling in mesh shaders and  infact is discouraged due to limited benefit.
Instead please opt for limiting the number of mesh shaders that  require launching by doing the cull within the task shader.

More advanced culling solutions can be found in the following video:

https://www.youtube.com/watch?v=n3cnUHYGbpw[Culling with NVIDIA Mesh Shaders]
