truck_platform/
lib.rs

1//! Graphic utility library based on wgpu.
2//!
3//! This crate is independent from other truck crates except `truck-base`.
4//! It provides an API that allows users to handle drawing elements in a unified manner.
5//! By implementing the [`Rendered`] trait, developers can define
6//! their own rendering elements and have them rendered in [`Scene`]
7//! in the same way as other rendering elements provided by truck.
8//!
9//! This documentation is intended to be read by two kinds of people: users and developers.
10//! Users, those who just want to draw the shape of an existing mesh or boundary representation,
11//! will only use:
12//! - [`Scene`],
13//! - [`SceneDescriptor`],
14//! - [`DeviceHandler`],
15//! - [`Camera`], and
16//! - [`Light`].
17//!
18//! If you are a developer, who wants to try out new
19//! visual representations, you can implement Rendered in your own structure and standardize it in
20//! a form that can be used by users in [`Scene`].
21//!
22//! The sample code in this crate is for developers.
23//! Users may wish to refer to the one in `truck-rendimpl`.
24//!
25//! [`Rendered`]: ./trait.Rendered.html
26//! [`Scene`]: ./struct.Scene.html
27//! [`DeviceHandler`]: ./struct.DeviceHandler.html
28//! [`SceneDescriptor`]: ./struct.SceneDescriptor.html
29//! [`Camera`]: ./struct.Camera.html
30//! [`Light`]: ./struct.Light.html
31
32#![cfg_attr(not(debug_assertions), deny(warnings))]
33#![deny(clippy::all, rust_2018_idioms)]
34#![warn(
35    missing_docs,
36    missing_debug_implementations,
37    trivial_casts,
38    trivial_numeric_casts,
39    unsafe_code,
40    unstable_features,
41    unused_import_braces,
42    unused_qualifications
43)]
44
45use bytemuck::{Pod, Zeroable};
46use derive_more::*;
47use std::sync::Arc;
48use truck_base::cgmath64::*;
49pub use wgpu;
50use wgpu::util::{BufferInitDescriptor, DeviceExt};
51use wgpu::*;
52
53#[cfg(not(target_arch = "wasm32"))]
54use std::time::Instant as TimeInstant;
55#[cfg(target_arch = "wasm32")]
56use web_time::Instant as TimeInstant;
57
58/// maximum number of light
59pub const LIGHT_MAX: usize = 255;
60
61#[repr(C)]
62#[derive(Clone, Copy, Debug, Zeroable, Pod)]
63struct CameraInfo {
64    camera_matrix: [[f32; 4]; 4],
65    camera_projection: [[f32; 4]; 4],
66}
67
68#[repr(C)]
69#[derive(Clone, Copy, Debug, Zeroable, Pod)]
70struct LightInfo {
71    light_position: [f32; 4],
72    light_color: [f32; 4],
73    light_type: [u32; 4],
74}
75
76#[repr(C)]
77#[derive(Clone, Copy, Debug, Zeroable, Pod)]
78struct SceneInfo {
79    background_color: [f32; 4],
80    resolution: [u32; 2],
81    time: f32,
82    num_of_lights: u32,
83}
84
85/// safe handler of GPU buffer
86/// [`Buffer`](https://docs.rs/wgpu/0.10.1/wgpu/struct.Buffer.html)
87#[derive(Debug)]
88pub struct BufferHandler {
89    buffer: Buffer,
90    size: u64,
91    stride: u64,
92}
93
94/// Utility for [`BindGroupLayoutEntry`]
95///
96/// The member variables of this struct are the ones of [`BindGroupLayoutEntry`]
97/// with only `binding` removed. We can create `BindGroupLayout` by
98/// giving its iterator to the function truck_platform::[`create_bind_group_layout`].
99/// # Examples
100/// ```
101/// use std::sync::{Arc, Mutex};
102/// use truck_platform::*;
103/// use wgpu::*;
104/// let handler = pollster::block_on(DeviceHandler::default_device());
105/// let entries = [
106///     PreBindGroupLayoutEntry {
107///         visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
108///         ty: BindingType::Buffer {
109///             ty: BufferBindingType::Uniform,
110///             has_dynamic_offset: false,
111///             min_binding_size: None,
112///         },
113///         count: None,
114///     },
115///     PreBindGroupLayoutEntry {
116///         visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
117///         ty: BindingType::Buffer {
118///             ty: BufferBindingType::Uniform,
119///             has_dynamic_offset: false,
120///             min_binding_size: None,
121///         },
122///         count: None,
123///     },
124/// ];
125/// let layout: BindGroupLayout = bind_group_util::create_bind_group_layout(handler.device(), &entries);
126/// ```
127///
128/// [`BindGroupLayoutEntry`]: https://docs.rs/wgpu/0.10.1/wgpu/struct.BindGroupLayoutEntry.html
129#[doc(hidden)]
130#[derive(Debug)]
131pub struct PreBindGroupLayoutEntry {
132    pub visibility: ShaderStages,
133    pub ty: BindingType,
134    pub count: Option<std::num::NonZeroU32>,
135}
136
137/// A collection of GPU buffers used by `wgpu` for rendering
138#[doc(hidden)]
139#[derive(Debug, Clone)]
140pub struct RenderObject {
141    vertex_buffer: Arc<BufferHandler>,
142    index_buffer: Option<Arc<BufferHandler>>,
143    pipeline: Arc<RenderPipeline>,
144    bind_group_layout: Arc<BindGroupLayout>,
145    bind_group: Arc<BindGroup>,
146    visible: bool,
147}
148
149/// the projection type of camera
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
151pub enum ProjectionType {
152    /// perspective camera
153    Perspective,
154    /// parallel camera
155    Parallel,
156}
157
158/// Camera
159///
160/// A [`Scene`](./struct.Scene.html) holds only one `Camera`.
161#[derive(Debug, Clone, Copy)]
162pub struct Camera {
163    /// camera matrix
164    ///
165    /// This matrix must be in the Euclidean momentum group, the semi-direct product of O(3) and R^3.
166    pub matrix: Matrix4,
167    projection: Matrix4,
168    projection_type: ProjectionType,
169}
170
171/// Rays corresponding to a point on the screen, defined by the camera.
172#[derive(Clone, Copy, Debug)]
173pub struct Ray {
174    origin: Point3,
175    direction: Vector3,
176}
177
178/// the kinds of light sources: point or uniform
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
180pub enum LightType {
181    /// point light source
182    Point,
183    /// uniform light source
184    Uniform,
185}
186
187/// Light
188///
189/// There is no limit to the number of lights that can be added to a [`Scene`](./struct.Scene.html).
190/// The information about the lights is sent to the shader as a storage buffer
191/// (cf: [`Scene::lights_buffer()`](./struct.Scene.html#method.lights_buffer)).
192#[derive(Clone, Debug, PartialEq)]
193pub struct Light {
194    /// position of light
195    pub position: Point3,
196    /// [0, 1] range RGB color of light
197    pub color: Vector3,
198    /// type of light source: point or uniform
199    pub light_type: LightType,
200}
201
202/// Chain that holds [`Device`], [`Queue`] and [`SurfaceConfiguration`].
203///
204/// This struct is used for creating [`Scene`].
205/// [`Device`] and [`Queue`] must be wrapped `Arc`,
206/// and [`SurfaceConfiguration`] `Arc<Mutex>`.
207///
208/// [`Device`]: https://docs.rs/wgpu/0.10.1/wgpu/struct.Device.html
209/// [`Queue`]: https://docs.rs/wgpu/0.10.1/wgpu/struct.Queue.html
210/// [`SurfaceConfiguration`]: https://docs.rs/wgpu/0.10.1/wgpu/struct.SurfaceConfiguration.html
211/// [`Scene`]: ./struct.Scene.html
212#[derive(Debug, Clone)]
213pub struct DeviceHandler {
214    adapter: Arc<Adapter>,
215    device: Arc<Device>,
216    queue: Arc<Queue>,
217}
218
219#[derive(Debug)]
220struct WindowHandler {
221    window: Arc<winit::window::Window>,
222    surface: Arc<Surface<'static>>,
223}
224
225/// The unique ID for `Rendered` struct.
226///
227/// This structure is not used explicitly by users for modeling by `truck-modeling` and `truck-rendimpl`.
228/// If you want to define a new drawing element in which "Rendered" will be implemented, you need to add
229/// this structure to the member variables of that drawing element.
230///
231/// This structure is assigned a unique value each time it is generated by `RenderID::gen()`.
232/// This property allows us to map a `Rendred` entity to data in GPU memory held by `Scene`.
233/// ```
234/// use truck_platform::RenderID;
235/// assert_ne!(RenderID::gen(), RenderID::gen());
236/// ```
237#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
238pub struct RenderID(usize);
239
240/// Configuration for studio to shoot the scene.
241#[derive(Debug, Clone)]
242pub struct StudioConfig {
243    /// camera of the scene. Default is `Camera::default()`.
244    pub camera: Camera,
245    /// All lights in the scene. Default is `vec![Light::default()]`.
246    pub lights: Vec<Light>,
247    /// background color. Default is `Color::BLACK`.
248    pub background: Color,
249}
250
251/// Configuration for buffer preparation
252#[derive(Clone, Debug, Copy)]
253pub struct BackendBufferConfig {
254    /// depth test flag. Default is `true`.
255    pub depth_test: bool,
256    /// sample count for anti-aliasing by MSAA. 1, 2, 4, 8, or 16. Default is `1`.
257    pub sample_count: u32,
258}
259
260/// Configuration for rendering texture
261#[derive(Clone, Debug, Copy)]
262pub struct RenderTextureConfig {
263    /// canvas size `(width, height)`. Default is `(1024, 768)`.
264    pub canvas_size: (u32, u32),
265    /// texture format. Default is `TextureFormat::Rgba8Unorm`.
266    pub format: TextureFormat,
267}
268
269/// Configures of [`Scene`](./struct.Scene.html).
270#[derive(Debug, Clone, Default)]
271pub struct SceneDescriptor {
272    /// Configures for studio i.e. camera, lights, and background.
273    pub studio: StudioConfig,
274    /// Configures buffer preparation, depth and MSAA.
275    pub backend_buffer: BackendBufferConfig,
276    /// Configuration for rendering texture
277    pub render_texture: RenderTextureConfig,
278}
279
280/// Configures of [`WindowScene`](./struct.WindowScene.html).
281/// Compared to the structure `SceneDescriptor`, this excludes `render_texture`, which can be obtained from window.
282#[derive(Debug, Clone, Default)]
283pub struct WindowSceneDescriptor {
284    /// Configures for studio i.e. camera, lights, and background.
285    pub studio: StudioConfig,
286    /// Configures buffer preparation, depth and MSAA.
287    pub backend_buffer: BackendBufferConfig,
288}
289
290/// Wraps `wgpu` and provides an intuitive graphics API.
291///
292/// `Scene` is the most important in `truck-platform`.
293/// This structure holds information about rendering and
294/// serves as a bridge to the actual rendering of `Rendered` objects.
295#[derive(Debug)]
296pub struct Scene {
297    device_handler: DeviceHandler,
298    objects: SliceHashMap<RenderID, RenderObject>,
299    bind_group_layout: BindGroupLayout,
300    foward_depth: Option<Texture>,
301    sampling_buffer: Option<Texture>,
302    scene_desc: SceneDescriptor,
303    clock: TimeInstant,
304}
305
306/// Utility for wrapper
307#[derive(Debug, Deref, DerefMut)]
308pub struct WindowScene {
309    #[deref]
310    #[deref_mut]
311    scene: Scene,
312    window_handler: WindowHandler,
313}
314
315/// Rendered objects in the scene.
316pub trait Rendered {
317    /// Returns the render id.
318    ///
319    /// [`RenderID`](./struct.RenderID.html) is a key that maps `self` to a drawing element.
320    /// Each object must have a RenderID to ensure that there are no duplicates.
321    fn render_id(&self) -> RenderID;
322    /// Creates the pair (vertex buffer, index buffer).
323    fn vertex_buffer(
324        &self,
325        device_handler: &DeviceHandler,
326    ) -> (Arc<BufferHandler>, Option<Arc<BufferHandler>>);
327    /// Creates the bind group layout.
328    fn bind_group_layout(&self, device_handler: &DeviceHandler) -> Arc<BindGroupLayout>;
329    /// Creates the bind group in `set = 1`.
330    fn bind_group(
331        &self,
332        device_handler: &DeviceHandler,
333        layout: &BindGroupLayout,
334    ) -> Arc<BindGroup>;
335    /// Creates the render pipeline.
336    fn pipeline(
337        &self,
338        device_handler: &DeviceHandler,
339        layout: &PipelineLayout,
340        scene_descriptor: &SceneDescriptor,
341    ) -> Arc<RenderPipeline>;
342    #[doc(hidden)]
343    fn render_object(&self, scene: &Scene) -> RenderObject {
344        let (vertex_buffer, index_buffer) = self.vertex_buffer(scene.device_handler());
345        let bind_group_layout = self.bind_group_layout(scene.device_handler());
346        let bind_group = self.bind_group(scene.device_handler(), &bind_group_layout);
347        let pipeline_layout = scene
348            .device()
349            .create_pipeline_layout(&PipelineLayoutDescriptor {
350                bind_group_layouts: &[&scene.bind_group_layout, &bind_group_layout],
351                push_constant_ranges: &[],
352                label: None,
353            });
354        let pipeline = self.pipeline(scene.device_handler(), &pipeline_layout, &scene.scene_desc);
355        RenderObject {
356            vertex_buffer,
357            index_buffer,
358            bind_group_layout,
359            bind_group,
360            pipeline,
361            visible: true,
362        }
363    }
364}
365
366mod buffer_handler;
367mod camera;
368mod light;
369#[doc(hidden)]
370pub mod rendered_macros;
371mod scene;
372mod slice_hashmap;
373use slice_hashmap::SliceHashMap;
374
375#[doc(hidden)]
376pub mod bind_group_util {
377    use crate::*;
378    #[doc(hidden)]
379    pub fn create_bind_group<'a, T: IntoIterator<Item = BindingResource<'a>>>(
380        device: &Device,
381        layout: &BindGroupLayout,
382        resources: T,
383    ) -> BindGroup {
384        let entries: &Vec<BindGroupEntry<'_>> = &resources
385            .into_iter()
386            .enumerate()
387            .map(move |(i, resource)| BindGroupEntry {
388                binding: i as u32,
389                resource,
390            })
391            .collect();
392        device.create_bind_group(&BindGroupDescriptor {
393            layout,
394            entries,
395            label: None,
396        })
397    }
398
399    #[doc(hidden)]
400    pub fn create_bind_group_layout<'a, T: IntoIterator<Item = &'a PreBindGroupLayoutEntry>>(
401        device: &Device,
402        entries: T,
403    ) -> BindGroupLayout {
404        let vec: Vec<_> = entries
405            .into_iter()
406            .enumerate()
407            .map(|(i, e)| BindGroupLayoutEntry {
408                binding: i as u32,
409                visibility: e.visibility,
410                ty: e.ty,
411                count: e.count,
412            })
413            .collect();
414        device.create_bind_group_layout(&BindGroupLayoutDescriptor {
415            label: None,
416            entries: &vec,
417        })
418    }
419}