Skip to content

Commit 0cffc1f

Browse files
committed
Updated taskgraph wiki
1 parent 417cf24 commit 0cffc1f

File tree

1 file changed

+81
-196
lines changed

1 file changed

+81
-196
lines changed

src/content/docs/wiki/taskgraph.md

Lines changed: 81 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
22
title: TaskGraph
33
description: TaskGraph
4-
slug: wiki/taskgraph
4+
slug: taskgraph
5+
editUrl: https://github.com/learndaxa/Wiki/edit/main/docs/taskgraph.md
56
---
67

78
## TaskGraph
@@ -49,7 +50,7 @@ To know how to generate sync and when its safe to reorder commands, the tg build
4950

5051
#### Usage Implications
5152

52-
when a task is added, tg will immediately form new access dependencies for all resources assigned to attachments of that task.
53+
When a task is added, tg will immediately form new access dependencies for all resources assigned to attachments of that task.
5354

5455
Example:
5556

@@ -96,86 +97,51 @@ A Task consists of four parts:
9697

9798
Notably, the graph works in two phases: the recording and the execution. The callbacks of tasks are only ever called in the execution of the graph, not the recording.
9899

99-
There are two ways to declare a task. You can declare tasks inline, directly inside the add_task function:
100+
Example of a task:
100101

101102
```cpp
102103
daxa::TaskImageView src = ...;
103104
daxa::TaskImageView dst = ...;
104105
int blur_width = ...;
105-
graph.add_task({
106-
.attachments = {
107-
daxa::inl_attachment(daxa::TaskImageAccess::TRANSFER_READ, src),
108-
daxa::inl_attachment(daxa::TaskImageAccess::TRANSFER_WRITE, dst),
109-
},
110-
.task = [=](daxa::TaskInterface ti)
111-
{
112-
copy_image_to_image(ti.recorder, ti.id(src), ti.id(dst), blur_width);
113-
},
114-
.name = "example task",
115-
});
106+
graph.add_task(daxa::Task::Transfer("example task")
107+
.reads(src) // adds attachment for src to the task
108+
.writes(dst) // adds attachment for dst to the task
109+
.executes([=](daxa::TaskInterface ti){
110+
copy_image_to_image(ti.recorder, ti.id(src), ti.id(dst), blur_width);
111+
}));
116112
```
117113
118-
> NOTE: There is a third defaulted parameter to inl_attachment, taking in the VIEW_TYPE for the image.
119-
> When filling this VIEW_TYPE parameter, task graph will create an image view that exactly fits
120-
> the dimensions of the attachments view slice.
121-
> When this parameter is defaulted, daxa will fill the image view id with 0.
122-
> How to access these tg generated image views is shown later.
123-
124-
This is convenient for smaller tasks or quick additions that don't necessarily need shaders.
125-
126-
The other way to declare tasks (using "task heads") is shown later.
127-
128114
### Task Attachments
129115
130116
Attachments describe a list of used graph resources that might require synchronization between tasks.
131117
132-
> Note: Any resource that is readonly for the execution of the task, like textures, do not need to be mentioned in the attachments.
118+
> Note: Only make attachments for resources that need sync. Textures that are uploaded and synched once after upload for example should be ignored in the graph.
133119
134120
Each attachment consists of:
121+
- the resources type (image/buffer/acceleration structure)
122+
- the resources access (stage + read/write/sampled)
123+
- the resources shader usage (id/index/ptr + image view type)
135124
136-
- a `task resource access` (either `TaskBufferAccess` or `TaskImageAccess`),
137-
- a description of how the resource is meant to be used in a shader,
138-
- an attachment index
139-
140-
For persistent tasks this is obvious, take `DAXA_TH_IMAGE` as an example:
125+
TaskGraph will use this information to automatically generate sync, reorder tasks and automatically fill push constants with your resources.
141126
142-
`DAXA_TH_IMAGE(TaskImageAccess, ImageViewType, TaskImageAttachmentIndexName)`.
143-
144-
TaskGraph will use all this information to generate optimal synchronization and ordering of tasks, based on the attachments and assigned resource views.
145-
146-
Inline tasks omit some of these and set them do default values. When listing an inline attachment, one also directly assigns the view to the attachment as well.
127+
> the automatic push constant/buffer fill is only available via TaskHeads (described later)
147128
148129
### TaskInterface
149130
150-
The interface provides functions to query information about the graph, attachments and task itself.
151-
152-
For example to get the runtime information for a given attachment the interface has the `get` function.
131+
The resources assigned to each attachment of tasks are not available or even created yet when recording the task. They might also change between graph executions!
153132
154-
It takes a resource view or an attachment index directly.
133+
So the only up to date and correct information about each task resource and attachment is available ONLY when the task callback is executed and ONLY accessible via the task interface.
155134
156-
It returns a `TaskAttachmentInfo` (`TaskBufferAttachmentInfo` for buffers and `TaskImageAttachmentInfo` for images), this struct contains all data about the attachment given on construction as well as runtime data used by the graph.
135+
The interface has functions to query all information on the resources behind the attachments, such as: id, image view, buffer device/host address, image layout, resource inf, task view.
157136
158-
This includes:
137+
Aside from gett9ing attachment information, the interface is used to get:
138+
* current device
139+
* current command recorder
140+
* current buffer suballocator (may be used to allocate small sections of a ring buffer in each task)
141+
* current task metadata (name,index,queue)
142+
* current attachment shader blob (described later)
159143
160-
- views assigned to attachments
161-
- runtime daxa resource ids
162-
- runtime daxa resource view ids (these are created by the graph based on the attachment view type)
163-
- image layout
164-
165-
The get function alone can make the code verbose. The TaskInterface provides many helper functions such as:
166-
* `id`
167-
* `view`
168-
* `info`
169-
* `device_address`
170-
171-
Aside from attachment information the interface also provides:
172-
173-
- a command recorder (automatically reused by the graph)
174-
- a transfer memory allocator (super fast per execution linear allocator for mapped gpu memory)
175-
- attachment shader data (generated from the list of attachments, can be send to shader)
176-
- task metadata such as the name and index
177-
178-
### TaskHead
144+
### TaskHead and Attachment Shader Blob
179145
180146
When using shader resources like buffers and images, one must transport the image id or buffer pointer to the shader. In traditional apis one would bind buffers and images to an index but in daxa these need to be in a struct that is either stored inside another buffer or directly within a push constant.
181147
@@ -234,65 +200,14 @@ struct MyTaskHead
234200
};
235201
```
236202

237-
In c++ this macro declares a namespace containing a few constexpr static variables.
238-
In the following code i omittied some code as it is hard to read/understand on the spot:
239-
240-
```c++
241-
namespace MyTaskHead
242-
{
243-
/* TEMPALTE MAGIC */
244-
245-
// Number of declared attachments:
246-
static inline constexpr daxa::usize ATTACHMENT_COUNT = {/* TEMPLATE MAGIC */};
247-
248-
// Generated Types:
249-
static inline constexpr auto ATTACHMENTS_T = {/* TEMPLATE MAGIC */};
250-
static inline constexpr auto VIEWS_T = {/* TEMPLATE MAGIC */};
251-
252-
// Attachment meta information:
253-
static inline constexpr auto ATTACHMENTS = {/* TEMPLATE MAGIC */};
254-
255-
// Short alias for attachment meta information:
256-
static inline constexpr auto const & AT = ATTACHMENTS;
257-
258-
// Shader byte blob with the exact size and alignment of the equivalent shader struct:
259-
struct alignas(daxa::get_asb_alignment(AT)) AttachmentShaderBlob
260-
{
261-
std::array<daxa::u8, daxa::get_asb_size(AT)> value = {};
262-
};
263-
264-
// Partially declared task, already defining some functions,
265-
// also getting some fields into the task structs namespace:
266-
struct Task : public daxa::IPartialTask
267-
{
268-
using AttachmentViews = ATTACHMENTS_T;
269-
using Views = VIEWS_T;
270-
static constexpr AttachmentsStruct<ATTACHMENT_COUNT> const & AT = ATTACHMENTS;
271-
static constexpr daxa::usize ATTACH_COUNT = ATTACHMENT_COUNT;
272-
static auto name() -> std::string_view { return std::string_view{NAME}; }
273-
static auto attachments() -> std::span<daxa::TaskAttachment const>
274-
{
275-
return AT._internal.values;
276-
}
277-
static auto attachment_shader_blob_size() -> daxa::u32
278-
{
279-
return sizeof(daxa::get_asb_size(AT));
280-
};
281-
};
282-
}
283-
284-
```
285-
286203
Extended example using a task head:
287204

288205
```c
289206
// within shared file
290207

291-
DAXA_DECL_TASK_HEAD_BEGIN(MyTaskHead)
292-
// buffer attachment: cpu side daxa::TaskBufferAccess: shader side pointer type: buffer name:
293-
DAXA_TH_BUFFER_PTR( COMPUTE_SHADER_READ, daxa_BufferPtr(daxa_u32), src_buffer)
294-
// image attachment: cpu side daxa::TaskImageAccess: cpu side daxa::ImageViewType: image name:
295-
DAXA_TH_IMAGE_ID( COMPUTE_SHADER_WRITE, REGULAR_2D, dst_image)
208+
DAXA_DECL_COMPUTE_TASK_HEAD_BEGIN(ExampleTaskHead)
209+
DAXA_TH_BUFFER_PTR( READ, daxa_BufferPtr(daxa_u32), src_buffer)
210+
DAXA_TH_IMAGE_ID( WRITE, REGULAR_2D, dst_image)
296211
DAXA_DECL_TASK_HEAD_END
297212

298213
// This push constant is shared in shader and c++!
@@ -301,10 +216,7 @@ struct MyPushStruct
301216
daxa_u32vec2 size;
302217
daxa_u32 settings_bitfield;
303218
// The head field is an aligned byte array in c++ and the attachment struct in shader:
304-
// Slang:
305-
MyTaskHead::AttachmentShaderBlob attachments;
306-
// Glsl:
307-
DAXA_TH_BLOB(MyTaskHead, attachments);
219+
DAXA_TH_BLOB(ExampleTaskHead, attachments);
308220
};
309221
```
310222
@@ -342,42 +254,25 @@ Example usage of the above task:
342254

343255
```c++
344256

345-
daxa::ImageViewId some_img_view = ...;
346-
daxa::BufferViewId some_buf_view = ...;
347-
348-
task_graph.add_task(MyTask{
349-
.views = MyTask::Views{
350-
.src_buffer = some_img_view,
351-
.dst_image = some_img_view,
352-
},
353-
.other_stuff = ...,
354-
});
355-
```
356-
357-
Daxa automatically generates a struct type `MyTask::Views` for syntactic suggar when assigning views. Its a struct with one field for each declared attachment. Each field is of the type `TaskAttachmentViewWrapper<T>` which accept task resource views.
358-
359-
### Alternative Use Of TaskHead
360-
361-
Task heads can also be directly used in inline tasks without having to declare a struct inheriting the task:
362-
363-
```c++
364-
365-
daxa::ImageViewId some_img_view = ...;
366-
daxa::BufferViewId some_buf_view = ...;
367-
368-
using MyTask = daxa::InlineTaskWithHead<MyTaskHead::Task>
369-
370-
task_graph.add_task(MyTask{
371-
.views = MyTask::Views{
372-
.src_buffer = some_img_view,
373-
.dst_image = some_img_view,
374-
},
375-
.task = [=](daxa::TaskInterface ti) { ... },
376-
});
257+
daxa::BufferViewId src = ...;
258+
daxa::ImageViewId dst = ...;
377259

260+
graph.add_task(daxa::HeadTask<ExampleTaskHead::Info>("example task")
261+
.head_views({.src_buffer = src}) // assign the view to the attachment, access is defined in head
262+
.head_views({.dst_image = dst}) // assign the view to the attachment, access is defined in head
263+
.executes([=](daxa::TaskInterface ti){
264+
auto const AT =
265+
ti.recorder.set_pipeline(...);
266+
ti.recorder.push_constant(MyPushStruct{
267+
.size = ...,
268+
.settings_bitfield = ...,
269+
// Here you assign the graph generated attachment shader blob into your pushconstant
270+
.attachments = ti.attachment_shader_blob,
271+
});
272+
ti.dispatch(...);
273+
}));
378274
```
379275
380-
381276
### TaskInterface and Attachment Information
382277
383278
The ATTACHMENTS or AT constants declared within the task head contain all metadata about the attachments.
@@ -391,53 +286,51 @@ Note that all these functions also take views directly instead of attachments in
391286
These indices can also be used to access information of attachments within the task callback:
392287
393288
```c++
394-
void callback(daxa::TaskInterface ti)
289+
void example_task_callback(daxa::TaskInterface ti)
395290
{
291+
auto const & AI = ExampleTaskHead::ATTACHMENT_INDICES;
292+
396293
// There are two ways to get the info for any attachment:
397294
{
398295
// daxa::TaskBufferAttachmentIndex index:
399-
[[maybe_unused]] daxa::TaskBufferAttachmentInfo const & buffer0_attachment0 = ti.get(AT.src_buffer);
296+
[[maybe_unused]] daxa::TaskBufferAttachmentInfo const & buffer0_attachment0 = ti.get(AI.buffer0);
400297
// daxa::TaskBufferView assigned to the buffer attachment:
401298
[[maybe_unused]] daxa::TaskBufferAttachmentInfo const & buffer0_attachment1 = ti.get(buffer0_attachment0.view);
402299
}
403300
// The Buffer Attachment info contents:
404301
{
405-
// Information retrieved from convenience functions:
406-
[[maybe_unused]] daxa::BufferId id_ = ti.id(AT.src_buffer, /*optional*/0);
407-
[[maybe_unused]] daxa::DeviceAddress address_ = ti.device_address(AT.src_buffer, /*optional*/0).value();
408-
[[maybe_unused]] std::byte* host_address = ti.buffer_host_address(AT.src_buffer).value();
409-
[[maybe_unused]] daxa::BufferInfo info_ = ti.info(AT.src_buffer, /*optional*/0).value();
410-
411-
// Information retrieved from the .get meta function:
412-
[[maybe_unused]] std::span<daxa::BufferId const> ids = ti.get(AT.src_buffer).ids;
413-
[[maybe_unused]] daxa::BufferId id = ti.get(AT.src_buffer).ids[0];
414-
[[maybe_unused]] char const * name = ti.get(AT.src_buffer).name;
415-
[[maybe_unused]] daxa::TaskBufferAccess access = ti.get(AT.src_buffer).access;
416-
[[maybe_unused]] u8 shader_array_size = ti.get(AT.src_buffer).shader_array_size;
417-
[[maybe_unused]] bool shader_as_address = ti.get(AT.src_buffer).shader_as_address;
418-
[[maybe_unused]] daxa::TaskBufferView view = ti.get(AT.src_buffer).view;
302+
[[maybe_unused]] daxa::BufferId id = ti.get(AI.buffer0).ids[0];
303+
[[maybe_unused]] char const * name = ti.get(AI.buffer0).name;
304+
[[maybe_unused]] daxa::TaskAccess access = ti.get(AI.buffer0).task_access;
305+
[[maybe_unused]] u8 shader_array_size = ti.get(AI.buffer0).shader_array_size;
306+
[[maybe_unused]] bool shader_as_address = ti.get(AI.buffer0).shader_as_address;
307+
[[maybe_unused]] daxa::TaskBufferView view = ti.get(AI.buffer0).view;
308+
[[maybe_unused]] std::span<daxa::BufferId const> ids = ti.get(AI.buffer0).ids;
419309
}
420310
// The Image Attachment info contents:
421311
{
422-
// Information retrieved from convenience functions:
423-
[[maybe_unused]] daxa::ImageId id_ = ti.id(AT.dst_image, /*optional*/0);
424-
[[maybe_unused]] daxa::ImageViewId view_ = ti.view(AT.dst_image, /*optional*/0);
425-
[[maybe_unused]] daxa::ImageViewInfo info_ = ti.info(AT.dst_image, /*optional*/0).value();
426-
427-
// Information retrieved from the .get meta function:
428-
[[maybe_unused]] char const * name = ti.get(AT.dst_image).name;
429-
[[maybe_unused]] daxa::TaskImageAccess access = ti.get(AT.dst_image).access;
430-
[[maybe_unused]] daxa::ImageViewType view_type = ti.get(AT.dst_image).view_type;
431-
[[maybe_unused]] u8 shader_array_size = ti.get(AT.dst_image).shader_array_size;
432-
[[maybe_unused]] daxa::TaskHeadImageArrayType shader_array_type = ti.get(AT.dst_image).shader_array_type;
433-
[[maybe_unused]] daxa::ImageLayout layout = ti.get(AT.dst_image).layout;
434-
[[maybe_unused]] daxa::TaskImageView view = ti.get(AT.dst_image).view;
435-
[[maybe_unused]] std::span<daxa::ImageId const> ids = ti.get(AT.dst_image).ids;
436-
437-
/// WARNING: ImageViews are only filled out for attachments that set the VIEW_TYPE!
438-
/// If you use an inline attachment and dont specify the VIEW_TYPE like for a transfer op,
439-
/// there will be an empty daxa::ImageViewId{} put into this array for the attachment!
440-
[[maybe_unused]] std::span<daxa::ImageViewId const> view_ids = ti.get(AT.dst_image).view_ids;
312+
[[maybe_unused]] char const * name = ti.get(AI.image0).name;
313+
[[maybe_unused]] daxa::TaskAccess access = ti.get(AI.image0).task_access;
314+
[[maybe_unused]] daxa::ImageViewType view_type = ti.get(AI.image0).view_type;
315+
[[maybe_unused]] u8 shader_array_size = ti.get(AI.image0).shader_array_size;
316+
[[maybe_unused]] daxa::TaskHeadImageArrayType shader_array_type = ti.get(AI.image0).shader_array_type;
317+
[[maybe_unused]] daxa::ImageLayout layout = ti.get(AI.image0).layout;
318+
[[maybe_unused]] daxa::TaskImageView view = ti.get(AI.image0).view;
319+
[[maybe_unused]] std::span<daxa::ImageId const> ids = ti.get(AI.image0).ids;
320+
[[maybe_unused]] std::span<daxa::ImageViewId const> view_ids = ti.get(AI.image0).view_ids;
321+
}
322+
// The interface has multiple convenience functions for easier access to the underlying resources attributes:
323+
{
324+
// Overloaded for buffer, blas, tlas, image
325+
[[maybe_unused]] daxa::BufferInfo info = ti.info(AI.buffer0).value();
326+
// Overloaded for buffer, blas, tlas
327+
[[maybe_unused]] daxa::DeviceAddress address = ti.device_address(AI.buffer0).value();
328+
329+
[[maybe_unused]] std::byte * host_address = ti.buffer_host_address(AI.buffer0).value();
330+
[[maybe_unused]] daxa::ImageViewInfo img_view_info = ti.image_view_info(AI.image0).value();
331+
332+
// In case the task resource has an array of real resources, one can use the optional second parameter to access those:
333+
[[maybe_unused]] daxa::BufferInfo info2 = ti.info(AI.buffer0, 123 /*resource index*/).value();
441334
}
442335
// The attachment infos are also provided, directly via a span:
443336
for ([[maybe_unused]] daxa::TaskAttachmentInfo const & attach : ti.attachment_infos)
@@ -447,10 +340,10 @@ void callback(daxa::TaskInterface ti)
447340
[[maybe_unused]] auto generated_blob = ti.attachment_shader_blob;
448341
// The head also declared an aligned struct with the right size as a dummy on the c++ side.
449342
// This can be used to declare shader/c++ shared structs containing this blob :
450-
[[maybe_unused]] TestTaskHead::AttachmentShaderBlob blob = {};
343+
[[maybe_unused]] ExampleTaskHead::AttachmentShaderBlob blob = {};
451344
// The blob also declares a constructor and assignment operator to take in the byte span generated by the taskgraph:
452345
blob = generated_blob;
453-
[[maybe_unused]] TestTaskHead::AttachmentShaderBlob blob2{ti.attachment_shader_blob};
346+
[[maybe_unused]] ExampleTaskHead::AttachmentShaderBlob blob2{ti.attachment_shader_blob};
454347
}
455348
```
456349

@@ -491,14 +384,6 @@ There are multiple ways to declare how a resource is used within the shader:
491384
// Ptr Attachments are represented by a device address.
492385
#define DAXA_TH_BUFFER_PTR(TASK_ACCESS, PTR_TYPE, NAME)
493386
#define DAXA_TH_TLAS_PTR(TASK_ACCESS, NAME)
494-
495-
// _MIP_ARRAY Attachments will be represented as an array of ids/indices where each array element
496-
// refers to a RUNTIME resource in the runtime resource array PER resource.
497-
#define DAXA_TH_BUFFER_ID_ARRAY(TASK_ACCESS, NAME, SIZE)
498-
#define DAXA_TH_BUFFER_PTR_ARRAY(TASK_ACCESS, PTR_TYPE, NAME, SIZE)
499-
#define DAXA_TH_IMAGE_ID_ARRAY(TASK_ACCESS, VIEW_TYPE, NAME, SIZE)
500-
#define DAXA_TH_IMAGE_INDEX_ARRAY(TASK_ACCESS, VIEW_TYPE, NAME, SIZE)
501-
#define DAXA_TH_IMAGE_TYPED_ARRAY(TASK_ACCESS, TEX_TYPE, NAME, SIZE)
502387
```
503388

504389
> Note: Some permutations are missing here. BLAS for example has no \_ID, \_INDEX or \_PTR version. This is intentional, as some resources can not be used in certain ways inside shaders.

0 commit comments

Comments
 (0)