bevy
bevy copied to clipboard
Immediate Mode Line/Gizmo Drawing
Objective
Add a convenient immediate mode drawing API for visual debugging.
Fixes #5619 Alternative to #1625 Partial alternative to #5734
Based off https://github.com/Toqozz/bevy_debug_lines with some changes:
- Simultaneous support for 2D and 3D.
- Methods for basic shapes; circles, spheres, rectangles, boxes, etc.
- 2D methods.
- Removed durations. Seemed niche, and can be handled by users.
Performance
Stress tested using Bevy's recommended optimization settings for the dev profile with the following command.
cargo run --example many_debug_lines \
--config "profile.dev.package.\"*\".opt-level=3" \
--config "profile.dev.opt-level=1"
I dipped to 65-70 FPS at 300,000 lines CPU: 3700x RAM Speed: 3200 Mhz GPU: 2070 super - probably not very relevant, mostly cpu/memory bound
Fancy bloom screenshot
Changelog
- Added
GizmoPlugin
- Added
Gizmos
system parameter for drawing lines and wireshapes.
TODO
- [ ] Update changelog
- [x] Update performance numbers
- [x] Add credit to PR description
Future work
- Cache rendering primitives instead of constructing them out of line segments each frame.
- Support for drawing solid meshes
- Interactions. (See bevy_mod_gizmos)
- Fancier line drawing. (See bevy_polyline)
- Support for
RenderLayers
- Display gizmos for a certain duration. Currently everything displays for one frame (ie. immediate mode)
- Changing settings per drawn item like drawing on top or drawing to different
RenderLayers
Co-Authored By: @lassade [email protected] Co-Authored By: @The5-1 [email protected] Co-Authored By: @Toqozz [email protected] Co-Authored By: @nicopap [email protected]
Credit should be given to @lassade, @The5-1, and @Toqozz for their prior work in this space.
Could you add Co-Authored By: <EMAIL>
to the PR description? Bors will merge it in with that in the final commit to main.
Separately, @lassade's existing implementation for bevy_gizmos
seems to also have support for filling in the internal space with a flat color. We can add it in a follow-up PR or leave it be.
Oh I totally missed this here, good stuff 👌
Was still struggling to get my old attempt ported to 0.8 and then 0.9 hit ;p Gotta take a closer look at this tomorrow to see how its done!
Note that I also heavily contributed to Toqozz/bevy_debug_lines. I did the port to the new renderer for 0.6, it was basically an entire rewrite.
Once #5303 by @IceSentry is merged, should we reuse the wireframe material here?
Once https://github.com/bevyengine/bevy/pull/5303 by @IceSentry is merged, should we reuse the wireframe material here?
This PR needs colors to be per vertex with an option to disable depth testing + a 2d version, so that's not an option, and the Material(2d)
traits and AsBindGroup
are currently not flexible enough to support this.
Using a channel/mutex ended up rather costly for performance. I switched to the method Commands
uses. I temporarily yoinked the code from #6817 for the draft :)
I'd like to give some more performance numbers but the systems in many_debug_lines.rs
don't appear to be running in parallel, but I did manage to keep above 60fps at 300 000 lines.
Render entities are now spawned directly in the render world each frame.
Still some things to do, like renaming the crate, so I'd hold off on doing another review pass :)
@devil-ira #5619 seems to be addressed by this. Can you add Fixes #5619
to the PR description?
I think that the API as a system param is lacking and doesn't fulfill the goal of creating an easy API for debugging. An API to draw gizmos for editing is different than an API to draw lines for understanding what is going on in your code. Of course, it can share methods, but having different goals they should be useable differently.
Here is an example to illustrate what I mean. I have two functions and a system
struct Segment {
move_left: bool,
value: Vec2,
}
#[derive(Component)]
struct Segments(Vec<Vec<Segment>>);
fn move_segment(segment: &mut Segment) {
if segment.move_left {
segment.value += Vec2::X;
}
}
fn move_list(segments: &mut [Segment]) {
for segment in segments {
move_segment(segment);
}
}
fn system(mut segments: Query<&mut Segment>) {
for segment_list in &mut segments {
move_list(&mut segment_list.0);
}
}
Now I want to draw a line but only when the segment moved. Well, with a systemparam based API, I need to modify all the parameter lists to add this line.
struct Segment {
move_left: bool,
value: Vec2,
}
#[derive(Component)]
struct Segments(Vec<Vec<Segment>>);
fn move_segment(segment: &mut Segment, draw_gizmo: &mut DrawGizmo) {
if segment.move_left {
segment.value += Vec2::X;
draw_gizmo.add_line(segment.value, segment.value + Vec2::Y);
}
}
fn move_list(segments: &mut [Segment], draw_gizmo: &mut DrawGizmo) {
for segment in segments {
move_segment(segment, draw_gizmo);
}
}
fn system(mut segments: Query<&mut Segment>, mut draw_gizmo: DrawGizmo) {
for segment_list in &mut segments {
move_list(&mut segment_list.0, &mut *draw_gizmo);
}
}
For one, this is extremely annoying. Secondly, a common thing to do is to push the debug drawing behind a compile flag, how would I do it with function parameters? This is so unpleasant that it defeats the purpose of an immediate-mode drawing API.
Ideally the API looks like this:
struct Segment {
move_left: bool,
value: Vec2,
}
#[derive(Component)]
struct Segments(Vec<Vec<Segment>>);
fn move_segment(segment: &mut Segment) {
if segment.move_left {
segment.value += Vec2::X;
DrawGizmo::add_line(segment.value, segment.value + Vec2::Y);
// OR
draw_line!(segment.value, segment.value + Vec2::Y);
}
}
fn move_list(segments: &mut [Segment]) {
for segment in segments {
move_segment(segment);
}
}
fn system(mut segments: Query<&mut Segment>) {
for segment_list in &mut segments {
move_list(&mut segment_list.0);
}
}
(also credits to chatGPT for drafting the two first code samples)
The current SystemParam approach is definitely the least controversial. It builds on existing patterns, it makes commands easily discoverable / autocomplete-able. and it doesn't introduce the weirdness that globals introduce (statefulness gets more challenging, it can break multi-app scenarios such as testing, harder for people to understand ~~control~~ data flow, etc).
I think an argument could be made for a set of println!
style apis, but:
- That isn't implemented yet
- We'd need to sort out the implications of doing it that way, work out the scenarios where it might break, and then weigh that against the value we get by doing it. I suspect we'd come out on top here, but thats "additional work".
- It seems like they could exist in addition to the more standard approach used in this PR.
I think my stance is "yeah lets discuss this, but we probably don't need to hold up this PR to do it".
Thanks for the response cart! Agreed, the current approach based on @JoJoJet's work is nice and a more convenient api can be considered and exist separately. I'll focus on getting this in a nicer state and I'll try to give @JoJoJet's PR a review :)
Regardless, having the API even if it requires the system param is still precious, since it's easy to build a global variable based abstraction on top of it.
I would expect that:
- a
debug_line!()
API is super convenient for checking a few small stuffs, but is limited in terms of performance and number of items it can draw; this is the equivalent ofprintln!()
and we know its uses and limitations. - the current approach is a heavily optimized alternative for heavier uses, like drawing a navmesh or other spatial data structures that have a large number of items and require 10k~100k lines each of more.
I think both use cases are valid and call for different solutions.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
Example alien_cake_addict
failed to run, please try running it locally and check the result.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
Taking a quick look, rather than add a render command to the main passes, I think it would be better to have a separate render node that runs after post processing.
Taking a quick look, rather than add a render command to the main passes, I think it would be better to have a separate render node that runs after post processing.
This is now done, all thanks to @IceSentry :)
There's currently an issue with UI no longer rendering.
How about the line thickness? The lines seem to be too thin.
@happydpc WGPU doesn't give control about line thickness, only 1 pixel. This is very much out of scope for this PR. I think even with only 1px, it's still good enough for debugging. With thicker lines, there is also the issue of line caps sneaking in. I'm not sure what a path toward proper line thickness would look like, most likely using quads, similar to https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline.
How about the line thickness? The lines seem to be too thin.
Unfortunately line-thickness is not widely supported (OpenGL only I think). Unity has the same limitation and Unreal instead draws 2 quads in an X shape (like grass in Minecraft) which is pretty janky IMO.
As @nicopap mentioned, line width is going to be tricky and deserves it's own PR.
After some discussions on discord, we realized that just using a custom node is not enough and this would need a separate camera. Since this is a bit more involved, I'll revert the changes using a custom node and leave this as a a follow up PR. Having this merged is more important to me than having it run after post processing
Render node changes have been reverted. CI failure is just Dependencies / check-bans.
bors try
try
Build succeeded:
- build (windows-latest)
- build (ubuntu-latest)
- build (macos-latest)
- build-wasm
- markdownlint
- run-examples
- run-examples-on-wasm
- check-doc
- check-missing-examples-in-docs
- ci
- check-compiles
- build-and-install-on-iOS
- run-examples-on-windows-dx12
- build-without-default-features (bevy)
- build-without-default-features (bevy_ecs)
- build-without-default-features (bevy_reflect)
- msrv
- build-android
You added a new feature but didn't add a description for it. Please update the root Cargo.toml file.
You added a new feature but didn't add a description for it. Please update the root Cargo.toml file.
Turns out this was based on @Toqozz's bevy_debug_lines
crate, which is licensed under the mit license only. This will need to be re-licensed if we're going to use it in Bevy.
@Toqozz: can you write "I agree to re-license the code from bevy_debug_lines used in this PR to the dual MIT/Apache-2.0 license Bevy uses" here if you agree to the re-license? No pressure btw. It is your code and you can do whatever you want with it. We will revert if we don't hear from you / if the answer is no.
I agree to re-license the code from bevy_debug_lines used in this PR to the dual MIT/Apache-2.0 license Bevy uses.
I didn't even realise that the license was limiting.