bevy_render/renderer/
render_device.rs

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