You are browsing as a guest. Sign up (or log in) to start making projects!

9h 58m 6s logged

DEVLOG 12 - Render Graph, Code Refactoring

 
A Little Recap
In the last devlog, I mentioned I watched the 2017 GDC conference, where they explained how the Frostbite development team had created a Render Graph. But what is a Render Graph, and what is it used for? To answer this question, we must first understand how the GPU works.
 
The Difference: CPU & GPU
First, a GPU, unlike a CPU, performs all its operations in parallel. When we write code for the CPU, we say: do this operation, then this, then that, and so on. The CPU performs its operations synchronously, in sequential order. The GPU, however, doesn’t work that way. If we tell the GPU that we want to draw three objects, we can send it three draw calls, which we’ll call A, B, and C. These draw calls are sent in order, so first A, then B, and then C. The order in which the commands are sent doesn’t correspond to the order in which they are executed, because the GPU could execute the commands in this order: C, B, A, or B, A, C, or A, C, B, and so on. This is because, while operation A might be in progress, operation B might start executing, so both commands will use the same resources and therefore run into a race condition if both operations are not read-only.
 
The Problem
From this perspective, the GPU is very powerful because it tries to stay constantly busy, eliminating dead time. However, this greatly complicates things on the synchronization side. Let’s take two operations: operation A that writes an image and operation B that reads the image. How can we be sure that the GPU executes operation A first and not B? Who can assure us that the GPU, while executing A, doesn’t have to execute B? The answer to all these questions is no one.
 
How Graphics APIs Handle All Of This
In graphics APIs like OpenGL, all of this is hidden from the programmer, so they don’t have to worry about it. But in graphics APIs like Vulkan and DirectX 12, this is the programmer’s responsibility. The programmer must write a barrier and tell the GPU not to touch the image from operation B before A has finished. For a few barriers, this is feasible, but as soon as you write more complex applications, making the code sufficiently organized, non-repetitive, and fast to develop becomes very challenging. This is why the concept of a Render Graph exists.
 
What A Render Graph Does
A Render Graph allows the programmer to specify which steps the GPU should execute, which operations resources like images and textures will perform, and in what order. The Frame Graph looks at this graph constructed by the programmer at each frame and internally understands where barriers and sometimes semaphores are needed (if we’re working with multiple queues, that’s not the case for me). Another responsibility of the Frame Graph is to internally allocate and manage resources that only exist within a single frame, called transient resources. Finally, I’m implementing Memory Aliasing. This reduces the memory used by resources: for example, if resource A is used for the last time in pass 3 and resource B for the first time in pass 4, they can share the same physical memory, thus reducing the overall memory requirement.
 
Bonus
I’ve also restructured the texture creation methods, making them simpler and allowing for fewer specifications. The before and after is a good result… The same goes for 1D and 3D textures.

0
4

Comments 0

No comments yet. Be the first!