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 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, occlusion_query_set: None, });
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, occlusion_query_set: None, });
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 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 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}