inox2d_wgpu/
lib.rs

1#![allow(dead_code)]
2
3mod buffers;
4mod node_bundle;
5mod pipeline;
6
7use inox2d::math::camera::Camera;
8use inox2d::model::Model;
9use inox2d::nodes::node_data::{InoxData, MaskMode};
10use inox2d::puppet::Puppet;
11use inox2d::render::RenderCtxKind;
12
13use encase::ShaderType;
14use glam::{vec3, Mat4, UVec2, Vec2, Vec3};
15use inox2d::texture::decode_model_textures;
16use tracing::warn;
17use wgpu::util::TextureDataOrder;
18use wgpu::{util::DeviceExt, *};
19
20use self::node_bundle::{CompositeData, PartData};
21use self::{
22	buffers::buffers_for_puppet,
23	node_bundle::{node_bundles_for_model, NodeBundle},
24	pipeline::{InoxPipeline, Uniform},
25};
26
27pub struct Renderer {
28	setup: InoxPipeline,
29	model_texture_binds: Vec<BindGroup>,
30	buffers: buffers::InoxBuffers,
31	bundles: Vec<node_bundle::NodeBundle>,
32	pub camera: Camera,
33
34	viewport: UVec2,
35	// Cached, must change when viewport does
36	composite_texture: Option<Texture>,
37	mask_texture: Option<Texture>,
38}
39
40impl Renderer {
41	pub fn new(device: &Device, queue: &Queue, texture_format: TextureFormat, model: &Model, viewport: UVec2) -> Self {
42		let setup = InoxPipeline::create(device, texture_format);
43
44		let mut model_texture_binds = Vec::new();
45
46		let sampler = device.create_sampler(&SamplerDescriptor {
47			min_filter: FilterMode::Linear,
48			mag_filter: FilterMode::Linear,
49			mipmap_filter: FilterMode::Linear,
50			address_mode_u: AddressMode::ClampToBorder,
51			address_mode_v: AddressMode::ClampToBorder,
52			border_color: Some(SamplerBorderColor::TransparentBlack),
53			..SamplerDescriptor::default()
54		});
55
56		let shalltexs = decode_model_textures(model.textures.iter());
57		for shalltex in &shalltexs {
58			let texture_size = wgpu::Extent3d {
59				width: shalltex.width(),
60				height: shalltex.height(),
61				depth_or_array_layers: 1,
62			};
63
64			let texture = device.create_texture_with_data(
65				queue,
66				&wgpu::TextureDescriptor {
67					size: texture_size,
68					mip_level_count: 1,
69					sample_count: 1,
70					dimension: wgpu::TextureDimension::D2,
71					format: wgpu::TextureFormat::Rgba8Unorm,
72					usage: wgpu::TextureUsages::TEXTURE_BINDING,
73					label: Some("texture"),
74					view_formats: &[],
75				},
76				TextureDataOrder::LayerMajor,
77				shalltex.pixels(),
78			);
79
80			let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
81
82			let texture_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
83				layout: &setup.texture_layout,
84				entries: &[
85					wgpu::BindGroupEntry {
86						binding: 0,
87						resource: wgpu::BindingResource::TextureView(&texture_view),
88					},
89					wgpu::BindGroupEntry {
90						binding: 1,
91						resource: wgpu::BindingResource::Sampler(&sampler),
92					},
93				],
94				label: Some("texture bind group"),
95			});
96			model_texture_binds.push(texture_bind);
97		}
98
99		let buffers = buffers_for_puppet(device, &model.puppet, setup.uniform_alignment_needed);
100		let bundles = node_bundles_for_model(device, &setup, &buffers, &model_texture_binds, &model.puppet);
101
102		Self {
103			setup,
104			buffers,
105			bundles,
106
107			model_texture_binds,
108			camera: Camera::default(),
109
110			viewport,
111			composite_texture: None,
112			mask_texture: None,
113		}
114	}
115
116	pub fn resize(&mut self, viewport: UVec2) {
117		self.viewport = viewport;
118		self.composite_texture = None;
119		self.mask_texture = None;
120	}
121
122	#[allow(clippy::too_many_arguments)]
123	fn render_part(
124		&self,
125		puppet: &Puppet,
126
127		view: &TextureView,
128		mask_view: &TextureView,
129		uniform_group: &BindGroup,
130
131		op: LoadOp<Color>,
132		encoder: &mut CommandEncoder,
133
134		PartData(bundle, masks): &PartData,
135	) {
136		let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
137			label: Some("Part Render Pass"),
138			color_attachments: &[Some(wgpu::RenderPassColorAttachment {
139				view,
140				resolve_target: None,
141				ops: wgpu::Operations {
142					load: op,
143					store: StoreOp::Store,
144				},
145			})],
146			depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
147				view: mask_view,
148				depth_ops: None,
149				stencil_ops: Some(Operations {
150					load: wgpu::LoadOp::Clear(u32::from(!masks.is_empty())),
151					store: StoreOp::Store,
152				}),
153			}),
154			timestamp_writes: None,    // todo!(),
155			occlusion_query_set: None, // todo!(),
156		});
157
158		render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
159		render_pass.set_vertex_buffer(1, self.buffers.uv_buffer.slice(..));
160		render_pass.set_vertex_buffer(2, self.buffers.deform_buffer.slice(..));
161		render_pass.set_index_buffer(self.buffers.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
162		render_pass.set_pipeline(&self.setup.mask_pipeline);
163
164		for mask in masks {
165			let node = puppet.nodes.get_node(mask.source).unwrap();
166			let part = if let InoxData::Part(part) = &node.data {
167				part
168			} else {
169				todo!()
170			};
171
172			render_pass.set_bind_group(1, &self.model_texture_binds[part.tex_albedo.raw()], &[]);
173			render_pass.set_bind_group(2, &self.model_texture_binds[part.tex_emissive.raw()], &[]);
174			render_pass.set_bind_group(3, &self.model_texture_binds[part.tex_bumpmap.raw()], &[]);
175
176			render_pass.set_bind_group(
177				0,
178				uniform_group,
179				&[(self.setup.uniform_alignment_needed * self.buffers.uniform_index_map[&mask.source]) as u32],
180			);
181
182			match mask.mode {
183				MaskMode::Mask => {
184					render_pass.set_stencil_reference(0);
185				}
186				MaskMode::Dodge => {
187					render_pass.set_stencil_reference(1);
188				}
189			}
190
191			let node_rinf = &puppet.render_ctx.node_render_ctxs[&mask.source];
192			if let RenderCtxKind::Part(pinf) = &node_rinf.kind {
193				let range = (pinf.index_offset as u32)..(pinf.index_offset as u32 + pinf.index_len as u32);
194				render_pass.draw_indexed(range, 0, 0..1);
195			} else {
196				warn!(
197					"Node mask {:?} is not a part but is trying to get rendered as one",
198					mask.source
199				);
200			}
201		}
202
203		render_pass.set_stencil_reference(0);
204		render_pass.execute_bundles(std::iter::once(bundle));
205
206		drop(render_pass);
207	}
208
209	#[allow(clippy::too_many_arguments)]
210	fn render_composite(
211		&self,
212		puppet: &Puppet,
213
214		view: &TextureView,
215		mask_view: &TextureView,
216		uniform_group: &BindGroup,
217
218		op: LoadOp<Color>,
219		encoder: &mut CommandEncoder,
220
221		composite_view: &TextureView,
222		composite_bind: &BindGroup,
223		CompositeData(parts, uuid): &CompositeData,
224	) {
225		for (i, data) in parts.iter().enumerate() {
226			self.render_part(
227				puppet,
228				composite_view,
229				mask_view,
230				uniform_group,
231				if i == 0 {
232					LoadOp::Clear(Color::TRANSPARENT)
233				} else {
234					LoadOp::Load
235				},
236				encoder,
237				data,
238			);
239		}
240
241		let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
242			label: Some("Render Pass"),
243			color_attachments: &[Some(wgpu::RenderPassColorAttachment {
244				view,
245				resolve_target: None,
246				ops: wgpu::Operations {
247					load: op,
248					store: StoreOp::Store,
249				},
250			})],
251			depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
252				view: mask_view,
253				depth_ops: None,
254				stencil_ops: None,
255			}),
256			timestamp_writes: None,    // todo!(),
257			occlusion_query_set: None, //todo!(),
258		});
259
260		render_pass.set_vertex_buffer(0, self.buffers.vertex_buffer.slice(..));
261		render_pass.set_vertex_buffer(1, self.buffers.uv_buffer.slice(..));
262		render_pass.set_vertex_buffer(2, self.buffers.deform_buffer.slice(..));
263		render_pass.set_index_buffer(self.buffers.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
264
265		let child = puppet.nodes.get_node(*uuid).unwrap();
266		let child = if let InoxData::Composite(comp) = &child.data {
267			comp
268		} else {
269			todo!()
270		};
271
272		render_pass.set_pipeline(&self.setup.composite_pipelines[&child.draw_state.blend_mode]);
273
274		render_pass.set_bind_group(
275			0,
276			uniform_group,
277			&[(self.setup.uniform_alignment_needed * self.buffers.uniform_index_map[uuid]) as u32],
278		);
279		render_pass.set_bind_group(1, composite_bind, &[]);
280		render_pass.set_bind_group(2, composite_bind, &[]);
281		render_pass.set_bind_group(3, composite_bind, &[]);
282		render_pass.draw_indexed(0..6, 0, 0..1);
283
284		drop(render_pass);
285	}
286
287	/// It is a logical error to pass in a different puppet than the one passed to create.
288	pub fn render(&mut self, queue: &Queue, device: &Device, puppet: &Puppet, view: &TextureView) {
289		let uniform_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
290			label: Some("inox2d uniform bind group"),
291			layout: &self.setup.uniform_layout,
292			entries: &[wgpu::BindGroupEntry {
293				binding: 1,
294				resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
295					buffer: &self.buffers.uniform_buffer,
296					offset: 0,
297					size: wgpu::BufferSize::new(Uniform::min_size().into()),
298				}),
299			}],
300		});
301
302		let composite_texture = self.composite_texture.get_or_insert_with(|| {
303			device.create_texture(&wgpu::TextureDescriptor {
304				size: wgpu::Extent3d {
305					width: self.viewport.x,
306					height: self.viewport.y,
307					depth_or_array_layers: 1,
308				},
309				mip_level_count: 1,
310				sample_count: 1,
311				dimension: wgpu::TextureDimension::D2,
312				format: wgpu::TextureFormat::Bgra8Unorm,
313				usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
314				label: Some("texture"),
315				view_formats: &[],
316			})
317		});
318
319		let composite_view = composite_texture.create_view(&wgpu::TextureViewDescriptor::default());
320
321		let mask_texture = self.mask_texture.get_or_insert_with(|| {
322			device.create_texture(&wgpu::TextureDescriptor {
323				size: wgpu::Extent3d {
324					width: self.viewport.x,
325					height: self.viewport.y,
326					depth_or_array_layers: 1,
327				},
328				mip_level_count: 1,
329				sample_count: 1,
330				dimension: wgpu::TextureDimension::D2,
331				format: wgpu::TextureFormat::Depth24PlusStencil8,
332				usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
333				label: Some("texture"),
334				view_formats: &[],
335			})
336		});
337
338		let mask_view = mask_texture.create_view(&wgpu::TextureViewDescriptor::default());
339
340		let sampler = device.create_sampler(&SamplerDescriptor {
341			min_filter: FilterMode::Linear,
342			mag_filter: FilterMode::Linear,
343			mipmap_filter: FilterMode::Linear,
344			address_mode_u: AddressMode::ClampToBorder,
345			address_mode_v: AddressMode::ClampToBorder,
346			border_color: Some(SamplerBorderColor::TransparentBlack),
347			..SamplerDescriptor::default()
348		});
349
350		let composite_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
351			layout: &self.setup.texture_layout,
352			entries: &[
353				wgpu::BindGroupEntry {
354					binding: 0,
355					resource: wgpu::BindingResource::TextureView(&composite_view),
356				},
357				wgpu::BindGroupEntry {
358					binding: 1,
359					resource: wgpu::BindingResource::Sampler(&sampler),
360				},
361			],
362			label: Some("texture bind group"),
363		});
364
365		for uuid in puppet.nodes.all_node_ids() {
366			let node = puppet.nodes.get_node(uuid).unwrap();
367
368			let unif = match &node.data {
369				InoxData::Part(_) => {
370					let mvp = Mat4::from_scale(vec3(1.0, 1.0, 0.0))
371						* self.camera.matrix(self.viewport.as_vec2())
372						* puppet.render_ctx.node_render_ctxs[&uuid].trans;
373
374					Uniform {
375						opacity: 1.0,
376						mult_color: Vec3::ONE,
377						screen_color: Vec3::ZERO,
378						emission_strength: 0.0,
379						offset: Vec2::ZERO,
380						mvp,
381					}
382				}
383				InoxData::Composite(_) => Uniform {
384					opacity: 1.0,
385					mult_color: Vec3::ONE,
386					screen_color: Vec3::ZERO,
387					emission_strength: 0.0,
388					offset: Vec2::ZERO,
389					mvp: Mat4::IDENTITY,
390				},
391				_ => continue,
392			};
393
394			let offset = self.setup.uniform_alignment_needed * self.buffers.uniform_index_map[&uuid];
395
396			let mut buffer = encase::UniformBuffer::new(Vec::new());
397			buffer.write(&unif).unwrap();
398			queue.write_buffer(&self.buffers.uniform_buffer, offset as u64, buffer.as_ref());
399		}
400
401		// Upload deform buffers
402		let mut buffer = encase::DynamicStorageBuffer::new(Vec::new());
403		(buffer.write(&puppet.render_ctx.vertex_buffers.deforms)).unwrap();
404		queue.write_buffer(&self.buffers.deform_buffer, 0, buffer.as_ref());
405
406		let mut first = true;
407
408		let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
409			label: Some("Part Render Encoder"),
410		});
411
412		for bundle in &self.bundles {
413			let op = if first {
414				first = false;
415
416				wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
417			} else {
418				wgpu::LoadOp::Load
419			};
420
421			match bundle {
422				NodeBundle::Part(data) => {
423					self.render_part(puppet, view, &mask_view, &uniform_group, op, &mut encoder, data);
424				}
425				NodeBundle::Composite(data) => {
426					self.render_composite(
427						puppet,
428						view,
429						&mask_view,
430						&uniform_group,
431						op,
432						&mut encoder,
433						&composite_view,
434						&composite_bind,
435						data,
436					);
437				}
438			}
439		}
440		queue.submit(std::iter::once(encoder.finish()));
441	}
442}