Skip to content

Commit f4dc8e1

Browse files
committed
wip
1 parent 527d3a5 commit f4dc8e1

File tree

17 files changed

+857
-26
lines changed

17 files changed

+857
-26
lines changed

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"]
8585
# Adds PBR rendering
8686
bevy_pbr = ["bevy_internal/bevy_pbr", "bevy_asset", "bevy_render", "bevy_core_pipeline"]
8787

88+
gpu_picking = []
89+
8890
# Provides rendering functionality
8991
bevy_render = ["bevy_internal/bevy_render"]
9092

@@ -1294,6 +1296,16 @@ description = "Shows how to rumble a gamepad using force feedback"
12941296
category = "Input"
12951297
wasm = false
12961298

1299+
[[example]]
1300+
name = "gpu_picking"
1301+
path = "examples/input/gpu_picking.rs"
1302+
1303+
[package.metadata.example.gpu_picking]
1304+
name = "GPU picking"
1305+
description = "Mouse picking using the gpu"
1306+
category = "Input"
1307+
wasm = true
1308+
12971309
[[example]]
12981310
name = "keyboard_input"
12991311
path = "examples/input/keyboard_input.rs"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This shader shows how to enable the gpu picking feature for a material
2+
3+
// You'll need the mesh binding because that's where the entity index is
4+
#import bevy_pbr::mesh_bindings
5+
6+
@group(1) @binding(0)
7+
var<uniform> color: vec4<f32>;
8+
9+
// Gpu picking uses multiple fragment output
10+
struct FragmentOutput {
11+
@location(0) color: vec4<f32>,
12+
// You can detect the feature with this flag
13+
#ifdef GPU_PICKING
14+
@location(1) entity: vec2<u32>,
15+
#endif
16+
};
17+
18+
@fragment
19+
fn fragment(
20+
#import bevy_pbr::mesh_vertex_output
21+
) -> FragmentOutput {
22+
var out: FragmentOutput;
23+
out.color = color;
24+
// make sure to output the entity index for gpu picking to work correctly
25+
#ifdef GPU_PICKING
26+
out.entity = mesh.entity;
27+
#endif
28+
return out;
29+
}

crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
use bevy_ecs::{prelude::*, query::QueryItem};
88
use bevy_render::{
99
camera::ExtractedCamera,
10+
picking::{EntityTextures, ExtractedGpuPickingCamera},
1011
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
1112
render_phase::RenderPhase,
1213
render_resource::{
@@ -34,8 +35,10 @@ impl ViewNode for MainOpaquePass3dNode {
3435
Option<&'static DepthPrepass>,
3536
Option<&'static NormalPrepass>,
3637
Option<&'static MotionVectorPrepass>,
38+
Option<&'static ExtractedGpuPickingCamera>,
3739
Option<&'static SkyboxPipelineId>,
3840
Option<&'static SkyboxBindGroup>,
41+
Option<&'static EntityTextures>,
3942
&'static ViewUniformOffset,
4043
);
4144

@@ -53,8 +56,10 @@ impl ViewNode for MainOpaquePass3dNode {
5356
depth_prepass,
5457
normal_prepass,
5558
motion_vector_prepass,
59+
gpu_picking_camera,
5660
skybox_pipeline,
5761
skybox_bind_group,
62+
entity_index_textures,
5863
view_uniform_offset,
5964
): QueryItem<Self::ViewQuery>,
6065
world: &World,
@@ -64,21 +69,34 @@ impl ViewNode for MainOpaquePass3dNode {
6469
#[cfg(feature = "trace")]
6570
let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered();
6671

72+
let mut color_attachments = vec![Some(target.get_color_attachment(Operations {
73+
load: match camera_3d.clear_color {
74+
ClearColorConfig::Default => LoadOp::Clear(world.resource::<ClearColor>().0.into()),
75+
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
76+
ClearColorConfig::None => LoadOp::Load,
77+
},
78+
store: true,
79+
}))];
80+
81+
if gpu_picking_camera.is_some() {
82+
if let Some(picking_textures) = entity_index_textures {
83+
color_attachments.push(Some(picking_textures.get_color_attachment(Operations {
84+
load: match camera_3d.clear_color {
85+
ClearColorConfig::None => LoadOp::Load,
86+
// TODO clear this earlier?
87+
_ => LoadOp::Clear(EntityTextures::no_entity_color()),
88+
},
89+
store: true,
90+
})));
91+
}
92+
}
93+
6794
// Setup render pass
6895
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
6996
label: Some("main_opaque_pass_3d"),
7097
// NOTE: The opaque pass loads the color
7198
// buffer as well as writing to it.
72-
color_attachments: &[Some(target.get_color_attachment(Operations {
73-
load: match camera_3d.clear_color {
74-
ClearColorConfig::Default => {
75-
LoadOp::Clear(world.resource::<ClearColor>().0.into())
76-
}
77-
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
78-
ClearColorConfig::None => LoadOp::Load,
79-
},
80-
store: true,
81-
}))],
99+
color_attachments: &color_attachments,
82100
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
83101
view: &depth.view,
84102
// NOTE: The opaque main pass loads the depth buffer and possibly overwrites it

crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::core_3d::Transparent3d;
22
use bevy_ecs::{prelude::*, query::QueryItem};
33
use bevy_render::{
44
camera::ExtractedCamera,
5+
picking::{EntityTextures, ExtractedGpuPickingCamera},
56
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
67
render_phase::RenderPhase,
78
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
@@ -21,12 +22,14 @@ impl ViewNode for MainTransparentPass3dNode {
2122
&'static RenderPhase<Transparent3d>,
2223
&'static ViewTarget,
2324
&'static ViewDepthTexture,
25+
Option<&'static ExtractedGpuPickingCamera>,
26+
Option<&'static EntityTextures>,
2427
);
2528
fn run(
2629
&self,
2730
graph: &mut RenderGraphContext,
2831
render_context: &mut RenderContext,
29-
(camera, transparent_phase, target, depth): QueryItem<Self::ViewQuery>,
32+
(camera, transparent_phase, target, depth, gpu_picking_camera, entity_index_textures): QueryItem<Self::ViewQuery>,
3033
world: &World,
3134
) -> Result<(), NodeRunError> {
3235
let view_entity = graph.view_entity();
@@ -37,13 +40,27 @@ impl ViewNode for MainTransparentPass3dNode {
3740
#[cfg(feature = "trace")]
3841
let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered();
3942

43+
let mut color_attachments = vec![Some(target.get_color_attachment(Operations {
44+
load: LoadOp::Load,
45+
store: true,
46+
}))];
47+
48+
if gpu_picking_camera.is_some() {
49+
if let Some(entity_index_textures) = entity_index_textures {
50+
color_attachments.push(Some(entity_index_textures.get_color_attachment(
51+
Operations {
52+
// The texture is already cleared in the opaque pass
53+
load: LoadOp::Load,
54+
store: true,
55+
},
56+
)));
57+
}
58+
}
59+
4060
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
4161
label: Some("main_transparent_pass_3d"),
4262
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
43-
color_attachments: &[Some(target.get_color_attachment(Operations {
44-
load: LoadOp::Load,
45-
store: true,
46-
}))],
63+
color_attachments: &color_attachments,
4764
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
4865
view: &depth.view,
4966
// NOTE: For the transparent pass we load the depth buffer. There should be no

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod graph {
1414
pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass";
1515
pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass";
1616
pub const END_MAIN_PASS: &str = "end_main_pass";
17+
pub const ENTITY_INDEX_BUFFER_COPY: &str = "entity_index_buffer_copy";
1718
pub const BLOOM: &str = "bloom";
1819
pub const TONEMAPPING: &str = "tonemapping";
1920
pub const FXAA: &str = "fxaa";
@@ -35,6 +36,7 @@ use bevy_ecs::prelude::*;
3536
use bevy_render::{
3637
camera::{Camera, ExtractedCamera},
3738
extract_component::ExtractComponentPlugin,
39+
picking::{EntityTextures, ExtractedGpuPickingCamera, ENTITY_TEXTURE_FORMAT},
3840
prelude::Msaa,
3941
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
4042
render_phase::{
@@ -84,6 +86,10 @@ impl Plugin for Core3dPlugin {
8486
prepare_core_3d_depth_textures
8587
.in_set(RenderSet::Prepare)
8688
.after(bevy_render::view::prepare_windows),
89+
// #[cfg(feature = "gpu_picking")]
90+
prepare_entity_textures
91+
.in_set(RenderSet::Prepare)
92+
.after(bevy_render::view::prepare_windows),
8793
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
8894
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
8995
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
@@ -316,3 +322,63 @@ pub fn prepare_core_3d_depth_textures(
316322
});
317323
}
318324
}
325+
326+
/// Create the required buffers based on the camera size
327+
pub fn prepare_entity_textures(
328+
mut commands: Commands,
329+
mut texture_cache: ResMut<TextureCache>,
330+
msaa: Res<Msaa>,
331+
render_device: Res<RenderDevice>,
332+
views_3d: Query<
333+
(Entity, &ExtractedCamera, Option<&ExtractedGpuPickingCamera>),
334+
(With<RenderPhase<Opaque3d>>, With<RenderPhase<AlphaMask3d>>),
335+
>,
336+
) {
337+
for (entity, camera, gpu_picking_camera) in &views_3d {
338+
if gpu_picking_camera.is_none() {
339+
continue;
340+
}
341+
342+
let Some(physical_target_size) = camera.physical_target_size else {
343+
continue;
344+
};
345+
346+
let size = Extent3d {
347+
depth_or_array_layers: 1,
348+
width: physical_target_size.x,
349+
height: physical_target_size.y,
350+
};
351+
352+
let descriptor = TextureDescriptor {
353+
label: None,
354+
size,
355+
mip_level_count: 1,
356+
sample_count: 1,
357+
dimension: TextureDimension::D2,
358+
format: ENTITY_TEXTURE_FORMAT,
359+
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
360+
view_formats: &[],
361+
};
362+
363+
let entity_textures = EntityTextures {
364+
main: texture_cache.get(
365+
&render_device,
366+
TextureDescriptor {
367+
label: Some("main_entity_texture"),
368+
..descriptor
369+
},
370+
),
371+
sampled: (msaa.samples() > 1).then(|| {
372+
texture_cache.get(
373+
&render_device,
374+
TextureDescriptor {
375+
label: Some("main_entity_texture_sampled"),
376+
sample_count: msaa.samples(),
377+
..descriptor
378+
},
379+
)
380+
}),
381+
};
382+
commands.entity(entity).insert(entity_textures);
383+
}
384+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use bevy_app::Plugin;
2+
use bevy_ecs::{query::QueryItem, world::World};
3+
use bevy_render::{
4+
picking::{EntityTextures, ExtractedGpuPickingCamera},
5+
render_graph::{RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
6+
renderer::RenderContext,
7+
RenderApp,
8+
};
9+
10+
use crate::core_3d::CORE_3D;
11+
12+
#[derive(Default)]
13+
pub struct EntityIndexBufferCopyNode;
14+
impl ViewNode for EntityIndexBufferCopyNode {
15+
type ViewQuery = (&'static EntityTextures, &'static ExtractedGpuPickingCamera);
16+
17+
fn run(
18+
&self,
19+
_graph: &mut RenderGraphContext,
20+
render_context: &mut RenderContext,
21+
(entity_index_textures, gpu_picking_camera): QueryItem<Self::ViewQuery>,
22+
_world: &World,
23+
) -> Result<(), bevy_render::render_graph::NodeRunError> {
24+
let Some(buffers) = gpu_picking_camera.buffers.as_ref() else {
25+
return Ok(());
26+
};
27+
28+
buffers.copy_texture_to_buffer(
29+
render_context.command_encoder(),
30+
&entity_index_textures.main.texture,
31+
);
32+
33+
Ok(())
34+
}
35+
}
36+
37+
pub struct EntityIndexBufferCopyPlugin;
38+
impl Plugin for EntityIndexBufferCopyPlugin {
39+
fn build(&self, app: &mut bevy_app::App) {
40+
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
41+
42+
// 3D
43+
use crate::core_3d::graph::node::*;
44+
render_app
45+
.add_render_graph_node::<ViewNodeRunner<EntityIndexBufferCopyNode>>(
46+
CORE_3D,
47+
ENTITY_INDEX_BUFFER_COPY,
48+
)
49+
.add_render_graph_edge(CORE_3D, UPSCALING, ENTITY_INDEX_BUFFER_COPY);
50+
}
51+
}

crates/bevy_core_pipeline/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod clear_color;
66
pub mod contrast_adaptive_sharpening;
77
pub mod core_2d;
88
pub mod core_3d;
9+
pub mod entity_index_buffer_copy;
910
pub mod fullscreen_vertex_shader;
1011
pub mod fxaa;
1112
pub mod msaa_writeback;
@@ -40,6 +41,7 @@ use crate::{
4041
contrast_adaptive_sharpening::CASPlugin,
4142
core_2d::Core2dPlugin,
4243
core_3d::Core3dPlugin,
44+
entity_index_buffer_copy::EntityIndexBufferCopyPlugin,
4345
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
4446
fxaa::FxaaPlugin,
4547
msaa_writeback::MsaaWritebackPlugin,
@@ -77,6 +79,7 @@ impl Plugin for CorePipelinePlugin {
7779
.add_plugin(UpscalingPlugin)
7880
.add_plugin(BloomPlugin)
7981
.add_plugin(FxaaPlugin)
80-
.add_plugin(CASPlugin);
82+
.add_plugin(CASPlugin)
83+
.add_plugin(EntityIndexBufferCopyPlugin);
8184
}
8285
}

crates/bevy_gizmos/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ use bevy_render::{
4444
};
4545
use bevy_transform::components::{GlobalTransform, Transform};
4646

47+
// #[cfg(feature = "gpu_picking")]
48+
use bevy_render::picking;
49+
4750
#[cfg(feature = "bevy_pbr")]
4851
use bevy_pbr::MeshUniform;
4952
#[cfg(feature = "bevy_sprite")]
@@ -325,6 +328,8 @@ fn extract_gizmo_data(
325328
transform,
326329
previous_transform: transform,
327330
inverse_transpose_model,
331+
// #[cfg(feature = "gpu_picking")]
332+
entity: picking::entity_as_uvec2(Entity::PLACEHOLDER),
328333
},
329334
),
330335
#[cfg(feature = "bevy_sprite")]

crates/bevy_pbr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ keywords = ["bevy"]
1010

1111
[features]
1212
webgl = []
13+
gpu_picking = []
1314

1415
[dependencies]
1516
# bevy

0 commit comments

Comments
 (0)