Skip to main content

bevy_render/renderer/
render_device.rs

1use super::RenderQueue;
2use crate::render_resource::{
3    BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawMeshPipelineDescriptor,
4    RawRenderPipelineDescriptor, RenderPipeline, Sampler, Texture,
5};
6use crate::renderer::wgpu_wrapper;
7use bevy_ecs::resource::Resource;
8use wgpu::{
9    util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor,
10    BindGroupLayoutEntry, BufferAsyncError, BufferBindingType, PollError, PollStatus,
11};
12
13wgpu_wrapper! {
14    #[derive(Clone)]
15    struct WgpuDevice(wgpu::Device);
16}
17
18/// This GPU device is responsible for the creation of most rendering and compute resources.
19#[derive(Resource, Clone)]
20pub struct RenderDevice {
21    device: WgpuDevice,
22}
23
24impl From<wgpu::Device> for RenderDevice {
25    fn from(device: wgpu::Device) -> Self {
26        Self::new(device)
27    }
28}
29
30impl RenderDevice {
31    pub fn new(device: wgpu::Device) -> Self {
32        Self {
33            device: WgpuDevice::new(device),
34        }
35    }
36
37    /// List all [`Features`](wgpu::Features) that may be used with this device.
38    ///
39    /// Functions may panic if you use unsupported features.
40    #[inline]
41    pub fn features(&self) -> wgpu::Features {
42        self.device.features()
43    }
44
45    /// List all [`Limits`](wgpu::Limits) that were requested of this device.
46    ///
47    /// If any of these limits are exceeded, functions may panic.
48    #[inline]
49    pub fn limits(&self) -> wgpu::Limits {
50        self.device.limits()
51    }
52
53    /// Creates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code.
54    ///
55    /// # Safety
56    ///
57    /// Creates a shader module with user-customizable runtime checks which allows shaders to
58    /// perform operations which can lead to undefined behavior like indexing out of bounds,
59    /// To avoid UB, ensure any unchecked shaders are sound!
60    /// This method should never be called for user-supplied shaders.
61    #[inline]
62    pub unsafe fn create_shader_module(
63        &self,
64        desc: wgpu::ShaderModuleDescriptor,
65    ) -> wgpu::ShaderModule {
66        #[cfg(feature = "spirv_shader_passthrough")]
67        match &desc.source {
68            wgpu::ShaderSource::SpirV(source)
69                if self
70                    .features()
71                    .contains(wgpu::Features::PASSTHROUGH_SHADERS) =>
72            {
73                // SAFETY:
74                // This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior.
75                // No attempt is made to ensure that data is valid SPIR-V.
76                unsafe {
77                    self.device.create_shader_module_passthrough(
78                        wgpu::ShaderModuleDescriptorPassthrough {
79                            label: desc.label,
80                            spirv: Some(source.clone()),
81                            ..Default::default()
82                        },
83                    )
84                }
85            }
86            // SAFETY:
87            //
88            // This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior.
89            // No attempt is made to ensure that data is valid SPIR-V.
90            _ => unsafe {
91                self.device
92                    .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked())
93            },
94        }
95        #[cfg(not(feature = "spirv_shader_passthrough"))]
96        // SAFETY: the caller is responsible for upholding the safety requirements
97        unsafe {
98            self.device
99                .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked())
100        }
101    }
102
103    /// Creates and validates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code.
104    ///
105    /// See [`ValidateShader`](bevy_shader::ValidateShader) for more information on the tradeoffs involved with shader validation.
106    #[inline]
107    pub fn create_and_validate_shader_module(
108        &self,
109        desc: wgpu::ShaderModuleDescriptor,
110    ) -> wgpu::ShaderModule {
111        #[cfg(feature = "spirv_shader_passthrough")]
112        match &desc.source {
113            wgpu::ShaderSource::SpirV(_source) => panic!("no safety checks are performed for spirv shaders. use `create_shader_module` instead"),
114            _ => self.device.create_shader_module(desc),
115        }
116        #[cfg(not(feature = "spirv_shader_passthrough"))]
117        self.device.create_shader_module(desc)
118    }
119
120    /// Check for resource cleanups and mapping callbacks.
121    ///
122    /// Return `true` if the queue is empty, or `false` if there are more queue
123    /// submissions still in flight. (Note that, unless access to the [`wgpu::Queue`] is
124    /// coordinated somehow, this information could be out of date by the time
125    /// the caller receives it. `Queue`s can be shared between threads, so
126    /// other threads could submit new work at any time.)
127    ///
128    /// no-op on the web, device is automatically polled.
129    #[inline]
130    pub fn poll(&self, maintain: wgpu::PollType) -> Result<PollStatus, PollError> {
131        self.device.poll(maintain)
132    }
133
134    /// Creates an empty [`CommandEncoder`](wgpu::CommandEncoder).
135    #[inline]
136    pub fn create_command_encoder(
137        &self,
138        desc: &wgpu::CommandEncoderDescriptor,
139    ) -> wgpu::CommandEncoder {
140        self.device.create_command_encoder(desc)
141    }
142
143    /// Creates an empty [`RenderBundleEncoder`](wgpu::RenderBundleEncoder).
144    #[inline]
145    pub fn create_render_bundle_encoder(
146        &self,
147        desc: &wgpu::RenderBundleEncoderDescriptor,
148    ) -> wgpu::RenderBundleEncoder<'_> {
149        self.device.create_render_bundle_encoder(desc)
150    }
151
152    /// Creates a new [`BindGroup`](wgpu::BindGroup).
153    #[inline]
154    pub fn create_bind_group<'a>(
155        &self,
156        label: impl Into<wgpu::Label<'a>>,
157        layout: &'a BindGroupLayout,
158        entries: &'a [BindGroupEntry<'a>],
159    ) -> BindGroup {
160        let wgpu_bind_group = self.device.create_bind_group(&BindGroupDescriptor {
161            label: label.into(),
162            layout,
163            entries,
164        });
165        BindGroup::from(wgpu_bind_group)
166    }
167
168    /// Creates a [`BindGroupLayout`](wgpu::BindGroupLayout).
169    #[inline]
170    pub fn create_bind_group_layout<'a>(
171        &self,
172        label: impl Into<wgpu::Label<'a>>,
173        entries: &'a [BindGroupLayoutEntry],
174    ) -> BindGroupLayout {
175        BindGroupLayout::from(
176            self.device
177                .create_bind_group_layout(&BindGroupLayoutDescriptor {
178                    label: label.into(),
179                    entries,
180                }),
181        )
182    }
183
184    /// Creates a [`PipelineLayout`](wgpu::PipelineLayout).
185    #[inline]
186    pub fn create_pipeline_layout(
187        &self,
188        desc: &wgpu::PipelineLayoutDescriptor,
189    ) -> wgpu::PipelineLayout {
190        self.device.create_pipeline_layout(desc)
191    }
192
193    /// Creates a [`RenderPipeline`].
194    #[inline]
195    pub fn create_render_pipeline(&self, desc: &RawRenderPipelineDescriptor) -> RenderPipeline {
196        let wgpu_render_pipeline = self.device.create_render_pipeline(desc);
197        RenderPipeline::from(wgpu_render_pipeline)
198    }
199
200    /// Creates a [`ComputePipeline`].
201    #[inline]
202    pub fn create_compute_pipeline(
203        &self,
204        desc: &wgpu::ComputePipelineDescriptor,
205    ) -> ComputePipeline {
206        let wgpu_compute_pipeline = self.device.create_compute_pipeline(desc);
207        ComputePipeline::from(wgpu_compute_pipeline)
208    }
209
210    /// Creates a [`RenderPipeline`] from a [`RawMeshPipelineDescriptor`].
211    #[inline]
212    pub fn create_mesh_pipeline(&self, desc: &RawMeshPipelineDescriptor) -> RenderPipeline {
213        let wgpu_mesh_pipeline = self.device.create_mesh_pipeline(desc);
214        RenderPipeline::from(wgpu_mesh_pipeline)
215    }
216
217    /// Creates a [`Buffer`].
218    pub fn create_buffer(&self, desc: &wgpu::BufferDescriptor) -> Buffer {
219        let wgpu_buffer = self.device.create_buffer(desc);
220        Buffer::from(wgpu_buffer)
221    }
222
223    /// Creates a [`Buffer`] and initializes it with the specified data.
224    pub fn create_buffer_with_data(&self, desc: &wgpu::util::BufferInitDescriptor) -> Buffer {
225        let wgpu_buffer = self.device.create_buffer_init(desc);
226        Buffer::from(wgpu_buffer)
227    }
228
229    /// Creates a new [`Texture`] and initializes it with the specified data.
230    ///
231    /// `desc` specifies the general format of the texture.
232    /// `data` is the raw data.
233    pub fn create_texture_with_data(
234        &self,
235        render_queue: &RenderQueue,
236        desc: &wgpu::TextureDescriptor,
237        order: wgpu::util::TextureDataOrder,
238        data: &[u8],
239    ) -> Texture {
240        let wgpu_texture = self
241            .device
242            .create_texture_with_data(render_queue, desc, order, data);
243        Texture::from(wgpu_texture)
244    }
245
246    /// Creates a new [`Texture`].
247    ///
248    /// `desc` specifies the general format of the texture.
249    pub fn create_texture(&self, desc: &wgpu::TextureDescriptor) -> Texture {
250        let wgpu_texture = self.device.create_texture(desc);
251        Texture::from(wgpu_texture)
252    }
253
254    /// Creates a new [`Sampler`].
255    ///
256    /// `desc` specifies the behavior of the sampler.
257    pub fn create_sampler(&self, desc: &wgpu::SamplerDescriptor) -> Sampler {
258        let wgpu_sampler = self.device.create_sampler(desc);
259        Sampler::from(wgpu_sampler)
260    }
261
262    /// Initializes [`Surface`](wgpu::Surface) for presentation.
263    ///
264    /// # Panics
265    ///
266    /// - A old [`SurfaceTexture`](wgpu::SurfaceTexture) is still alive referencing an old surface.
267    /// - Texture format requested is unsupported on the surface.
268    pub fn configure_surface(&self, surface: &wgpu::Surface, config: &wgpu::SurfaceConfiguration) {
269        surface.configure(&self.device, config);
270    }
271
272    /// Returns the wgpu [`Device`](wgpu::Device).
273    pub fn wgpu_device(&self) -> &wgpu::Device {
274        &self.device
275    }
276
277    pub fn map_buffer(
278        &self,
279        buffer: &wgpu::BufferSlice,
280        map_mode: wgpu::MapMode,
281        callback: impl FnOnce(Result<(), BufferAsyncError>) + Send + 'static,
282    ) {
283        buffer.map_async(map_mode, callback);
284    }
285
286    // Rounds up `row_bytes` to be a multiple of [`wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`].
287    pub const fn align_copy_bytes_per_row(row_bytes: usize) -> usize {
288        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
289
290        // If row_bytes is aligned calculate a value just under the next aligned value.
291        // Otherwise calculate a value greater than the next aligned value.
292        let over_aligned = row_bytes + align - 1;
293
294        // Round the number *down* to the nearest aligned value.
295        (over_aligned / align) * align
296    }
297
298    pub fn get_supported_read_only_binding_type(
299        &self,
300        buffers_per_shader_stage: u32,
301    ) -> BufferBindingType {
302        if self.limits().max_storage_buffers_per_shader_stage >= buffers_per_shader_stage {
303            BufferBindingType::Storage { read_only: true }
304        } else {
305            BufferBindingType::Uniform
306        }
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn align_copy_bytes_per_row() {
316        // Test for https://github.com/bevyengine/bevy/issues/16992
317        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
318
319        assert_eq!(RenderDevice::align_copy_bytes_per_row(0), 0);
320        assert_eq!(RenderDevice::align_copy_bytes_per_row(1), align);
321        assert_eq!(RenderDevice::align_copy_bytes_per_row(align + 1), align * 2);
322        assert_eq!(RenderDevice::align_copy_bytes_per_row(align), align);
323    }
324}