bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Immediate Mode Line/Gizmo Drawing

Open tim-blackbird opened this issue 2 years ago • 28 comments

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

Screenshot_20230207_155033

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]

tim-blackbird avatar Nov 09 '22 20:11 tim-blackbird

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.

james7132 avatar Nov 14 '22 01:11 james7132

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!

The5-1 avatar Nov 14 '22 01:11 The5-1

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.

nicopap avatar Nov 14 '22 07:11 nicopap

Once #5303 by @IceSentry is merged, should we reuse the wireframe material here?

alice-i-cecile avatar Nov 17 '22 13:11 alice-i-cecile

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.

tim-blackbird avatar Dec 04 '22 14:12 tim-blackbird

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

tim-blackbird avatar Dec 07 '22 00:12 tim-blackbird

@devil-ira #5619 seems to be addressed by this. Can you add Fixes #5619 to the PR description?

james7132 avatar Dec 10 '22 00:12 james7132

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)

nicopap avatar Dec 10 '22 13:12 nicopap

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:

  1. That isn't implemented yet
  2. 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".
  3. 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".

cart avatar Jan 23 '23 21:01 cart

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

tim-blackbird avatar Jan 23 '23 21:01 tim-blackbird

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.

nicopap avatar Jan 24 '23 08:01 nicopap

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 of println!() 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.

djeedai avatar Jan 29 '23 08:01 djeedai

You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.

github-actions[bot] avatar Feb 06 '23 23:02 github-actions[bot]

Example alien_cake_addict failed to run, please try running it locally and check the result.

github-actions[bot] avatar Feb 06 '23 23:02 github-actions[bot]

You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.

github-actions[bot] avatar Feb 07 '23 09:02 github-actions[bot]

You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.

github-actions[bot] avatar Feb 07 '23 10:02 github-actions[bot]

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.

JMS55 avatar Feb 07 '23 17:02 JMS55

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

tim-blackbird avatar Feb 26 '23 08:02 tim-blackbird

There's currently an issue with UI no longer rendering.

tim-blackbird avatar Feb 26 '23 09:02 tim-blackbird

How about the line thickness? The lines seem to be too thin.

happydpc avatar Feb 26 '23 11:02 happydpc

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

nicopap avatar Feb 26 '23 12:02 nicopap

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.

tim-blackbird avatar Feb 26 '23 13:02 tim-blackbird

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

IceSentry avatar Feb 26 '23 20:02 IceSentry

Render node changes have been reverted. CI failure is just Dependencies / check-bans.

tim-blackbird avatar Feb 27 '23 12:02 tim-blackbird

bors try

nicopap avatar Feb 28 '23 07:02 nicopap

You added a new feature but didn't add a description for it. Please update the root Cargo.toml file.

github-actions[bot] avatar Mar 11 '23 23:03 github-actions[bot]

You added a new feature but didn't add a description for it. Please update the root Cargo.toml file.

github-actions[bot] avatar Mar 11 '23 23:03 github-actions[bot]

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.

cart avatar Mar 20 '23 22:03 cart

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.

Toqozz avatar Mar 20 '23 22:03 Toqozz