rend3/renderer/
mod.rs

1use crate::{
2    format_sso,
3    graph::{GraphTextureStore, ReadyData},
4    instruction::{InstructionKind, InstructionStreamPair},
5    managers::{
6        CameraManager, DirectionalLightManager, InternalTexture, MaterialManager, MeshManager, ObjectManager,
7        SkeletonManager, TextureManager,
8    },
9    types::{
10        Camera, DirectionalLight, DirectionalLightChange, DirectionalLightHandle, MaterialHandle, Mesh, MeshHandle,
11        Object, ObjectHandle, Texture, TextureHandle,
12    },
13    util::mipmap::MipmapGenerator,
14    ExtendedAdapterInfo, InstanceAdapterDevice, RendererInitializationError, RendererProfile,
15};
16use glam::Mat4;
17use parking_lot::Mutex;
18use rend3_types::{
19    Handedness, Material, MipmapCount, MipmapSource, ObjectChange, Skeleton, SkeletonHandle, TextureFormat,
20    TextureFromTexture, TextureUsages,
21};
22use std::{
23    num::NonZeroU32,
24    panic::Location,
25    sync::{atomic::AtomicUsize, Arc},
26};
27use wgpu::{
28    util::DeviceExt, CommandBuffer, CommandEncoderDescriptor, Device, DownlevelCapabilities, Extent3d, Features,
29    ImageCopyTexture, ImageDataLayout, Limits, Origin3d, Queue, TextureAspect, TextureDescriptor, TextureDimension,
30    TextureSampleType, TextureViewDescriptor, TextureViewDimension,
31};
32use wgpu_profiler::GpuProfiler;
33
34pub mod error;
35mod ready;
36mod setup;
37
38/// Core struct which contains the renderer world. Primary way to interact with
39/// the world.
40pub struct Renderer {
41    instructions: InstructionStreamPair,
42
43    /// The rendering profile used.
44    pub profile: RendererProfile,
45    /// Information about the adapter.
46    pub adapter_info: ExtendedAdapterInfo,
47    /// Queue all command buffers will be submitted to.
48    pub queue: Arc<Queue>,
49    /// Device all objects will be created with.
50    pub device: Arc<Device>,
51
52    /// Features of the device
53    pub features: Features,
54    /// Limits of the device
55    pub limits: Limits,
56    /// Downlevel limits of the device
57    pub downlevel: DownlevelCapabilities,
58    /// Handedness of all parts of this renderer.
59    pub handedness: Handedness,
60
61    /// Identifier allocator.
62    current_ident: AtomicUsize,
63    /// All the lockable data
64    pub data_core: Mutex<RendererDataCore>,
65
66    /// Tool which generates mipmaps from a texture.
67    pub mipmap_generator: MipmapGenerator,
68}
69
70/// All the mutex protected data within the renderer
71pub struct RendererDataCore {
72    /// Position and settings of the camera.
73    pub camera_manager: CameraManager,
74    /// Manages all vertex and index data.
75    pub mesh_manager: MeshManager,
76    /// Manages all 2D textures, including bindless bind group.
77    pub d2_texture_manager: TextureManager,
78    /// Manages all Cube textures, including bindless bind groups.
79    pub d2c_texture_manager: TextureManager,
80    /// Manages all materials, including material bind groups when CpuDriven.
81    pub material_manager: MaterialManager,
82    /// Manages all objects.
83    pub object_manager: ObjectManager,
84    /// Manages all directional lights, including their shadow maps.
85    pub directional_light_manager: DirectionalLightManager,
86    /// Manages skeletons, and their owned portion of the MeshManager's buffers
87    pub skeleton_manager: SkeletonManager,
88
89    /// Stores gpu timing and debug scopes.
90    pub profiler: GpuProfiler,
91
92    /// Stores a cache of render targets between graph invocations.
93    pub(crate) graph_texture_store: GraphTextureStore,
94}
95
96impl Renderer {
97    /// Create a new renderer with the given IAD.
98    ///
99    /// You can create your own IAD or call [`create_iad`](crate::create_iad).
100    ///
101    /// The aspect ratio is that of the window. This automatically configures
102    /// the camera. If None is passed, an aspect ratio of 1.0 is assumed.
103    pub fn new(
104        iad: InstanceAdapterDevice,
105        handedness: Handedness,
106        aspect_ratio: Option<f32>,
107    ) -> Result<Arc<Self>, RendererInitializationError> {
108        setup::create_renderer(iad, handedness, aspect_ratio)
109    }
110
111    /// Adds a 3D mesh to the renderer. This doesn't instantiate it to world. To
112    /// show this in the world, you need to create an [`Object`] using this
113    /// mesh.
114    ///
115    /// The handle will keep the mesh alive. All objects created will also keep
116    /// the mesh alive.
117    #[track_caller]
118    pub fn add_mesh(&self, mesh: Mesh) -> MeshHandle {
119        let handle = MeshManager::allocate(&self.current_ident);
120
121        self.instructions.push(
122            InstructionKind::AddMesh {
123                handle: handle.clone(),
124                mesh,
125            },
126            *Location::caller(),
127        );
128
129        handle
130    }
131
132    /// Adds a skeleton into the renderer. This combines a [`Mesh`] with a set
133    /// of joints that can be used to animate that mesh.
134    ///
135    /// The handle will keep the skeleton alive. All objects created will also
136    /// keep the skeleton alive. The skeleton will also keep the mesh it
137    /// references alive.
138    #[track_caller]
139    pub fn add_skeleton(&self, skeleton: Skeleton) -> SkeletonHandle {
140        let handle = SkeletonManager::allocate(&self.current_ident);
141        self.instructions.push(
142            InstructionKind::AddSkeleton {
143                handle: handle.clone(),
144                skeleton,
145            },
146            *Location::caller(),
147        );
148        handle
149    }
150
151    /// Add a 2D texture to the renderer. This can be used in a [`Material`].
152    ///
153    /// The handle will keep the texture alive. All materials created with this
154    /// texture will also keep the texture alive.
155    #[track_caller]
156    pub fn add_texture_2d(&self, texture: Texture) -> TextureHandle {
157        profiling::scope!("Add Texture 2D");
158
159        Self::validation_texture_format(texture.format);
160
161        let handle = TextureManager::allocate(&self.current_ident);
162        let size = Extent3d {
163            width: texture.size.x,
164            height: texture.size.y,
165            depth_or_array_layers: 1,
166        };
167
168        let mip_level_count = match texture.mip_count {
169            MipmapCount::Specific(v) => v.get(),
170            MipmapCount::Maximum => size.max_mips(),
171        };
172
173        let desc = TextureDescriptor {
174            label: None,
175            size,
176            mip_level_count,
177            sample_count: 1,
178            dimension: TextureDimension::D2,
179            format: texture.format,
180            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_SRC | TextureUsages::COPY_DST,
181        };
182
183        let (buffer, tex) = match texture.mip_source {
184            MipmapSource::Uploaded => (
185                None,
186                self.device.create_texture_with_data(&self.queue, &desc, &texture.data),
187            ),
188            MipmapSource::Generated => {
189                let desc = TextureDescriptor {
190                    usage: desc.usage | TextureUsages::RENDER_ATTACHMENT,
191                    ..desc
192                };
193                let tex = self.device.create_texture(&desc);
194
195                let format_desc = texture.format.describe();
196
197                // write first level
198                self.queue.write_texture(
199                    ImageCopyTexture {
200                        texture: &tex,
201                        mip_level: 0,
202                        origin: Origin3d::ZERO,
203                        aspect: TextureAspect::All,
204                    },
205                    &texture.data,
206                    ImageDataLayout {
207                        offset: 0,
208                        bytes_per_row: NonZeroU32::new(
209                            format_desc.block_size as u32 * (size.width / format_desc.block_dimensions.0 as u32),
210                        ),
211                        rows_per_image: None,
212                    },
213                    size,
214                );
215
216                let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor::default());
217
218                // generate mipmaps
219                self.mipmap_generator
220                    .generate_mipmaps(&self.device, &mut encoder, &tex, &desc);
221
222                (Some(encoder.finish()), tex)
223            }
224        };
225
226        let view = tex.create_view(&TextureViewDescriptor::default());
227        self.instructions.push(
228            InstructionKind::AddTexture {
229                handle: handle.clone(),
230                desc,
231                texture: tex,
232                view,
233                buffer,
234                cube: false,
235            },
236            *Location::caller(),
237        );
238        handle
239    }
240
241    /// Add a 2D texture to the renderer by copying a set of mipmaps from an
242    /// existing texture. This new can be used in a [`Material`].
243    ///
244    /// The handle will keep the texture alive. All materials created with this
245    /// texture will also keep the texture alive.
246    #[track_caller]
247    pub fn add_texture_2d_from_texture(&self, texture: TextureFromTexture) -> TextureHandle {
248        profiling::scope!("Add Texture 2D From Texture");
249
250        let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor::default());
251
252        let handle = TextureManager::allocate(&self.current_ident);
253
254        let data_core = self.data_core.lock();
255
256        let InternalTexture {
257            texture: old_texture,
258            desc: old_texture_desc,
259        } = data_core.d2_texture_manager.get_internal(texture.src.get_raw());
260
261        let new_size = old_texture_desc.mip_level_size(texture.start_mip).unwrap();
262
263        let mip_level_count = texture
264            .mip_count
265            .map_or_else(|| old_texture_desc.mip_level_count - texture.start_mip, |c| c.get());
266
267        let desc = TextureDescriptor {
268            size: new_size,
269            mip_level_count,
270            ..old_texture_desc.clone()
271        };
272
273        let tex = self.device.create_texture(&desc);
274
275        let view = tex.create_view(&TextureViewDescriptor::default());
276
277        for new_mip in 0..mip_level_count {
278            let old_mip = new_mip + texture.start_mip;
279
280            let _label = format_sso!("mip {} to {}", old_mip, new_mip);
281            profiling::scope!(&_label);
282
283            encoder.copy_texture_to_texture(
284                ImageCopyTexture {
285                    texture: old_texture,
286                    mip_level: old_mip,
287                    origin: Origin3d::ZERO,
288                    aspect: TextureAspect::All,
289                },
290                ImageCopyTexture {
291                    texture: &tex,
292                    mip_level: new_mip,
293                    origin: Origin3d::ZERO,
294                    aspect: TextureAspect::All,
295                },
296                old_texture_desc.mip_level_size(old_mip).unwrap(),
297            );
298        }
299        self.instructions.push(
300            InstructionKind::AddTexture {
301                handle: handle.clone(),
302                texture: tex,
303                desc,
304                view,
305                buffer: Some(encoder.finish()),
306                cube: false,
307            },
308            *Location::caller(),
309        );
310        handle
311    }
312
313    /// Adds a Cube texture to the renderer. This can be used as a cube
314    /// environment map by a render routine.
315    ///
316    /// The handle will keep the texture alive.
317    #[track_caller]
318    pub fn add_texture_cube(&self, texture: Texture) -> TextureHandle {
319        profiling::scope!("Add Texture Cube");
320
321        Self::validation_texture_format(texture.format);
322
323        let handle = TextureManager::allocate(&self.current_ident);
324        let size = Extent3d {
325            width: texture.size.x,
326            height: texture.size.y,
327            depth_or_array_layers: 6,
328        };
329
330        let mip_level_count = match texture.mip_count {
331            MipmapCount::Specific(v) => v.get(),
332            MipmapCount::Maximum => size.max_mips(),
333        };
334
335        let desc = TextureDescriptor {
336            label: None,
337            size,
338            mip_level_count,
339            sample_count: 1,
340            dimension: TextureDimension::D2,
341            format: texture.format,
342            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
343        };
344
345        let tex = self.device.create_texture_with_data(&self.queue, &desc, &texture.data);
346
347        let view = tex.create_view(&TextureViewDescriptor {
348            dimension: Some(TextureViewDimension::Cube),
349            ..TextureViewDescriptor::default()
350        });
351        self.instructions.push(
352            InstructionKind::AddTexture {
353                handle: handle.clone(),
354                texture: tex,
355                desc,
356                view,
357                buffer: None,
358                cube: true,
359            },
360            *Location::caller(),
361        );
362        handle
363    }
364
365    fn validation_texture_format(format: TextureFormat) {
366        let sample_type = format.describe().sample_type;
367        if let TextureSampleType::Float { filterable } = sample_type {
368            if !filterable {
369                panic!(
370                    "Textures formats must allow filtering with a linear filter. {:?} has sample type {:?} which does not.",
371                    format, sample_type
372                )
373            }
374        } else {
375            panic!(
376                "Textures formats must be sample-able as floating point. {:?} has sample type {:?}.",
377                format, sample_type
378            )
379        }
380    }
381
382    /// Adds a material to the renderer. This can be used in an [`Object`].
383    ///
384    /// The handle will keep the material alive. All objects created with this
385    /// material will also keep this material alive.
386    ///
387    /// The material will keep the inside textures alive.
388    #[track_caller]
389    pub fn add_material<M: Material>(&self, material: M) -> MaterialHandle {
390        let handle = MaterialManager::allocate(&self.current_ident);
391        self.instructions.push(
392            InstructionKind::AddMaterial {
393                handle: handle.clone(),
394                fill_invoke: Box::new(move |material_manager, device, profile, d2_manager, mat_handle| {
395                    material_manager.fill(device, profile, d2_manager, mat_handle, material)
396                }),
397            },
398            *Location::caller(),
399        );
400        handle
401    }
402
403    /// Updates a given material. Old references will be dropped.
404    #[track_caller]
405    pub fn update_material<M: Material>(&self, handle: &MaterialHandle, material: M) {
406        self.instructions.push(
407            InstructionKind::ChangeMaterial {
408                handle: handle.clone(),
409                change_invoke: Box::new(
410                    move |material_manager, device, profile, d2_manager, object_manager, mat_handle| {
411                        material_manager.update(device, profile, d2_manager, object_manager, mat_handle, material)
412                    },
413                ),
414            },
415            *Location::caller(),
416        )
417    }
418
419    /// Adds an object to the renderer. This will create a visible object using
420    /// the given mesh and materal.
421    ///
422    /// The handle will keep the material alive.
423    ///
424    /// The object will keep all materials, textures, and meshes alive.
425    #[track_caller]
426    pub fn add_object(&self, object: Object) -> ObjectHandle {
427        let handle = ObjectManager::allocate(&self.current_ident);
428        self.instructions.push(
429            InstructionKind::AddObject {
430                handle: handle.clone(),
431                object,
432            },
433            *Location::caller(),
434        );
435        handle
436    }
437
438    /// Duplicates an existing object in the renderer, returning the new
439    /// object's handle. Any changes specified in the `change` struct will be
440    /// applied to the duplicated object, and the same mesh, material and
441    /// transform as the original object will be used otherwise.
442    #[track_caller]
443    pub fn duplicate_object(&self, object_handle: &ObjectHandle, change: ObjectChange) -> ObjectHandle {
444        let dst_handle = ObjectManager::allocate(&self.current_ident);
445        self.instructions.push(
446            InstructionKind::DuplicateObject {
447                src_handle: object_handle.clone(),
448                dst_handle: dst_handle.clone(),
449                change,
450            },
451            *Location::caller(),
452        );
453        dst_handle
454    }
455
456    /// Move the given object to a new transform location.
457    #[track_caller]
458    pub fn set_object_transform(&self, handle: &ObjectHandle, transform: Mat4) {
459        self.instructions.push(
460            InstructionKind::SetObjectTransform {
461                handle: handle.get_raw(),
462                transform,
463            },
464            *Location::caller(),
465        );
466    }
467
468    /// Sets the joint positions for a skeleton. See
469    /// [Renderer::set_skeleton_joint_matrices] to set the vertex
470    /// transformations directly, without having to supply two separate
471    /// matrix vectors.
472    ///
473    /// ## Inputs
474    /// - `joint_global_positions`: Contains one transform matrix per bone,
475    ///   containing that bone's current clobal transform
476    /// - `inverse_bind_poses`: Contains one inverse bind transform matrix per
477    ///   bone, that is, the inverse of the bone's transformation at its rest
478    ///   position.
479    #[track_caller]
480    pub fn set_skeleton_joint_transforms(
481        &self,
482        handle: &SkeletonHandle,
483        joint_global_transforms: &[Mat4],
484        inverse_bind_transforms: &[Mat4],
485    ) {
486        self.set_skeleton_joint_matrices(
487            handle,
488            Skeleton::compute_joint_matrices(joint_global_transforms, inverse_bind_transforms),
489        );
490    }
491
492    /// Sets the joint matrices for a skeleton. The joint matrix is the
493    /// transformation that will be applied to a vertex affected by a joint.
494    /// Note that this is not the same as the joint's transformation. See
495    /// [Renderer::set_skeleton_joint_transforms] for an alternative method that
496    /// allows setting the joint transformation instead.
497    #[track_caller]
498    pub fn set_skeleton_joint_matrices(&self, handle: &SkeletonHandle, joint_matrices: Vec<Mat4>) {
499        self.instructions.push(
500            InstructionKind::SetSkeletonJointDeltas {
501                handle: handle.get_raw(),
502                joint_matrices,
503            },
504            *Location::caller(),
505        )
506    }
507
508    /// Add a sun-like light into the world.
509    ///
510    /// The handle will keep the light alive.
511    #[track_caller]
512    pub fn add_directional_light(&self, light: DirectionalLight) -> DirectionalLightHandle {
513        let handle = DirectionalLightManager::allocate(&self.current_ident);
514
515        self.instructions.push(
516            InstructionKind::AddDirectionalLight {
517                handle: handle.clone(),
518                light,
519            },
520            *Location::caller(),
521        );
522
523        handle
524    }
525
526    /// Updates the settings for given directional light.
527    #[track_caller]
528    pub fn update_directional_light(&self, handle: &DirectionalLightHandle, change: DirectionalLightChange) {
529        self.instructions.push(
530            InstructionKind::ChangeDirectionalLight {
531                handle: handle.get_raw(),
532                change,
533            },
534            *Location::caller(),
535        )
536    }
537
538    /// Sets the aspect ratio of the camera. This should correspond with the
539    /// aspect ratio of the user.
540    #[track_caller]
541    pub fn set_aspect_ratio(&self, ratio: f32) {
542        self.instructions
543            .push(InstructionKind::SetAspectRatio { ratio }, *Location::caller())
544    }
545
546    /// Sets the position, pov, or projection mode of the camera.
547    #[track_caller]
548    pub fn set_camera_data(&self, data: Camera) {
549        self.instructions
550            .push(InstructionKind::SetCameraData { data }, *Location::caller())
551    }
552
553    /// Render a frame of the scene onto the given output, using the given
554    /// RenderRoutine.
555    ///
556    /// The RendererStatistics may not be the results from this frame, but might
557    /// be the results from multiple frames ago.
558    pub fn ready(&self) -> (Vec<CommandBuffer>, ReadyData) {
559        ready::ready(self)
560    }
561}