1use std::{collections::HashMap, env, sync::Arc};
2
3use anyhow::{Context, Result, anyhow, bail};
4
5use crate::{
6 BindGroupLayoutSpec, Binding, BindingResource, BufferDesc, BufferId, BufferResource,
7 ComputeDispatch, ComputePassDesc, ComputeProgramDesc, CopyTextureDesc, DrawCommand,
8 FrameUpdate, LoadOp, PassDesc, Program, ProgramDesc, ProgramId, RenderPassDesc, RenderPlan,
9 RenderProgramDesc, SamplerId, TextureDesc, TextureId, TextureResource, Upload,
10};
11
12struct RuntimeTexture {
13 texture: Arc<wgpu::Texture>,
14 view: wgpu::TextureView,
15 desc: TextureDesc,
16}
17
18struct RuntimeBuffer {
19 buffer: wgpu::Buffer,
20 desc: BufferDesc,
21}
22
23struct RuntimeSampler {
24 sampler: wgpu::Sampler,
25}
26
27struct RuntimeRenderProgram {
28 pipeline: wgpu::RenderPipeline,
29 bind_group_layouts: Vec<wgpu::BindGroupLayout>,
30}
31
32struct RuntimeComputeProgram {
33 pipeline: wgpu::ComputePipeline,
34 bind_group_layouts: Vec<wgpu::BindGroupLayout>,
35}
36
37enum RuntimeProgram {
38 Render(RuntimeRenderProgram),
39 Compute(RuntimeComputeProgram),
40}
41
42pub struct Renderer {
43 pub device: wgpu::Device,
44 pub queue: wgpu::Queue,
45 textures: Vec<RuntimeTexture>,
46 buffers: Vec<RuntimeBuffer>,
47 samplers: Vec<RuntimeSampler>,
48 programs: Vec<RuntimeProgram>,
49}
50
51impl Renderer {
52 pub async fn new() -> Result<Self> {
53 let instance =
54 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
55 let power_preference =
56 wgpu::PowerPreference::from_env().unwrap_or(wgpu::PowerPreference::HighPerformance);
57 let force_fallback_adapter = env_flag("LUMEN_GPU_FORCE_FALLBACK_ADAPTER");
58 let adapter = instance
59 .request_adapter(&wgpu::RequestAdapterOptions {
60 power_preference,
61 compatible_surface: None,
62 force_fallback_adapter,
63 })
64 .await
65 .context("no compatible wgpu adapter")?;
66 let adapter_info = adapter.get_info();
67 tracing::info!(
68 target: "lumen_gpu",
69 adapter = %adapter_info.name,
70 backend = ?adapter_info.backend,
71 device_type = ?adapter_info.device_type,
72 "selected wgpu adapter"
73 );
74 let (device, queue) = adapter
75 .request_device(&wgpu::DeviceDescriptor::default())
76 .await
77 .context("create wgpu device")?;
78 Ok(Self::from_device(device, queue))
79 }
80
81 pub fn from_device(device: wgpu::Device, queue: wgpu::Queue) -> Self {
82 Self {
83 device,
84 queue,
85 textures: Vec::new(),
86 buffers: Vec::new(),
87 samplers: Vec::new(),
88 programs: Vec::new(),
89 }
90 }
91
92 pub fn prepare_plan(&mut self, plan: &RenderPlan) -> Result<()> {
93 tracing::debug!(
94 target: "lumen_gpu",
95 textures = plan.textures.len(),
96 buffers = plan.buffers.len(),
97 samplers = plan.samplers.len(),
98 programs = plan.programs.len(),
99 passes = plan.passes.len(),
100 "prepare render plan"
101 );
102 self.textures = plan
103 .textures
104 .iter()
105 .map(|resource| self.create_texture(resource))
106 .collect::<Result<Vec<_>>>()?;
107 self.buffers = plan
108 .buffers
109 .iter()
110 .map(|resource| self.create_buffer(resource))
111 .collect();
112 self.samplers = plan
113 .samplers
114 .iter()
115 .map(|resource| RuntimeSampler {
116 sampler: self.device.create_sampler(&resource.desc),
117 })
118 .collect();
119 self.programs = plan
120 .programs
121 .iter()
122 .map(|program| self.create_program(program))
123 .collect::<Result<Vec<_>>>()?;
124 Ok(())
125 }
126
127 pub fn texture_view(&self, id: TextureId) -> Option<&wgpu::TextureView> {
128 self.textures
129 .get(id.0 as usize)
130 .map(|texture| &texture.view)
131 }
132
133 pub fn texture(&self, id: TextureId) -> Option<&wgpu::Texture> {
134 self.textures
135 .get(id.0 as usize)
136 .map(|texture| texture.texture.as_ref())
137 }
138
139 pub fn texture_arc(&self, id: TextureId) -> Option<Arc<wgpu::Texture>> {
140 self.textures
141 .get(id.0 as usize)
142 .map(|texture| Arc::clone(&texture.texture))
143 }
144
145 pub fn replace_texture(
146 &mut self,
147 id: TextureId,
148 texture: wgpu::Texture,
149 desc: TextureDesc,
150 ) -> Result<Arc<wgpu::Texture>> {
151 self.replace_texture_arc(id, Arc::new(texture), desc)
152 }
153
154 pub fn replace_texture_arc(
155 &mut self,
156 id: TextureId,
157 texture: Arc<wgpu::Texture>,
158 desc: TextureDesc,
159 ) -> Result<Arc<wgpu::Texture>> {
160 let runtime = self
161 .textures
162 .get_mut(id.0 as usize)
163 .ok_or_else(|| anyhow!("unknown texture id {id:?}"))?;
164 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
165 let old = std::mem::replace(&mut runtime.texture, texture);
166 runtime.view = view;
167 runtime.desc = desc;
168 Ok(old)
169 }
170
171 pub fn replace_texture_discard_old(
172 &mut self,
173 id: TextureId,
174 texture: wgpu::Texture,
175 desc: TextureDesc,
176 ) -> Result<()> {
177 let _ = self.replace_texture(id, texture, desc)?;
178 Ok(())
179 }
180
181 pub fn copy_texture_to_external(
182 &self,
183 source_id: TextureId,
184 destination: &wgpu::Texture,
185 ) -> Result<wgpu::SubmissionIndex> {
186 let source = self.runtime_texture(source_id)?;
187 let mut encoder = self
188 .device
189 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
190 label: Some("lumen-gpu external texture copy"),
191 });
192 encoder.copy_texture_to_texture(
193 wgpu::TexelCopyTextureInfo {
194 texture: &source.texture,
195 mip_level: 0,
196 origin: wgpu::Origin3d::ZERO,
197 aspect: wgpu::TextureAspect::All,
198 },
199 wgpu::TexelCopyTextureInfo {
200 texture: destination,
201 mip_level: 0,
202 origin: wgpu::Origin3d::ZERO,
203 aspect: wgpu::TextureAspect::All,
204 },
205 source.desc.domain.storage_size.as_extent(),
206 );
207 Ok(self.queue.submit([encoder.finish()]))
208 }
209
210 pub fn copy_texture_to_external_discard_submission(
211 &self,
212 source_id: TextureId,
213 destination: &wgpu::Texture,
214 ) -> Result<()> {
215 let _ = self.copy_texture_to_external(source_id, destination)?;
216 Ok(())
217 }
218
219 pub fn buffer(&self, id: BufferId) -> Option<&wgpu::Buffer> {
220 self.buffers.get(id.0 as usize).map(|buffer| &buffer.buffer)
221 }
222
223 pub fn execute(
224 &mut self,
225 plan: &RenderPlan,
226 update: &FrameUpdate<'_>,
227 ) -> Result<wgpu::SubmissionIndex> {
228 self.apply_frame_update(plan, update)?;
229 self.submit_plan(plan)
230 }
231
232 pub fn apply_frame_update(
233 &mut self,
234 plan: &RenderPlan,
235 update: &FrameUpdate<'_>,
236 ) -> Result<()> {
237 self.validate_prepared(plan)?;
238 tracing::trace!(
239 target: "lumen_gpu",
240 uploads = update.uploads().len(),
241 "apply frame update"
242 );
243 self.apply_uploads(update)
244 }
245
246 pub fn submit_plan(&mut self, plan: &RenderPlan) -> Result<wgpu::SubmissionIndex> {
247 self.validate_prepared(plan)?;
248 tracing::trace!(
249 target: "lumen_gpu",
250 passes = plan.passes.len(),
251 "submit render plan"
252 );
253
254 let mut encoder = self
255 .device
256 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
257 label: Some("lumen-gpu render-plan encoder"),
258 });
259
260 for pass in &plan.passes {
261 match &pass.desc {
262 PassDesc::Render(desc) => self.execute_render_pass(desc, &mut encoder)?,
263 PassDesc::Compute(desc) => self.execute_compute_pass(desc, &mut encoder)?,
264 PassDesc::CopyTexture(desc) => self.execute_copy_texture(desc, &mut encoder)?,
265 }
266 }
267
268 Ok(self.queue.submit([encoder.finish()]))
269 }
270
271 pub fn execute_discard_submission(
272 &mut self,
273 plan: &RenderPlan,
274 update: &FrameUpdate<'_>,
275 ) -> Result<()> {
276 let _ = self.execute(plan, update)?;
277 Ok(())
278 }
279
280 fn create_texture(&self, resource: &TextureResource) -> Result<RuntimeTexture> {
281 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
282 label: resource.label.as_deref(),
283 size: resource.desc.domain.storage_size.as_extent(),
284 mip_level_count: 1,
285 sample_count: 1,
286 dimension: wgpu::TextureDimension::D2,
287 format: resource.desc.format,
288 usage: resource.desc.usage,
289 view_formats: &[],
290 });
291 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
292 Ok(RuntimeTexture {
293 texture: Arc::new(texture),
294 view,
295 desc: resource.desc,
296 })
297 }
298
299 fn create_buffer(&self, resource: &BufferResource) -> RuntimeBuffer {
300 let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
301 label: resource.label.as_deref(),
302 size: resource.desc.size.max(1),
303 usage: resource.desc.usage,
304 mapped_at_creation: false,
305 });
306 RuntimeBuffer {
307 buffer,
308 desc: resource.desc,
309 }
310 }
311
312 fn create_program(&self, program: &Program) -> Result<RuntimeProgram> {
313 match &program.desc {
314 ProgramDesc::Render(desc) => {
315 self.create_render_program(desc).map(RuntimeProgram::Render)
316 }
317 ProgramDesc::Compute(desc) => self
318 .create_compute_program(desc)
319 .map(RuntimeProgram::Compute),
320 }
321 }
322
323 fn create_render_program(&self, desc: &RenderProgramDesc) -> Result<RuntimeRenderProgram> {
324 let shader = self
325 .device
326 .create_shader_module(wgpu::ShaderModuleDescriptor {
327 label: desc.label.as_deref(),
328 source: wgpu::ShaderSource::Wgsl(desc.shader.clone().into()),
329 });
330 let bind_group_layouts = create_bind_group_layouts(&self.device, &desc.bind_groups);
331 let layout_refs: Vec<_> = bind_group_layouts.iter().map(Some).collect();
332 let pipeline_layout = self
333 .device
334 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
335 label: desc.label.as_deref(),
336 bind_group_layouts: &layout_refs,
337 immediate_size: 0,
338 });
339 let pipeline = self
340 .device
341 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
342 label: desc.label.as_deref(),
343 layout: Some(&pipeline_layout),
344 vertex: wgpu::VertexState {
345 module: &shader,
346 entry_point: Some(&desc.vertex_entry),
347 compilation_options: Default::default(),
348 buffers: &desc.vertex_buffers,
349 },
350 primitive: desc.primitive,
351 depth_stencil: None,
352 multisample: wgpu::MultisampleState::default(),
353 fragment: Some(wgpu::FragmentState {
354 module: &shader,
355 entry_point: Some(&desc.fragment_entry),
356 compilation_options: Default::default(),
357 targets: &desc.targets,
358 }),
359 multiview_mask: None,
360 cache: None,
361 });
362 Ok(RuntimeRenderProgram {
363 pipeline,
364 bind_group_layouts,
365 })
366 }
367
368 fn create_compute_program(&self, desc: &ComputeProgramDesc) -> Result<RuntimeComputeProgram> {
369 let shader = self
370 .device
371 .create_shader_module(wgpu::ShaderModuleDescriptor {
372 label: desc.label.as_deref(),
373 source: wgpu::ShaderSource::Wgsl(desc.shader.clone().into()),
374 });
375 let bind_group_layouts = create_bind_group_layouts(&self.device, &desc.bind_groups);
376 let layout_refs: Vec<_> = bind_group_layouts.iter().map(Some).collect();
377 let pipeline_layout = self
378 .device
379 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
380 label: desc.label.as_deref(),
381 bind_group_layouts: &layout_refs,
382 immediate_size: 0,
383 });
384 let pipeline = self
385 .device
386 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
387 label: desc.label.as_deref(),
388 layout: Some(&pipeline_layout),
389 module: &shader,
390 entry_point: Some(&desc.entry),
391 compilation_options: Default::default(),
392 cache: None,
393 });
394 Ok(RuntimeComputeProgram {
395 pipeline,
396 bind_group_layouts,
397 })
398 }
399
400 fn validate_prepared(&self, plan: &RenderPlan) -> Result<()> {
401 if self.textures.len() != plan.textures.len()
402 || self.buffers.len() != plan.buffers.len()
403 || self.samplers.len() != plan.samplers.len()
404 || self.programs.len() != plan.programs.len()
405 {
406 bail!("render plan has not been prepared or has changed since prepare_plan");
407 }
408 Ok(())
409 }
410
411 fn apply_uploads(&self, update: &FrameUpdate<'_>) -> Result<()> {
412 for upload in update.uploads() {
413 match upload {
414 Upload::Buffer { id, offset, data } => {
415 tracing::trace!(
416 target: "lumen_gpu",
417 ?id,
418 offset,
419 bytes = data.len(),
420 "upload buffer"
421 );
422 let buffer = self.runtime_buffer(*id)?;
423 let end = offset.saturating_add(data.len() as u64);
424 if end > buffer.desc.size {
425 bail!("buffer upload for {id:?} exceeds declared buffer size");
426 }
427 self.queue.write_buffer(&buffer.buffer, *offset, data);
428 }
429 Upload::TextureRgba8 {
430 id,
431 data,
432 bytes_per_row,
433 rows_per_image,
434 } => {
435 tracing::trace!(
436 target: "lumen_gpu",
437 ?id,
438 bytes = data.len(),
439 bytes_per_row,
440 rows_per_image,
441 "upload rgba8 texture"
442 );
443 let texture = self.runtime_texture(*id)?;
444 self.queue.write_texture(
445 texture.texture.as_image_copy(),
446 data,
447 wgpu::TexelCopyBufferLayout {
448 offset: 0,
449 bytes_per_row: Some(*bytes_per_row),
450 rows_per_image: Some(*rows_per_image),
451 },
452 texture.desc.domain.storage_size.as_extent(),
453 );
454 }
455 Upload::TextureRgba8Region {
456 id,
457 data,
458 origin,
459 size,
460 bytes_per_row,
461 rows_per_image,
462 } => {
463 tracing::trace!(
464 target: "lumen_gpu",
465 ?id,
466 bytes = data.len(),
467 origin_x = origin[0],
468 origin_y = origin[1],
469 origin_z = origin[2],
470 width = size.width,
471 height = size.height,
472 "upload rgba8 texture region"
473 );
474 let texture = self.runtime_texture(*id)?;
475 self.queue.write_texture(
476 wgpu::TexelCopyTextureInfo {
477 texture: &texture.texture,
478 mip_level: 0,
479 origin: wgpu::Origin3d {
480 x: origin[0],
481 y: origin[1],
482 z: origin[2],
483 },
484 aspect: wgpu::TextureAspect::All,
485 },
486 data,
487 wgpu::TexelCopyBufferLayout {
488 offset: 0,
489 bytes_per_row: Some(*bytes_per_row),
490 rows_per_image: Some(*rows_per_image),
491 },
492 size.as_extent(),
493 );
494 }
495 Upload::TextureRgba16Float {
496 id,
497 data,
498 bytes_per_row,
499 rows_per_image,
500 } => {
501 tracing::trace!(
502 target: "lumen_gpu",
503 ?id,
504 bytes = data.len() * std::mem::size_of::<u16>(),
505 bytes_per_row,
506 rows_per_image,
507 "upload rgba16f texture"
508 );
509 let texture = self.runtime_texture(*id)?;
510 self.queue.write_texture(
511 texture.texture.as_image_copy(),
512 bytemuck::cast_slice(data),
513 wgpu::TexelCopyBufferLayout {
514 offset: 0,
515 bytes_per_row: Some(*bytes_per_row),
516 rows_per_image: Some(*rows_per_image),
517 },
518 texture.desc.domain.storage_size.as_extent(),
519 );
520 }
521 }
522 }
523 Ok(())
524 }
525
526 fn execute_render_pass(
527 &self,
528 desc: &RenderPassDesc,
529 encoder: &mut wgpu::CommandEncoder,
530 ) -> Result<()> {
531 tracing::trace!(
532 target: "lumen_gpu",
533 label = desc.label.as_deref().unwrap_or(""),
534 targets = desc.targets.len(),
535 bind_groups = desc.bindings.len(),
536 vertex_buffers = desc.vertex_buffers.len(),
537 "encode render pass"
538 );
539 let RuntimeProgram::Render(program) = self.runtime_program(desc.program)? else {
540 bail!("program {:?} is not a render program", desc.program);
541 };
542 let attachments = desc
543 .targets
544 .iter()
545 .map(|target| {
546 let texture = self.runtime_texture(target.texture)?;
547 Ok(Some(wgpu::RenderPassColorAttachment {
548 view: &texture.view,
549 depth_slice: None,
550 resolve_target: None,
551 ops: wgpu::Operations {
552 load: match target.load {
553 LoadOp::Load => wgpu::LoadOp::Load,
554 LoadOp::Clear(color) => wgpu::LoadOp::Clear(color),
555 },
556 store: target.store,
557 },
558 }))
559 })
560 .collect::<Result<Vec<_>>>()?;
561 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
562 label: desc.label.as_deref(),
563 color_attachments: &attachments,
564 depth_stencil_attachment: None,
565 occlusion_query_set: None,
566 timestamp_writes: None,
567 multiview_mask: None,
568 });
569 pass.set_pipeline(&program.pipeline);
570 let bind_groups =
571 self.create_pass_bind_groups(&program.bind_group_layouts, &desc.bindings)?;
572 for (group, bind_group) in bind_groups.iter().enumerate() {
573 pass.set_bind_group(group as u32, bind_group, &[]);
574 }
575 for (slot, buffer_id) in desc.vertex_buffers.iter().enumerate() {
576 pass.set_vertex_buffer(
577 slot as u32,
578 self.runtime_buffer(*buffer_id)?.buffer.slice(..),
579 );
580 }
581 if let Some((buffer_id, format)) = desc.index_buffer {
582 pass.set_index_buffer(self.runtime_buffer(buffer_id)?.buffer.slice(..), format);
583 }
584 if let Some(scissor) = desc.scissor {
585 pass.set_scissor_rect(scissor.x, scissor.y, scissor.width, scissor.height);
586 }
587 match &desc.draw {
588 DrawCommand::Draw(draw) => pass.draw(draw.vertices.clone(), draw.instances.clone()),
589 DrawCommand::DrawIndexed(draw) => pass.draw_indexed(
590 draw.indices.clone(),
591 draw.base_vertex,
592 draw.instances.clone(),
593 ),
594 }
595 Ok(())
596 }
597
598 fn execute_compute_pass(
599 &self,
600 desc: &ComputePassDesc,
601 encoder: &mut wgpu::CommandEncoder,
602 ) -> Result<()> {
603 match desc.dispatch {
604 ComputeDispatch::Direct(dispatch) => tracing::trace!(
605 target: "lumen_gpu",
606 label = desc.label.as_deref().unwrap_or(""),
607 x = dispatch.x,
608 y = dispatch.y,
609 z = dispatch.z,
610 "encode compute pass"
611 ),
612 ComputeDispatch::Indirect { buffer, offset } => tracing::trace!(
613 target: "lumen_gpu",
614 label = desc.label.as_deref().unwrap_or(""),
615 ?buffer,
616 offset,
617 "encode indirect compute pass"
618 ),
619 }
620 let RuntimeProgram::Compute(program) = self.runtime_program(desc.program)? else {
621 bail!("program {:?} is not a compute program", desc.program);
622 };
623 let bind_groups =
624 self.create_pass_bind_groups(&program.bind_group_layouts, &desc.bindings)?;
625 let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
626 label: desc.label.as_deref(),
627 timestamp_writes: None,
628 });
629 pass.set_pipeline(&program.pipeline);
630 for (group, bind_group) in bind_groups.iter().enumerate() {
631 pass.set_bind_group(group as u32, bind_group, &[]);
632 }
633 match desc.dispatch {
634 ComputeDispatch::Direct(dispatch) => {
635 pass.dispatch_workgroups(dispatch.x, dispatch.y, dispatch.z);
636 }
637 ComputeDispatch::Indirect { buffer, offset } => {
638 let buffer = &self.runtime_buffer(buffer)?.buffer;
639 pass.dispatch_workgroups_indirect(buffer, offset);
640 }
641 }
642 Ok(())
643 }
644
645 fn execute_copy_texture(
646 &self,
647 desc: &CopyTextureDesc,
648 encoder: &mut wgpu::CommandEncoder,
649 ) -> Result<()> {
650 tracing::trace!(
651 target: "lumen_gpu",
652 source = ?desc.source,
653 destination = ?desc.destination,
654 width = desc.size.width,
655 height = desc.size.height,
656 "encode texture copy"
657 );
658 let source = self.runtime_texture(desc.source)?;
659 let destination = self.runtime_texture(desc.destination)?;
660 encoder.copy_texture_to_texture(
661 source.texture.as_image_copy(),
662 wgpu::TexelCopyTextureInfo {
663 texture: &destination.texture,
664 mip_level: 0,
665 origin: desc.origin,
666 aspect: wgpu::TextureAspect::All,
667 },
668 desc.size.as_extent(),
669 );
670 Ok(())
671 }
672
673 fn create_pass_bind_groups(
674 &self,
675 layouts: &[wgpu::BindGroupLayout],
676 bindings: &[Binding],
677 ) -> Result<Vec<wgpu::BindGroup>> {
678 let mut grouped: HashMap<u32, Vec<&Binding>> = HashMap::new();
679 for binding in bindings {
680 grouped.entry(binding.group).or_default().push(binding);
681 }
682 let mut bind_groups = Vec::with_capacity(layouts.len());
683 for (group_index, layout) in layouts.iter().enumerate() {
684 let mut group_entries = grouped.remove(&(group_index as u32)).unwrap_or_default();
685 group_entries.sort_by_key(|entry| entry.binding);
686 let entries = group_entries
687 .iter()
688 .map(|binding| self.bind_group_entry(binding))
689 .collect::<Result<Vec<_>>>()?;
690 bind_groups.push(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
691 label: None,
692 layout,
693 entries: &entries,
694 }));
695 }
696 if !grouped.is_empty() {
697 bail!("pass contains bindings for groups not declared by its program");
698 }
699 Ok(bind_groups)
700 }
701
702 fn bind_group_entry(&self, binding: &Binding) -> Result<wgpu::BindGroupEntry<'_>> {
703 let resource = match binding.resource {
704 BindingResource::Texture { id, .. } => {
705 wgpu::BindingResource::TextureView(&self.runtime_texture(id)?.view)
706 }
707 BindingResource::Buffer { id, .. } => {
708 self.runtime_buffer(id)?.buffer.as_entire_binding()
709 }
710 BindingResource::Sampler(id) => {
711 wgpu::BindingResource::Sampler(&self.runtime_sampler(id)?.sampler)
712 }
713 };
714 Ok(wgpu::BindGroupEntry {
715 binding: binding.binding,
716 resource,
717 })
718 }
719
720 fn runtime_texture(&self, id: TextureId) -> Result<&RuntimeTexture> {
721 self.textures
722 .get(id.0 as usize)
723 .ok_or_else(|| anyhow!("unknown texture id {id:?}"))
724 }
725
726 fn runtime_buffer(&self, id: BufferId) -> Result<&RuntimeBuffer> {
727 self.buffers
728 .get(id.0 as usize)
729 .ok_or_else(|| anyhow!("unknown buffer id {id:?}"))
730 }
731
732 fn runtime_sampler(&self, id: SamplerId) -> Result<&RuntimeSampler> {
733 self.samplers
734 .get(id.0 as usize)
735 .ok_or_else(|| anyhow!("unknown sampler id {id:?}"))
736 }
737
738 fn runtime_program(&self, id: ProgramId) -> Result<&RuntimeProgram> {
739 self.programs
740 .get(id.0 as usize)
741 .ok_or_else(|| anyhow!("unknown program id {id:?}"))
742 }
743}
744
745fn env_flag(name: &str) -> bool {
746 env::var(name)
747 .map(|value| {
748 matches!(
749 value.to_ascii_lowercase().as_str(),
750 "1" | "true" | "yes" | "on"
751 )
752 })
753 .unwrap_or(false)
754}
755
756fn create_bind_group_layouts(
757 device: &wgpu::Device,
758 specs: &[BindGroupLayoutSpec],
759) -> Vec<wgpu::BindGroupLayout> {
760 specs
761 .iter()
762 .map(|spec| {
763 let entries: Vec<_> = spec
764 .entries
765 .iter()
766 .map(|entry| wgpu::BindGroupLayoutEntry {
767 binding: entry.binding,
768 visibility: entry.visibility,
769 ty: entry.ty,
770 count: None,
771 })
772 .collect();
773 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
774 label: spec.label.as_deref(),
775 entries: &entries,
776 })
777 })
778 .collect()
779}