The Work Graph Playground is a DirectX12-based C++-application that allows graphics programmers to learn and experiment with the new Work Graphs feature using HLSL shaders. It runs on Windows 10 and Windows 11 systems.
Work Graphs is a Graphics-API feature released in March 2024 for DirectX12. In a nutshell, with Work Graphs, shaders can dynamically schedule new workloads at runtime directly from the GPU. Prior to Work Graphs, all GPU workloads had to be scheduled by the CPU. Therefore, Work Graphs can reduce memory requirements, improve caching behavior, better utilize compute resources, reduce communication-needs between CPU-and-GPU, and simplify synchronization.
From a programmer's perspective, Work Graphs extend the host-side of the API, but for most of the time, programmers deal with the shader-code part introduced with Work Graphs.
We provide several tutorials to walk you through the HLSL-usage of Work Graphs. As most of the power of Work Graphs is unleashed through HLSL, our tutorials focus on the HLSL aspects of Work Graphs. Our Work Graph Playground frees you from dealing with the host-side of Work Graphs. However, you can use our host-side code as a reference for integrating Work Graphs in your own projects.
In each tutorial, we cover one aspect of Work Graphs. The tutorials build upon each other, so we recommend taking them one-by-one. You need just a few prerequisites to run the tutorials.
By the end of the tutorials, if not even earlier, you should be inspired to create your own Work Graphs samples and grow from there! If you want to experiment with Work Graphs, check out Adding new tutorials below on how to add your own samples or tutorials to the playground application. Be sure to check out the additional resources and samples linked below if you wish to learn more about Work Graphs.
As a person taking this tutorial, you need to know HLSL, C++, Direct3D12, and have a basic understanding of how GPU compute shaders work.
Besides a computer, you need:
- A text/code editor of your choice
- A Windows version that supports the Microsoft Agility SDK
- Optional: Graphics diagnostic tools for debugging.
To run the sample directly, you'll also need a GPU and driver with D3D12 Work Graphs 1.0 support. You can learn more about D3D12 Work Graphs 1.0 and driver availability on Microsoft's blog post or on our own blog post on GPUOpen.com.
If your GPU does not support Work Graphs, you can download and install the DirectX WARP adapter by running the DownloadWarpAdapter.bat
, which is included in the pre-built binaries.
The script performs the following steps:
- Download the Microsoft.Direct3D.WARP 1.0.14.2 NuGet package.
If you wish to use the mesh nodes feature, download version 1.0.14.1-preview instead.
BY INSTALLING THE THIRD-PARTY SOFTWARE, YOU AGREE TO BE BOUND BY THE LICENSE AGREEMENT(S) APPLICABLE TO SUCH SOFTWARE and you agree to carefully review and abide by the terms and conditions of all license(s) that govern such software.
- Change the file extension to
.zip
, such that the full filename ismicrosoft.direct3d.warp.1.0.14.2.zip
ormicrosoft.direct3d.warp.1.0.14.1-preview.zip
if you downloaded the version for mesh nodes. - Open or extract the zip file, locate the
d3d10warp.dll
inbuild/native/bin/x64
, and copy it next to theWorkGraphPlayground.exe
.
If you're building the application from source, the steps above are automated by the CMake build script.
Download the latest release from here or follow the instructions for building the application from source.
Start the WorkGraphPlayground.exe
either directly or from the command line.
You can pass the following options to WorkGraphPlayground.exe
:
--forceWarpAdapter
uses the WARP adapter, even if your GPU does support Work Graphs. If you're using pre-built binaries, you'll need to download and install the WARP adapter first. See instructions above.--enableDebugLayer
to enable the D3D12 Debug Layer (recommended).--enableGpuValidationLayer
to turn on D3D12 GPU validation.
You should see the following application window:
The tutorials only consist of HLSL shader code and are located in the tutorials
folder.
The shader files are automatically reloaded at runtime whenever any changes are detected, meaning you don't have to restart the application whenever you modify the shader source code.
You will see this in action in the first tutorial.
If the shader compilation fails, the previous (successfully) compiled shader code is used. Any error messages or other output from the shader compiler are displayed in the application output log.
We recommend running the app with the --enableDebugLayer
command-line argument to also see any further error messages from the D3D12 debug layer. Note that Graphics diagnostic tools must be installed in order to enable the debug layer.
Description: This is a minimal introductory tutorial. You get acquainted with our tutorial playground and two very simple Work Graphs nodes. Your task is to launch one worker node from the entry node and print your name from the worker node. You can directly edit the shader file in an editor of your choice. Upon saving, you experience that our playground will automatically reload and recompile your changes made in the shader file.
Learning Outcome: You get a feeling for our playground application, shader hot reloading, and reassure that our tutorials run on your device. You learn how to
- mark HLSL functions as Work Graphs nodes with
Shader("node")
- add further attributes to shader functions, and
- get a first glimpse on how to invoke other nodes
References to Specification:
- Shader Target
- Shader Function Attributes
- See
EmptyNodeOutput
in Node output declaration
Description: Work Graphs use records to model data-flow. Records serve as inputs and outputs of nodes. In this tutorial, you emit records at a producer node and receive it at different consumer nodes. Your producer node issues multiple consumer nodes that render different things. You parameterize the nodes with records.
Learning Outcome: Besides getting a better understanding of how EmptyNodeOutput
works, you learn how to
- declare non-empty records with
NodeOutput
at a producer node; - use the
MaxRecords
attribute to cap the number of record outputs at compile time; - identify situations when to use
GroupNodeOutputRecords
andThreadNodeOutputRecords
to output records; - output records during runtime with
GroupIncrementOutputCount
;ThreadIncrementOutputCount
, andOutputComplete
; - obtain zero or one output record with
GetThreadNodeOutputRecords
(per thread)GetGroupNodeOutputRecords
(per thread-group); - obtain records at a receiving node with
ThreadNodeInputRecord
; and - read and write data to records with the
Get()
-method or the[]
-operator.
References to Specification:
- Objects
- Node output declaration
- Node input declaration
- Methods operating on node output
- Record access
Description: You can launch Work Graphs nodes in three different ways: "thread", "coalescing", and "broadcast". From an entry node, you will launch three different nodes, each using a different launch mode.
Learning Outcome: You will learn
- how and when to use
NodeMaxDispatchGrid
and what the difference toNodeDispatchGrid
is; - how to use
DispatchNodeInputRecord
as input for nodes launched withbroadcast
; - how to access individual threads with
SV_DispatchThreadID
for nodes launched inbroadcast
; and - how to obtain input for nodes launched in
coalescing
launch mode throughGroupNodeInputRecords
.
References to Specification:
Description: The "Classify-and-Execute" pattern is commonly found in graphics. With "Classify-and-Execute", you first determine the class of a work item, and depending the classification result, you execute different shaders. In this tutorial, we use a basic shading example: As work item, we use a pixel that covers a ray-surface intersection. First, a work graph node "classifies" the work-items, i.e., the pixel, into three different material classes. Finally, we "execute" one of three Work-Graph nodes, depending the classification result. We use the Work-Graphs concept Node Arrays to elegantly solve this "Classify-and-Execute" problem.
Learning Outcome: You will learn how to
- use
NodeOutputArray
to declare that output records are in fact node arrays; - set the maximum number of output classes that
NodeOutputArray
emits withNodeArraySize
; - obtain the classified record
ThreadNodeOutputRecords
withGetThreadNodeOutputRecords
; - prepare different Nodes and their HLSL functions for use with Node Arrays by extending
NodeId
with an index; and - properly use
ThreadNodeInputRecord
for NodeArrays to obtain the input.
References to Specification:
- Output record objects
- Objects
- Node input declaration
- Node output declaration
GetThreadNodeOutputRecords
NodeId
Description: Nodes can issue records not only for other nodes, but also for themselves. This is called Work Graph Recursion. It supports trivial cycles, i.e., node A can issue work for node A again. However, one limitation is though, that a node A cannot issue work to nodes from which A has already received records from, even transitively. That means, non-trivial cycles are also disallowed. Also the recursion depth is limited. However, fractals are a great example to try out trivial cycles for self-recursion! You see how Work Graphs compute a simple fractal, the Koch Snowflake. You get to compute a second fractal, see Menger Sponge.
Learning Outcome: You learn how to implement trivial cycles, by
- Configure the maximum recursion depth for trivial cycles using
NodeMaxRecursionDepth
; - use
GetRemainingRecursionLevels
to terminate recursion; and - recursively call the calling node.
References to Specification:
Description: When you want that different thread-groups of a broadcasting node communicate amongst each other, Work Graphs offer an input record type RWDispatchNodeInputRecord
which is also writeable.
Previously, we've seen DispatchNodeInputRecord
which is only readable.
We'll use an RWDispatchNodeInputRecord
to store the bounding-box of an object that many threads within a broadcasting node compute cooperatively.
One thread then gets to draw the bounding box.
Learning Outcome: You learn
- to use
RWDispatchNodeInputRecord
such that you can read- and write input records; - how to, when to, and why to apply the
globallycoherent
attribute onRWDispatchNodeInputRecord
records; - to read and write
RWDispatchNodeInputRecord
s with atomic operations at the example of InterlockedMin and InterlockedMax; - to prepare record-structs with the
NodeTrackRWInputSharing
-attribute for the usage ofFinishedCrossGroupSharing
; and - how to synchronize the input record across all the thread-groups of a broadcast launch with
Barrier
andFinishedCrossGroupSharing
.
References to Specification:
- Shaders can use input record lifetime for scoped scratch storage
- Input Record objects
- Node input declaration
- Node input attributes
- Record struct
FinishedCrossGroupSharing
- Objects
- Barrier
Description:
Another pattern commonly found in computer graphics is recursive subdivision of a geometric primitive. Among the countless examples, we picked computing a Mandelbrot set. We provide the algorithmic part of that in the header-file Mandlebrot.h
and a "brute-force" solution. However, a simple property of Mandelbrot sets gives raise to a subdivision algorithm that optimizes the computation of the Mandelbrot set. Use Work Graphs to exploit this property and make the algorithm more efficient.
Learning Outcome: In this final tutorial, we would like to see you try out your Work Graphs expertise. Your learning outcome should be that your able to solve a common graphics problem with Work Graphs. From there on, you should become able to assess for what tasks Work Graphs are a fit for you.
References to Specification:
Mesh Nodes are an experimental preview feature of the D3D12 API, thus syntax and semantics, performance characteristics, or driver support might all be subject to change in the future.
Prerequisites
To be able to run tutorials that require the Mesh Nodes feature, you need a version of the Work Graph Playground that was built with Mesh Node support enabled.
You can either find these in the latest releases denoted with the -MeshNodes
suffix, or build it from source following the instructions below.
You also need a GPU and driver with D3D12 Work Graphs 1.1 (Mesh Nodes) support. You can learn more about driver availability on Microsoft's announcement blog post or on our own blog post on GPUOpen.com.
If your GPU does not support Work Graphs and/or Mesh Nodes, you can use the WARP software adapter instead by following the instructions above. Note that you need to download version 1.0.14.1-preview for mesh node support. If you build the Playground from source, this version is automatically downloaded.
As Mesh Nodes are currently an experimental preview feature, you’ll also need to enable the Developer Mode in Windows.
- Under Windows 10, this can be found under Settings > Update & Security > For Developers.
- Under Windows 11, this can be found under Settings > Privacy & Security > For Developers.
Mesh Nodes enable full mesh-shading pipelines, comprised of a mesh shader, pixel shader, and pipeline state to be added to and called in a Work Graph. The tutorials in the Work Graph Playground focus more on how mesh nodes interact with the already existing Work Graph concepts and briefly cover writing the actual mesh shaders. If you wish to learn more about mesh shaders and how to get the optimal performance out of them, we recommend you to check out the following resources:
The Work Graph Playground focuses on teaching and exploring the GPU-side aspect and shader authoring for GPU Work Graphs. However, Mesh Nodes require significant changes to the CPU-side setup process for Work Graphs, which are not covered in our tutorials here. If you wish to learn more about compiling and creating a Work Graph with Mesh Nodes, you can check out blog post on Mesh Nodes or the WorkGraphsHelloWorkGraphs sample. The first mesh node tutorial is also based on this sample.
Adding a Mesh Node
Unlike regular compute nodes, which only require a [Shader("node")]
attribute to be added to function, Mesh Nodes require additional state.
This state needs to be set on the CPU when creating the work graph in the form of a generic program.
To allow for adding mesh nodes without editing and recompiling the CPU-side code, we opted for the following approach:
- Any function in the tutorial source code with a
[Shader("node")]
attribute andMeshShader
suffix in the function name is treated as the mesh shader of a new mesh node. - We expect the pixel shader to have the same function name, but with a
PixelShader
suffix, i.e., in the example below,MyMeshShader
andMyPixelShader
are used as mesh- and pixel-shader for a new mesh node with node id("MyMeshNode", 0)
, as declared by theNodeId
attribute.If no[Shader("node")] [NodeLaunch("mesh")] [NodeId("MyMeshNode", 0)] ... void MyMeshShader(...) { ... } float4 MyPixelShader(...) : SV_Target0 { ... }
NodeId
attribute is present, the node id is derived from the mesh shader function name, i.e., in this exampleMyMeshShader
.
If noMyPixelShader
function is found, the mesh node will not use any pixel shader, and thus will not be able to write to the color render target. - All mesh nodes use the same pipeline state:
- Counter-clockwise triangles without any front- or back-face culling.
- A single RGBA render target
SV_Target0
. - All fragments are tested against and written to the depth buffer.
- For more details on the pipeline state and mesh- and pixel-shader matching, please see WorkGraph.cpp.
Mesh nodes render their output into a different color target than the one available as RWTexture2D<float4> RenderTarget
, used by the print function for example.
However, RenderTarget
can still be used and written to if mesh nodes are used in the work graph and the output is composited over the Mesh Node output once the Work Graph execution has finished.
Reading the output of Mesh Nodes is not possible.
Multi-sampling of the mesh node render target can be enabled by passing --msaa <sample count>
as a command-line argument.
Description: Mesh Nodes allow mesh shader pipelines, comprised of a mesh shader, an optional pixel shader, and graphics pipeline state to be included and called inside a Work Graph. In this tutorial, you learn how to add Mesh Nodes to your graph in the Work Graph Playground. This tutorial is based on the Hello Mesh Nodes sample and uses the recursive Koch snowflake fractal from tutorial 4.
Learning Outcome: You learn:
- how to declare mesh shaders for mesh nodes,
- the differences between regular mesh shaders and mesh nodes,
- the Work Graph Playground naming convention for matching and converting mesh- and pixel-shader pairs to mesh nodes, and
- how to write simple mesh- and pixel shaders for mesh nodes.
References to Specification:
To add a new tutorial, create a new folder inside the tutorials
folder.
The position of your tutorial in the tutorial list in the application UI will be based on this folder name.
If you wish to keep your new tutorial separate, you can also create a new tutorial folder.
Inside this folder, create a new .hlsl
file. The filename in camel-case will be used as a name for the tutorial (e.g., MyNewTutorial.hlsl
will result in My New Tutorial
).
If you wish to provide a sample solution, create a second .hlsl
file with the suffix Solution
(e.g., MyNewTutorialSolution.hlsl
).
If your tutorial requires more than one file, you can always create additional .h
header files and include them in your tutorial files.
Restart the application to see your tutorial appear in the tutorial list.
Each tutorial must define a node named Entry
with no input record.
This node will be invoked once per frame.
The Common.h
header file provides access to shader resources (output render target & scratch buffers) and utility methods for drawing text or primitives (lines & rectangles).
Shaders are compiled using the Microsoft DirectX shader compiler with the following arguments:
-T lib_6_8 -enable-16bit-types -HV 2021 -Zpc -I./tutorials/
See ShaderCompiler.cpp for more details.
- Follow the instructions under Building from Source to build the Work Graph Playground locally.
- Start by creating a new folder with your desired name in the repository.
- Open
CMakeLists.txt
and locate the section marked with:
# Add new tutorial folders here
near the bottom of the file.
4. Add a function call to add_tutorial_folder
with your folder name:
add_tutorial_folder(./path/to/your/folder "My Tutorial Prefix" "Shader Source Files/My New Tutorials")
Tutorials in your folder will then show up in the UI as My Tutorial Prefix #: Tutorial Name
and appear in the solution under Shader Source Files/My New Tutorials
.
5. Re-run the configuration command from Building from Source (e.g., cmake -B build .
) to reconfigure the solution with your new folder.
6. In Visual Studio, build and run the Work Graph Playground
project.
7. Follow the instructions for Adding New Tutorials to add tutorials to your new folder.
Here, we show how you can directly build our Work Graph Playground from source.
- CMake 3.17
- Visual Studio 2019
- Windows 10 SDK 10.0.18362.0
- A windows version that supports the Microsoft Agility SDK
Clone the repository, including ImGui submodule:
git clone https://github.com/GPUOpen-LibrariesAndSDKs/WorkGraphPlayground.git --recurse-submodules
Configuring with CMake:
cmake -B build .
If you want to enable mesh node support, configure the project with PLAYGROUND_ENABLE_MESH_NODES
enabled:
cmake -B build . -D PLAYGROUND_ENABLE_MESH_NODES=ON
This command will download the following NuGet packages:
In order to use this software, you may need to have certain third party software installed on your system. Whether you install this software directly or whether a script is provided that, when executed by you, automatically fetches and installs software onto your system BY INSTALLING THE THIRD PARTY SOFTWARE, YOU AGREE TO BE BOUND BY THE LICENSE AGREEMENT(S) APPLICABLE TO SUCH SOFTWARE and you agree to carefully review and abide by the terms and conditions of all license(s) that govern such software. You acknowledge and agree that AMD is not distributing to you any of such software and that you are solely responsible for the installation of such software on your system.
Opening VS Solution:
cmake --open build
In Visual Studio, build and run the Work Graph Playground
project.
See adding new tutorials to add new tutorials. Re-run cmake -B build .
to add any new files to the Visual Studio solution.
While Work Graphs is a new feature, there are already some resources available.
Work Graphs General:
- GPU Work Graphs in Microsoft DirectX® 12
- Work graphs API – compute rasterizer learning sample
- GDC 2024 - GPU Work Graphs: Welcome to the Future of GPU Programming
- HPG 2024 - Work Graphs: Hands-On with the Future of Graphics Programming
Mesh Nodes and Mesh Shaders:
- GDC 2024 - Work Graphs and draw calls – a match made in heaven!
- GPU Work Graphs mesh nodes in Microsoft DirectX® 12
- HPG 2024 - Real-Time Procedural Generation with GPU Work Graphs
- Mesh shaders on AMD RDNA™ graphics cards
- GDC 2024 - Mesh Shaders in AMD RDNA™ 3 Architecture
Work Graphs Samples: