| 320 | |
| 321 | impl MaterialSystem { |
| 322 | pub fn new( |
| 323 | device: &wgpu::Device, |
| 324 | queue: &wgpu::Queue, |
| 325 | // Joint-buffer plumbing is per-draw (used by ensure_draw_slot, |
| 326 | // not at construction time). Kept on the constructor's |
| 327 | // signature for symmetry with the per-draw path. |
| 328 | _joint_buffer: &wgpu::Buffer, |
| 329 | ) -> Self { |
| 330 | let layouts = MaterialAbiLayouts::create(device); |
| 331 | |
| 332 | // Per-frame UBO. |
| 333 | let per_frame_buffer = device.create_buffer(&wgpu::BufferDescriptor { |
| 334 | label: Some("material_per_frame"), |
| 335 | size: std::mem::size_of::<PerFrameUniforms>() as u64, |
| 336 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, |
| 337 | mapped_at_creation: false, |
| 338 | }); |
| 339 | let per_frame_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { |
| 340 | label: Some("material_per_frame_bg"), |
| 341 | layout: &layouts.per_frame, |
| 342 | entries: &[wgpu::BindGroupEntry { |
| 343 | binding: 0, resource: per_frame_buffer.as_entire_binding(), |
| 344 | }], |
| 345 | }); |
| 346 | |
| 347 | // Default white 1×1 texture + sampler shared across optional PBR bindings. |
| 348 | let default_white_tex = device.create_texture_with_data( |
| 349 | queue, |
| 350 | &wgpu::TextureDescriptor { |
| 351 | label: Some("material_default_white"), |
| 352 | size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, |
| 353 | mip_level_count: 1, |
| 354 | sample_count: 1, |
| 355 | dimension: wgpu::TextureDimension::D2, |
| 356 | format: wgpu::TextureFormat::Rgba8UnormSrgb, |
| 357 | usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, |
| 358 | view_formats: &[], |
| 359 | }, |
| 360 | Default::default(), |
| 361 | &[255, 255, 255, 255], |
| 362 | ); |
| 363 | let default_white_view = default_white_tex.create_view(&Default::default()); |
| 364 | |
| 365 | // EN-011 — Default 1×1 black HDR texture for the planar |
| 366 | // reflection slot (binding 12). Format is Rgba16Float to match |
| 367 | // the actual probe RT so the bind-group layout is identical |
| 368 | // whether a probe is bound or the default. |
| 369 | let black_pixel: [u16; 4] = [0, 0, 0, 0]; |
| 370 | let default_black_tex = device.create_texture_with_data( |
| 371 | queue, |
| 372 | &wgpu::TextureDescriptor { |
| 373 | label: Some("material_default_black_reflection"), |
| 374 | size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, |
| 375 | mip_level_count: 1, |
| 376 | sample_count: 1, |
| 377 | dimension: wgpu::TextureDimension::D2, |
| 378 | format: super::formats::HDR_FORMAT, |
| 379 | usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, |