|
| 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