Skip to content

Commit af8ecd6

Browse files
Based-AadamlIceSentryZeophlite
authored
Render Architecture Overview & Pipelined Rendering Book Pages (#2469)
Co-authored-by: adaml <adam-lundrigan@proton.me> Co-authored-by: IceSentry <IceSentry@users.noreply.github.com> Co-authored-by: Daniel Skates <zeophlite@gmail.com>
1 parent 1329624 commit af8ecd6

2 files changed

Lines changed: 218 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
+++
2+
title = "Bevy's Rendering Architecture"
3+
insert_anchor_links = "right"
4+
[extra]
5+
weight = 1
6+
+++
7+
8+
Bevy's renderer consists of a series of modular, performant, and cross-platform-compatible tools.
9+
It provides industry standard features while still remaining accessible and approachable to users who aren't very familiar with graphics programming.
10+
Ultimately, the goal of Bevy's renderer is to let users construct the visual style their game needs.
11+
After all, if users can't create the look they want, then what is even the point!
12+
13+
Even though Bevy's core audience is game developers, we know that Bevy is used much more broadly.
14+
We won't be able to satisfy every use case by default; this is why we focus on modularity.
15+
Bevy's renderer should be flexible enough that it can be used to make pixel art, stylized 2D, high-end PBR, CAD visualizations, or anything else you can think of.
16+
This also means we want users to be able to build any features they need that Bevy doesn't offer by default.
17+
If a feature isn't implemented directly, you should be able to build on what already exists, or have the space to create that functionality yourself.
18+
19+
Before we start detailing _how_ you can start using the many features of Bevy's renderer, it'll help to understand _what_ the renderer actually is and _why_ it's set up the way it is.
20+
Understanding these concepts will allow you to get a bird's eye view of the rendering process and let you know where to look when you start working with the renderer.
21+
22+
## The Graphics Stack
23+
24+
Modern computers can come with some seriously powerful hardware.
25+
GPUs in particular have evolved into the workhorses of modern games, utilizing thousands, or even tens of thousands of cores to render complex graphics at unbelievable speeds.
26+
But the GPU doesn't inherently know how to output a "photo-realistic pirate ship" or a "voxel world filled with cubed creatures".
27+
It's ultimately up to you to create those scenes and find that "perfect look".
28+
29+
To help with achieving that "perfect look", Bevy is built to let you interact with as much of the rendering process as you want.
30+
You can set everything up within Bevy itself, allowing our tools to handle the dirty work.
31+
Alternatively, you could bypass Bevy's abstractions and dive straight into the graphics APIs, wielding all the rendering power yourself!
32+
33+
This is enabled by a series of layers that send data from your game to the computer's GPU.
34+
At a high level, those layers (affectionately called "the stack") looks something like this:
35+
36+
1. Bevy's Renderer
37+
2. Abstraction Layer (WGPU)
38+
3. Graphics API (Vulkan, Metal, DirectX12, WebGPU, etc.)
39+
4. GPU Driver (AMD, NVIDIA, Intel, Apple, etc.)
40+
5. GPU Hardware (The actual graphics card or integrated graphics device)
41+
42+
Every frame, your game will send data to the renderer to determine what should be rendered to the player's screen.
43+
By default, Bevy uses an abstraction tool to translate this data into the computer's underlying graphics API calls (Vulkan, Metal, DirectX12, WebGPU, etc.).
44+
Finally, these calls are sent and then processed by the GPU driver before being executed on the GPU hardware itself.
45+
The end result is a frame being rendered by the GPU and displayed on the screen!
46+
47+
## WGPU
48+
49+
[WGPU](https://wgpu.rs/) is a modern, low-level graphics library.
50+
It's an implementation of the [WebGPU API](https://webgpu.org/) specification, a cross-platform graphics API originally designed to enable GPU accelerated rendering within web browsers.
51+
However, it isn't just for web browsers.
52+
Using an implementation of WebGPU means that an application can natively target every modern graphics API released after 2010, namely Vulkan, DirectX12, Metal, WebGL2, or WebGPU depending on the system.
53+
54+
Bevy uses WGPU (via the [`wgpu`](https://crates.io/crates/wgpu) crate) to unify its rendering process.
55+
Instead of having to individually support each graphics API, WGPU gives Bevy a unified interface for rendering.
56+
As long as you're targeting a supported platform (Windows, MacOS, Linux, Android, and iOS), WGPU can translate your requests into the appropriate graphics API calls.
57+
58+
{% callout(type="info") %}
59+
### Why WGPU?
60+
61+
Now that we've gone over what Bevy uses for its rendering setup, you might be wondering why Bevy chooses to rely on a third-party tool instead of creating our own solution.
62+
After all, we know what type of rendering setup we want, what platforms we want to support, and what features we want to implement.
63+
We could write our own abstractions over the individual graphics APIs, and expose those features through a custom API.
64+
A Bevy-native rendering solution might even give us better performance by integrating core engine features more closely.
65+
66+
While there would be benefits to creating a Bevy-native setup, the truth of the matter is that WGPU already occupies the space that Bevy currently needs.
67+
By choosing to rely on WGPU, we're removing several burdens: supporting multiple graphics APIs, implementing unified features across those APIs, detecting hardware-specific limitations, and so on.
68+
We also gain quite a lot by sharing knowledge and learning resources with WGPU rather than having to create our own bespoke material.
69+
Ultimately, it's about finding a solution that meets Bevy's needs while removing unnecessary burdens from our maintainers: WGPU is currently the best fit for those goals.
70+
71+
{% end %}
72+
73+
This chapter focuses on Bevy's own rendering features, but a broader base of knowledge is quite useful, and you may occasionally want to interact with WGPU directly for advanced features.
74+
If you are interested in learning more about WGPU, we can point you towards [Learn WGPU](https://sotrh.github.io/learn-wgpu/), a series of tutorials that can help you learn more about working with WGPU and graphics programming concepts in general.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
+++
2+
title = "Pipelined Rendering"
3+
insert_anchor_links = "right"
4+
[extra]
5+
weight = 2
6+
+++
7+
8+
Traditional rendering methods usually rely on the "simulation" logic (game state, path-finding, physics, etc.) being computed and then "rendered" to the screen after.
9+
This is a linear process, and can be restrictive: the simulation can't progress until the render is finished, and the render can't start until the simulation is complete.
10+
While this does still work for less intensive or older games, the abilities of modern computer hardware have outgrown this approach.
11+
Both simulations and rendering logic have become more complicated in recent years, meaning that it isn't performant for one process to wait for the other to finish.
12+
13+
**Pipeline Rendering** is an alternative approach that sees the main simulation logic decoupled from the render logic.
14+
Instead of waiting on each other, the simulation logic is run on one CPU thread while the render logic is copied to a separate thread before being passed to the GPU to be rendered.
15+
This allows the simulation to execute concurrently with the render, meaning less "idle" time for both processes.
16+
17+
Bevy implements a version of pipelined rendering using two separate `SubApp`s to track the compute and render logic separately.
18+
Both `SubApp`s abide by ECS principles and have their own `World`s, schedules, and systems to control how their respective logic is executed.
19+
Specifically, the Main `SubApp` (where our Main `World` lives) calculates the simulation logic.
20+
Then, the freshly computed info in the Main `SubApp` is extracted into the Render `SubApp`.
21+
This extraction happens once per frame, and can delay the game if either the game logic in the Main `SubApp` or the render process in the Render `SubApp` takes longer than the other.
22+
23+
{% callout(type="info") %}
24+
25+
#### GPU-Driven Vs CPU-Driven Rendering
26+
27+
Determining what parts of a scene should be rendered is just as important as actually rendering the scene itself.
28+
It's likely that some objects will obstruct or overlap others, and figuring out how those objects should be shown on screen can be an intensive process.
29+
Like we mentioned above though, rendering has evolved to take advantage of the power that modern GPUs possess.
30+
Instead of relying on the CPU (as was done in the past), modern rendering setups are shifting more intensive work onto the GPU itself.
31+
32+
This can be thought of as CPU-driven rendering versus GPU-driven rendering.
33+
CPU-driven rendering is where the draw commands that determine how objects are rendered are created on the CPU, and only passed to the GPU to eventually be rendered.
34+
On the other hand, GPU-driven rendering sees the GPU perform those computations itself; the CPU is only responsible for passing the data to the GPU.
35+
36+
By default, Bevy uses GPU driven rendering, however the CPU is still available to be used if desired.
37+
38+
{% end %}
39+
40+
## The Rendering Process
41+
42+
The Render `SubApp` follows a series of steps to carry out the rendering process.
43+
Each step has a specific purpose in arranging and converting your games data into rendered frames.
44+
However, it's also possible to skip certain steps if their function isn't explicitly required.
45+
46+
Let's look at the main four steps that occur during rendering:
47+
48+
### Extract
49+
50+
To render an image to the screen, we first have to figure out what is happening in our game!
51+
We use the Extract step to copy information from the Main `SubApp` and send it into the Render `SubApp`.
52+
This happens every frame, so reducing the amount of data that is copied can help improve the performance of your game.
53+
54+
How do we actually send data to the Render `SubApp`?
55+
One way is to derive the [`ExtractComponent`] trait on a component that we want to transfer.
56+
This will automatically insert a [`SyncToRenderWorld`] component on an entity that receives the specified component.
57+
During the [`ExtractSchedule`], entities with the `SyncToRenderWorld` component are copied over into the Render `World`.
58+
59+
```rust
60+
// Derive `ExtractComponent` on a component.
61+
#[derive(Component, ExtractComponent)]
62+
struct ImageHandle {
63+
handle: Handle<Image>,
64+
}
65+
66+
// Or, manually insert `SyncToRenderWorld` on an entity.
67+
fn transfer_to_render_world(
68+
mut commands: Commands,
69+
mut entity_query: Query<Entity, With<ImageHandle>>
70+
) {
71+
for entity in entity_query.iter_mut() {
72+
commands.entity(entity).insert((SyncToRenderWorld));
73+
}
74+
}
75+
```
76+
77+
Once copied over, a connection between the Render `SubApp` entity and their Main `SubApp` counterpart is created by storing a component with their counterparts entity ID.
78+
[`RenderEntity`] is inserted into the entity residing in the Main `SubApp` and contains the Render `SubApp` entity ID, while [`MainEntity`] is inserted into the entity residing in the Render `SubApp` and contains the Main `SubApp` entity ID.
79+
80+
Alternatively, if you only need a specific type of asset (like a texture or a mesh), you can load an [`Asset`].
81+
`Asset`s that are loaded in your Main `SubApp` are automatically copied over in the `ExtractSchedule`.
82+
Bevy is able to do this by reading the associated [`AssetEvent`] messages that the `Asset` type emits when registered and loaded.
83+
You can read more about how `Asset`s are loaded and handled by Bevy in the [dedicated Assets chapter](/learn/book/assets).
84+
85+
### Prepare & Queue
86+
87+
All the data that we just copied is just that - data.
88+
We need to prepare the data by organizing it into [`BindGroup`]s, collections that point to an objects data stored in [`Buffer`]s.
89+
Bevy is able to perform this for us, setting up each `BindGroup` using a [`BindGroupLayout`] to make accessing that data more convenient.
90+
91+
After our initial data is organized, the actual jobs that the renderer has to do are created.
92+
It might be overwhelming to think about everything that has to be queued, but you can think of it as arranging a group of objects onto different layers (called a render "phase") using "instructions".
93+
The data we copied contains the descriptions for each object in a scene (like a `Transform` component for where it's located in a scene).
94+
Each render phase we set up contains the "instructions" for how a part of those objects should look when viewed from a specific camera.
95+
For example, an opaque phase describes how the objects are arranged relative to each other from the camera's perspective, and a transparency phase describes if some objects can be seen through other objects.
96+
97+
Remember that data that's been assigned into [`BindGroup`]s?
98+
We use these groupings to represent the individual objects that the camera sees in each render phase (aptly called [`PhaseItem`]s).
99+
Each `PhaseItem` is assigned a [`Draw`] function, which is the final combination of instructions for how that item will be displayed on screen.
100+
Finally, for each `Draw` function, a draw call is created which will tell the GPU what to display on the screen.
101+
102+
### Render
103+
104+
This is where the magic finally happens!
105+
All the queued [`Draw`] functions for every [`PhaseItem`]s are executed, incorporating the specified [`RenderPipeline`]s and [`BindGroup`]s that help us get the desired look.
106+
107+
Since the Render `SubApp` abides by the ECS principles, we can leverage systems to perform the actual task of rendering.
108+
Each system that accesses the [`RenderContext`] system parameter can be used to create the commands that are sent to the GPU to render the game.
109+
Using systems allows us to structure the execution of each system with a [`Schedule`], either those provided by Bevy by default ([`Core2d`] and [`Core3d`]), or by creating a custom render schedule.
110+
111+
### Cleanup
112+
113+
A lot of data can get copied into the Render `SubApp`, which can quickly get out of hand if not managed.
114+
To help us out, the Render `SubApp` does a bit of cleaning once a frame has finished rendering.
115+
116+
We don't want data that isn't needed sticking around when we could use that space for new textures or meshes.
117+
However, we also don't want to completely wipe the Render `SubApp`.
118+
If there are entities and assets that persist in the Main `SubApp`, it doesn't make sense to keep copying them over every frame.
119+
Therefore, Bevy synchronizes the Render `SubApp` to the current state of the Main `SubApp`.
120+
121+
Remember [`RenderEntity`] and [`MainEntity`]?
122+
These two components are the keys to keeping this synchronization process running.
123+
If an entity is despawned in the Main `SubApp`, it's subsequently removed from the Render `SubApp` using the entity ID stored in these components.
124+
The same is done for loaded assets, although once again the [`AssetEvent`]s are read to determine whether an asset should be unloaded from the Render `SubApp`.
125+
However, if you do need data to persist in the Render `SubApp`, you can create and use a [`Resource`] to store it in the Render `SubApp`.
126+
127+
[`ExtractComponent`]: https://docs.rs/bevy/latest/bevy/render/extract_component/trait.ExtractComponent.html
128+
[`SyncToRenderWorld`]: https://docs.rs/bevy/latest/bevy/render/sync_world/struct.SyncToRenderWorld.html
129+
[`ExtractSchedule`]: https://docs.rs/bevy/latest/bevy/prelude/struct.ExtractSchedule.html
130+
[`RenderEntity`]: https://docs.rs/bevy/latest/bevy/render/sync_world/struct.RenderEntity.html
131+
[`MainEntity`]: https://docs.rs/bevy/latest/bevy/render/sync_world/struct.MainEntity.html
132+
[`Asset`]: https://docs.rs/bevy/latest/bevy/asset/trait.Asset.html
133+
[`AssetEvent`]: https://docs.rs/bevy/latest/bevy/asset/enum.AssetEvent.html
134+
[`BindGroup`]: https://docs.rs/bevy/latest/bevy/render/render_resource/struct.BindGroup.html
135+
[`Buffer`]: https://docs.rs/bevy/latest/bevy/render/render_resource/struct.Buffer.html
136+
[`BindGroupLayout`]: https://docs.rs/bevy/latest/bevy/render/render_resource/struct.BindGroupLayout.html
137+
[`PhaseItem`]: https://docs.rs/bevy/latest/bevy/render/render_phase/trait.PhaseItem.html
138+
[`Draw`]: https://docs.rs/bevy/latest/bevy/render/render_phase/trait.Draw.html
139+
[`RenderPipeline`]: https://docs.rs/bevy/latest/bevy/render/render_resource/struct.RenderPipeline.html
140+
[`RenderContext`]: https://docs.rs/bevy/latest/bevy/render/renderer/struct.RenderContext.html
141+
[`Schedule`]: https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.Schedule.html
142+
[`Core2d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/schedule/struct.Core2d.html
143+
[`Core3d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/schedule/struct.Core3d.html
144+
[`Resource`]: https://docs.rs/bevy/latest/bevy/ecs/prelude/trait.Resource.html

0 commit comments

Comments
 (0)