Skip to content

Commit 143fb61

Browse files
committed
clean up
1 parent 1b1920a commit 143fb61

File tree

9 files changed

+97
-172
lines changed

9 files changed

+97
-172
lines changed

Cargo.toml

Lines changed: 2 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

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ impl Plugin for Core3dPlugin {
8686
prepare_core_3d_depth_textures
8787
.in_set(RenderSet::Prepare)
8888
.after(bevy_render::view::prepare_windows),
89+
// #[cfg(feature = "gpu_picking")]
8990
prepare_entity_textures
9091
.in_set(RenderSet::Prepare)
9192
.after(bevy_render::view::prepare_windows),

crates/bevy_core_pipeline/src/entity_index_buffer_copy/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ impl ViewNode for EntityIndexBufferCopyNode {
2525
return Ok(());
2626
};
2727

28-
// copy entity index texture
2928
buffers.copy_texture_to_buffer(
3029
render_context.command_encoder(),
3130
&entity_index_textures.main.texture,
32-
&buffers.entity_buffer,
3331
);
3432

3533
Ok(())

crates/bevy_gizmos/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ use bevy_reflect::{
3737
use bevy_render::{
3838
color::Color,
3939
mesh::Mesh,
40-
picking,
4140
primitives::Aabb,
4241
render_phase::AddRenderCommand,
4342
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
4443
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
4544
};
4645
use bevy_transform::components::{GlobalTransform, Transform};
4746

47+
// #[cfg(feature = "gpu_picking")]
48+
use bevy_render::picking;
49+
4850
#[cfg(feature = "bevy_pbr")]
4951
use bevy_pbr::MeshUniform;
5052
#[cfg(feature = "bevy_sprite")]
@@ -326,6 +328,7 @@ fn extract_gizmo_data(
326328
transform,
327329
previous_transform: transform,
328330
inverse_transpose_model,
331+
// #[cfg(feature = "gpu_picking")]
329332
entity: picking::entity_as_uvec2(Entity::PLACEHOLDER),
330333
},
331334
),

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

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pub struct MeshUniform {
132132
pub transform: Mat4,
133133
pub previous_transform: Mat4,
134134
pub inverse_transpose_model: Mat4,
135-
// TODO should probably be a separate uniform in case gpu picking is disabled
135+
// #[cfg(feature = "gpu_picking")]
136136
pub entity: UVec2,
137137
pub flags: u32,
138138
}
@@ -187,6 +187,7 @@ pub fn extract_meshes(
187187
flags: flags.bits(),
188188
transform,
189189
previous_transform,
190+
// #[cfg(feature = "gpu_picking")]
190191
entity: picking::entity_as_uvec2(entity),
191192
inverse_transpose_model: transform.inverse().transpose(),
192193
};

crates/bevy_render/src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ pub mod prelude {
4545
use bevy_window::{PrimaryWindow, RawHandleWrapper};
4646
use globals::GlobalsPlugin;
4747
pub use once_cell;
48-
use picking::GpuPickingPlugin;
4948
use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
5049
use wgpu::Instance;
5150

@@ -336,8 +335,7 @@ impl Plugin for RenderPlugin {
336335
.add_plugin(CameraPlugin)
337336
.add_plugin(ViewPlugin)
338337
.add_plugin(MeshPlugin)
339-
.add_plugin(GlobalsPlugin)
340-
.add_plugin(GpuPickingPlugin);
338+
.add_plugin(GlobalsPlugin);
341339

342340
app.register_type::<color::Color>()
343341
.register_type::<primitives::Aabb>()

crates/bevy_render/src/picking.rs

Lines changed: 74 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,106 @@
11
use crate::{
22
camera::ExtractedCamera,
33
extract_component::ExtractComponentPlugin,
4-
render_resource::{Buffer, BufferSlice, Texture},
4+
render_resource::{Buffer, Texture},
55
renderer::RenderDevice,
66
texture::{CachedTexture, TextureFormatPixelInfo},
77
Render, RenderApp, RenderSet,
88
};
9-
use async_channel::{Receiver, Sender, TrySendError};
9+
use async_channel::{Receiver, Sender};
1010
use bevy_app::{Plugin, Update};
1111
use bevy_ecs::prelude::*;
1212

1313
use bevy_math::UVec2;
1414
use bevy_render_macros::ExtractComponent;
1515
use bevy_tasks::AsyncComputeTaskPool;
16-
use bevy_utils::{default, HashMap};
16+
use bevy_utils::default;
1717
use wgpu::{
18-
BufferDescriptor, BufferUsages, Color, CommandEncoder, Extent3d, ImageDataLayout, Maintain,
19-
MapMode, Operations, RenderPassColorAttachment, TextureFormat,
18+
BufferDescriptor, BufferUsages, Color, CommandEncoder, Extent3d, ImageDataLayout, MapMode,
19+
Operations, RenderPassColorAttachment, TextureFormat,
2020
};
2121

2222
pub const ENTITY_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg32Uint;
2323

24+
/// This plugin enables the gpu picking feature of bevy.
25+
///
26+
/// Gpu picking let's you know which entity is currently under the mouse.
27+
///
28+
/// # How this works:
29+
///
30+
/// - For each entity being rendered, it will output it's entity id to a texture.
31+
/// - Once everything is rendered it will copy that texture to the cpu and send it to the main world
32+
/// - From the main world you can give it a position like the current mouse position and
33+
/// know exactly which entity was rendered at that specific screen location.
34+
/// - This works at the [`Camera`] level, so it will work with multiple windows or split screen
35+
///
36+
/// # Api Overview:
37+
///
38+
/// You need to add the [`GpuPickingCamera`] to any `Camera` that will be used for picking.
39+
/// Then add the [`GpuPickingMesh`] comnponent to any `Mesh` that will need to be picked.
40+
///
41+
/// Once those components are added, you can query for [`GpuPickingCamera`]
42+
/// and use `GpuPickingCamera::get_entity(position)` to know which entity is at the given position on screen
2443
pub struct GpuPickingPlugin;
2544
impl Plugin for GpuPickingPlugin {
2645
fn build(&self, app: &mut bevy_app::App) {
2746
app.add_plugin(ExtractComponentPlugin::<GpuPickingMesh>::default())
2847
.add_plugin(ExtractComponentPlugin::<GpuPickingCamera>::default())
29-
.add_systems(Update, update_entity_buffer);
48+
.add_systems(Update, receive_buffer);
3049

3150
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
3251

3352
render_app.add_systems(
3453
Render,
3554
(
3655
prepare_gpu_picking_buffers.in_set(RenderSet::Prepare),
37-
send_buffer_to_main_world.in_set(RenderSet::RenderFlush),
56+
map_and_send_buffer_async.in_set(RenderSet::RenderFlush),
3857
),
3958
);
4059
}
4160
}
4261

43-
/// Copies the entity buffer to the cpu and sends it to the main world
44-
fn send_buffer_to_main_world(
45-
query: Query<&ExtractedGpuPickingCamera, With<ExtractedCamera>>,
46-
render_device: Res<RenderDevice>,
47-
) {
62+
/// Maps the entity buffer and sends it to the main world asynchronously
63+
fn map_and_send_buffer_async(query: Query<&ExtractedGpuPickingCamera, With<ExtractedCamera>>) {
4864
for gpu_picking_camera in &query {
4965
let Some(buffers) = gpu_picking_camera.buffers.as_ref() else {
5066
return;
5167
};
68+
let buffers = buffers.clone();
69+
let sender = gpu_picking_camera.sender.clone();
5270

53-
// Send the data to the main world
54-
fn send(
55-
sender: Sender<GpuPickingData>,
56-
padded_bytes_per_row: usize,
57-
entity_buffer: &Buffer,
58-
buffer_slice: BufferSlice,
59-
) {
60-
let buffer_view = buffer_slice.get_mapped_range();
61-
// immediately copy the data to CPU to avoid holding the mapped view for long
62-
let entity_data = Vec::from(&*buffer_view);
63-
drop(buffer_view);
64-
entity_buffer.unmap();
71+
// Mapping the buffer is an asynchronous process.
72+
// This means we need to wait until the buffer is mapped before sending it to the main world
73+
let task = async move {
74+
let (tx, rx) = async_channel::bounded(1);
6575

66-
if let Err(err) = sender.try_send(GpuPickingData {
67-
padded_bytes_per_row,
68-
entity_data,
69-
}) {
70-
match err {
71-
TrySendError::Full(_) => bevy_log::error!("GPU Picking channel is full"),
72-
TrySendError::Closed(_) => {
73-
bevy_log::error!("GPU Picking channel is closed");
74-
}
75-
}
76-
}
77-
78-
// This can only fail if the sender is full or closed
79-
// and we can't do anything if either of those things happen
80-
// let _ = sender.try_send(GpuPickingData {
81-
// padded_bytes_per_row,
82-
// entity_data,
83-
// });
84-
}
85-
86-
if true {
87-
// sync
76+
// map entity buffer
8877
let buffer_slice = buffers.entity_buffer.slice(..);
8978
buffer_slice.map_async(MapMode::Read, move |result| match result {
90-
Ok(_) => {}
79+
Ok(_) => tx.try_send(()).unwrap(),
9180
Err(err) => bevy_log::error!("Failed to map entity buffer: {err}"),
9281
});
9382

94-
// WARN This is blocking
95-
render_device.poll(Maintain::Wait);
83+
// Buffer is mapped and ready to be sent
84+
rx.recv().await.unwrap();
9685

9786
// Send the buffer to the main world
98-
send(
99-
gpu_picking_camera.sender.clone(),
100-
buffers.buffer_dimensions.padded_bytes_per_row,
101-
&buffers.entity_buffer,
102-
buffer_slice,
103-
);
104-
} else {
105-
// async
106-
let entity_buffer = buffers.entity_buffer.clone();
107-
let sender = gpu_picking_camera.sender.clone();
108-
let padded_bytes_per_row = buffers.buffer_dimensions.padded_bytes_per_row;
109-
110-
// Mapping the buffer is an asynchronous process.
111-
// This means we need to wait until the buffer is mapped before sending it to the main world
112-
let task = async move {
113-
let (tx, rx) = async_channel::bounded(1);
114-
115-
// map entity buffer
116-
let buffer_slice = entity_buffer.slice(..);
117-
buffer_slice.map_async(MapMode::Read, move |result| match result {
118-
Ok(_) => tx.try_send(()).unwrap(),
119-
Err(err) => bevy_log::error!("Failed to map entity buffer: {err}"),
120-
});
121-
122-
// Buffer is mapped and ready to be sent
123-
rx.recv().await.unwrap();
124-
125-
// Send the buffer to the main world
126-
send(sender, padded_bytes_per_row, &entity_buffer, buffer_slice);
127-
};
128-
AsyncComputeTaskPool::get().spawn(task).detach();
129-
}
87+
let entity_buffer = &buffers.entity_buffer;
88+
let buffer_view = buffer_slice.get_mapped_range();
89+
// immediately copy the data to CPU to avoid holding the mapped view for long
90+
let entity_data = Vec::from(&*buffer_view);
91+
drop(buffer_view);
92+
entity_buffer.unmap();
93+
94+
// Because the channel is bounded to 1 entry, it's pretty common to have the channel full.
95+
// This isn't ideal but not blocking makes it faster which is preferred.
96+
//
97+
// The other possible error is for the channel to be closed and in that case we can't do anything
98+
let _ = sender.try_send(GpuPickingData {
99+
padded_bytes_per_row: buffers.buffer_dimensions.padded_bytes_per_row,
100+
entity_data,
101+
});
102+
};
103+
AsyncComputeTaskPool::get().spawn(task).detach();
130104
}
131105
}
132106

@@ -166,7 +140,7 @@ impl Default for GpuPickingCamera {
166140
impl GpuPickingCamera {
167141
pub fn new() -> Self {
168142
Self {
169-
channel: async_channel::unbounded(),
143+
channel: async_channel::bounded(1),
170144
data: GpuPickingData::default(),
171145
}
172146
}
@@ -218,24 +192,21 @@ impl crate::extract_component::ExtractComponent for GpuPickingCamera {
218192
}
219193

220194
/// Contains the buffer and it's dimension required for gpu picking
195+
#[derive(Clone)]
221196
pub struct GpuPickingCameraBuffers {
222197
pub entity_buffer: Buffer,
223198
buffer_dimensions: BufferDimensions,
224199
}
225200

226201
impl GpuPickingCameraBuffers {
227-
pub fn copy_texture_to_buffer(
228-
&self,
229-
encoder: &mut CommandEncoder,
230-
texture: &Texture,
231-
buffer: &Buffer,
232-
) {
202+
/// Copies the given texture to the entity buffer
203+
pub fn copy_texture_to_buffer(&self, encoder: &mut CommandEncoder, texture: &Texture) {
233204
// This can't be in the Node because it needs access to wgpu but
234205
// bevy_core_pipeline doesn't depend on wgpu
235206
encoder.copy_texture_to_buffer(
236207
texture.as_image_copy(),
237208
wgpu::ImageCopyBuffer {
238-
buffer,
209+
buffer: &self.entity_buffer,
239210
layout: ImageDataLayout {
240211
offset: 0,
241212
bytes_per_row: Some(self.buffer_dimensions.padded_bytes_per_row as u32),
@@ -251,16 +222,11 @@ impl GpuPickingCameraBuffers {
251222
}
252223
}
253224

254-
fn update_entity_buffer(mut q: Query<&mut GpuPickingCamera>) {
225+
fn receive_buffer(mut q: Query<&mut GpuPickingCamera>) {
255226
for mut cam in &mut q {
256227
let (_, receiver) = cam.channel.clone();
257-
loop {
258-
let Ok(buffer) = receiver.try_recv() else { break; };
259-
if receiver.is_empty() {
260-
cam.data = buffer;
261-
break;
262-
}
263-
}
228+
let Ok(data) = receiver.try_recv() else { continue; };
229+
cam.data = data;
264230
}
265231
}
266232

@@ -316,39 +282,25 @@ impl EntityTextures {
316282
fn prepare_gpu_picking_buffers(
317283
render_device: Res<RenderDevice>,
318284
mut cameras: Query<
319-
(Entity, &ExtractedCamera, &mut ExtractedGpuPickingCamera),
285+
(&ExtractedCamera, &mut ExtractedGpuPickingCamera),
320286
Changed<ExtractedCamera>,
321287
>,
322-
mut buffer_cache: Local<HashMap<Entity, (BufferDimensions, Buffer)>>,
323288
) {
324-
for (entity, camera, mut gpu_picking_camera) in &mut cameras {
289+
for (camera, mut gpu_picking_camera) in &mut cameras {
325290
let Some(size) = camera.physical_target_size else { continue; };
326291

327-
// TODO create 2 buffers and altenate between each buffer each frame
328-
329-
// Only create a buffer if it doesn't already exist or the size is different
330-
let mut create_buffer = true;
331-
if let Some((buffer_dimensions, _)) = buffer_cache.get(&entity) {
332-
create_buffer = buffer_dimensions.width != size.x as usize
333-
|| buffer_dimensions.height != size.y as usize;
334-
}
335-
336-
if create_buffer {
337-
let buffer_dimensions =
338-
BufferDimensions::new(size.x as usize, size.y as usize, ENTITY_TEXTURE_FORMAT);
339-
let entity_buffer = render_device.create_buffer(&BufferDescriptor {
340-
label: Some("Entity Buffer"),
341-
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
342-
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
343-
mapped_at_creation: false,
344-
});
345-
buffer_cache.insert(entity, (buffer_dimensions, entity_buffer));
346-
}
292+
let buffer_dimensions =
293+
BufferDimensions::new(size.x as usize, size.y as usize, ENTITY_TEXTURE_FORMAT);
294+
let entity_buffer = render_device.create_buffer(&BufferDescriptor {
295+
label: Some("Entity Buffer"),
296+
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
297+
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
298+
mapped_at_creation: false,
299+
});
347300

348-
let Some((buffer_dimensions, entity_buffer)) = buffer_cache.get(&entity) else { continue; };
349301
gpu_picking_camera.buffers = Some(GpuPickingCameraBuffers {
350302
entity_buffer: entity_buffer.clone(),
351-
buffer_dimensions: *buffer_dimensions,
303+
buffer_dimensions,
352304
});
353305
}
354306
}

0 commit comments

Comments
 (0)