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}