1
1
use crate :: {
2
2
camera:: ExtractedCamera ,
3
3
extract_component:: ExtractComponentPlugin ,
4
- render_resource:: { Buffer , BufferSlice , Texture } ,
4
+ render_resource:: { Buffer , Texture } ,
5
5
renderer:: RenderDevice ,
6
6
texture:: { CachedTexture , TextureFormatPixelInfo } ,
7
7
Render , RenderApp , RenderSet ,
8
8
} ;
9
- use async_channel:: { Receiver , Sender , TrySendError } ;
9
+ use async_channel:: { Receiver , Sender } ;
10
10
use bevy_app:: { Plugin , Update } ;
11
11
use bevy_ecs:: prelude:: * ;
12
12
13
13
use bevy_math:: UVec2 ;
14
14
use bevy_render_macros:: ExtractComponent ;
15
15
use bevy_tasks:: AsyncComputeTaskPool ;
16
- use bevy_utils:: { default, HashMap } ;
16
+ use bevy_utils:: default;
17
17
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 ,
20
20
} ;
21
21
22
22
pub const ENTITY_TEXTURE_FORMAT : TextureFormat = TextureFormat :: Rg32Uint ;
23
23
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
24
43
pub struct GpuPickingPlugin ;
25
44
impl Plugin for GpuPickingPlugin {
26
45
fn build ( & self , app : & mut bevy_app:: App ) {
27
46
app. add_plugin ( ExtractComponentPlugin :: < GpuPickingMesh > :: default ( ) )
28
47
. add_plugin ( ExtractComponentPlugin :: < GpuPickingCamera > :: default ( ) )
29
- . add_systems ( Update , update_entity_buffer ) ;
48
+ . add_systems ( Update , receive_buffer ) ;
30
49
31
50
let Ok ( render_app) = app. get_sub_app_mut ( RenderApp ) else { return ; } ;
32
51
33
52
render_app. add_systems (
34
53
Render ,
35
54
(
36
55
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 ) ,
38
57
) ,
39
58
) ;
40
59
}
41
60
}
42
61
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 > > ) {
48
64
for gpu_picking_camera in & query {
49
65
let Some ( buffers) = gpu_picking_camera. buffers . as_ref ( ) else {
50
66
return ;
51
67
} ;
68
+ let buffers = buffers. clone ( ) ;
69
+ let sender = gpu_picking_camera. sender . clone ( ) ;
52
70
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 ) ;
65
75
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
88
77
let buffer_slice = buffers. entity_buffer . slice ( ..) ;
89
78
buffer_slice. map_async ( MapMode :: Read , move |result| match result {
90
- Ok ( _) => { }
79
+ Ok ( _) => tx . try_send ( ( ) ) . unwrap ( ) ,
91
80
Err ( err) => bevy_log:: error!( "Failed to map entity buffer: {err}" ) ,
92
81
} ) ;
93
82
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 ( ) ;
96
85
97
86
// 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 ( ) ;
130
104
}
131
105
}
132
106
@@ -166,7 +140,7 @@ impl Default for GpuPickingCamera {
166
140
impl GpuPickingCamera {
167
141
pub fn new ( ) -> Self {
168
142
Self {
169
- channel : async_channel:: unbounded ( ) ,
143
+ channel : async_channel:: bounded ( 1 ) ,
170
144
data : GpuPickingData :: default ( ) ,
171
145
}
172
146
}
@@ -218,24 +192,21 @@ impl crate::extract_component::ExtractComponent for GpuPickingCamera {
218
192
}
219
193
220
194
/// Contains the buffer and it's dimension required for gpu picking
195
+ #[ derive( Clone ) ]
221
196
pub struct GpuPickingCameraBuffers {
222
197
pub entity_buffer : Buffer ,
223
198
buffer_dimensions : BufferDimensions ,
224
199
}
225
200
226
201
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 ) {
233
204
// This can't be in the Node because it needs access to wgpu but
234
205
// bevy_core_pipeline doesn't depend on wgpu
235
206
encoder. copy_texture_to_buffer (
236
207
texture. as_image_copy ( ) ,
237
208
wgpu:: ImageCopyBuffer {
238
- buffer,
209
+ buffer : & self . entity_buffer ,
239
210
layout : ImageDataLayout {
240
211
offset : 0 ,
241
212
bytes_per_row : Some ( self . buffer_dimensions . padded_bytes_per_row as u32 ) ,
@@ -251,16 +222,11 @@ impl GpuPickingCameraBuffers {
251
222
}
252
223
}
253
224
254
- fn update_entity_buffer ( mut q : Query < & mut GpuPickingCamera > ) {
225
+ fn receive_buffer ( mut q : Query < & mut GpuPickingCamera > ) {
255
226
for mut cam in & mut q {
256
227
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;
264
230
}
265
231
}
266
232
@@ -316,39 +282,25 @@ impl EntityTextures {
316
282
fn prepare_gpu_picking_buffers (
317
283
render_device : Res < RenderDevice > ,
318
284
mut cameras : Query <
319
- ( Entity , & ExtractedCamera , & mut ExtractedGpuPickingCamera ) ,
285
+ ( & ExtractedCamera , & mut ExtractedGpuPickingCamera ) ,
320
286
Changed < ExtractedCamera > ,
321
287
> ,
322
- mut buffer_cache : Local < HashMap < Entity , ( BufferDimensions , Buffer ) > > ,
323
288
) {
324
- for ( entity , camera, mut gpu_picking_camera) in & mut cameras {
289
+ for ( camera, mut gpu_picking_camera) in & mut cameras {
325
290
let Some ( size) = camera. physical_target_size else { continue ; } ;
326
291
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
+ } ) ;
347
300
348
- let Some ( ( buffer_dimensions, entity_buffer) ) = buffer_cache. get ( & entity) else { continue ; } ;
349
301
gpu_picking_camera. buffers = Some ( GpuPickingCameraBuffers {
350
302
entity_buffer : entity_buffer. clone ( ) ,
351
- buffer_dimensions : * buffer_dimensions ,
303
+ buffer_dimensions,
352
304
} ) ;
353
305
}
354
306
}
0 commit comments