Skip to main content

mcraw_tui/
gpu.rs

1use anyhow::{anyhow, Result};
2use std::sync::Arc;
3
4pub struct GpuContext { pub device: wgpu::Device, pub queue: wgpu::Queue }
5impl GpuContext {
6    pub async fn new() -> Result<Self> {
7        let instance = wgpu::Instance::default();
8        let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await.ok_or_else(|| anyhow!("Failed to find a suitable GPU adapter"))?;
9        let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor { label: Some("mcraw-tui GPU"), required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::default(), memory_hints: wgpu::MemoryHints::Performance }, None).await.map_err(|e| anyhow!("Failed to create GPU device: {}", e))?;
10        Ok(Self { device, queue })
11    }
12}
13
14pub struct RcdPipeline {
15    context: Arc<GpuContext>, width: u32, height: u32, aligned_stride: u32, upload_data: Vec<u8>,
16    cfa_texture: wgpu::Texture, vh_texture: wgpu::Texture,
17    pq_texture: wgpu::Texture, lp_texture: wgpu::Texture, out_buffer: wgpu::Buffer, readback_buffer: wgpu::Buffer,
18    conv_pipeline: wgpu::ComputePipeline, conv_bind_group: wgpu::BindGroup, fill_pipeline: wgpu::ComputePipeline,
19    fill_bind_group: wgpu::BindGroup, uniform_buffer: wgpu::Buffer, sampler: wgpu::Sampler,
20    conv_bgl: wgpu::BindGroupLayout, fill_bgl: wgpu::BindGroupLayout,
21}
22
23#[repr(C)]
24#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
25struct GpuUniforms {
26    width: u32, height: u32, filters: u32, gamma_mode: u32,
27    black_level: f32, white_level: f32, wb_r: f32, wb_b: f32,
28    black_r: f32, black_g: f32, black_b: f32, _black_pad: f32,
29    ccm_row0: [f32; 4], ccm_row1: [f32; 4], ccm_row2: [f32; 4],
30    phase_x: i32, phase_y: i32, _pad: [u32; 2],
31}
32
33fn transfer_to_gamma_mode(tf: &crate::color::TransferFunction) -> u32 {
34    use crate::color::TransferFunction;
35    match tf {
36        TransferFunction::Linear => 0, TransferFunction::Rec709 => 1,
37        TransferFunction::SLog3 => 2, TransferFunction::VLog => 3, TransferFunction::ARRIlog3 => 4,
38        TransferFunction::ARRIlog4 => 13, TransferFunction::CLog3 => 5, TransferFunction::FLog2 => 6,
39        TransferFunction::ACESCCT => 7, TransferFunction::PQ => 8, TransferFunction::HLG => 9,
40        TransferFunction::DaVinciIntermediate => 10,
41        TransferFunction::AppleLog | TransferFunction::AppleLog2 => 11,
42        TransferFunction::Gamma24 => 12,
43    }
44}
45
46impl RcdPipeline {
47    pub fn new(context: Arc<GpuContext>, width: u32, height: u32) -> Result<Self> {
48        let device = &context.device;
49        let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest, ..Default::default() });
50        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("RCD Uniforms"), size: std::mem::size_of::<GpuUniforms>() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false });
51        let conv_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("RCD Conv"), source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/rcd_conv.wgsl").into()) });
52        let fill_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("RCD Fill"), source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/rcd_fill.wgsl").into()) });
53        
54        let conv_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("RCD Conv BGL"), entries: &[
55            wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Uint, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false }, count: None },
56            wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None },
57            wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None },
58            wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::R32Float, view_dimension: wgpu::TextureViewDimension::D2 }, count: None },
59            wgpu::BindGroupLayoutEntry { binding: 4, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::R32Float, view_dimension: wgpu::TextureViewDimension::D2 }, count: None },
60            wgpu::BindGroupLayoutEntry { binding: 5, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::StorageTexture { access: wgpu::StorageTextureAccess::WriteOnly, format: wgpu::TextureFormat::R32Float, view_dimension: wgpu::TextureViewDimension::D2 }, count: None },
61        ]});
62        let conv_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("RCD Conv Layout"), bind_group_layouts: &[&conv_bgl], push_constant_ranges: &[] });
63        let conv_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("RCD Conv"), layout: Some(&conv_pipeline_layout), module: &conv_shader, entry_point: Some("main"), compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None });
64        
65        let fill_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("RCD Fill BGL"), entries: &[
66            wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Uint, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false }, count: None },
67            wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false }, count: None },
68            wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false }, count: None },
69            wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false }, count: None },
70            wgpu::BindGroupLayoutEntry { binding: 4, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None }, count: None },
71            wgpu::BindGroupLayoutEntry { binding: 5, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None },
72        ]});
73        let fill_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("RCD Fill Layout"), bind_group_layouts: &[&fill_bgl], push_constant_ranges: &[] });
74        let fill_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: Some("RCD Fill"), layout: Some(&fill_pipeline_layout), module: &fill_shader, entry_point: Some("main"), compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None });
75        
76        let out_size = 8u64; // Dummy size
77        let out_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("RCD Out Buffer"), size: out_size, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false });
78        let readback_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("RCD Readback Buffer"), size: out_size, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false });
79        
80        let dummy_u16 = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R16Uint, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[] });
81        let dummy_f32 = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Float, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[] });
82        let dummy_storage = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Float, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[] });
83        
84        let conv_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &conv_bgl, entries: &[
85            wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&dummy_u16.create_view(&Default::default())) },
86            wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
87            wgpu::BindGroupEntry { binding: 2, resource: uniform_buffer.as_entire_binding() },
88            wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&dummy_storage.create_view(&Default::default())) },
89            wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&dummy_storage.create_view(&Default::default())) },
90            wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&dummy_storage.create_view(&Default::default())) },
91        ], label: None });
92        
93        let fill_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &fill_bgl, entries: &[
94            wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&dummy_u16.create_view(&Default::default())) },
95            wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&dummy_f32.create_view(&Default::default())) },
96            wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&dummy_f32.create_view(&Default::default())) },
97            wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&dummy_f32.create_view(&Default::default())) },
98            wgpu::BindGroupEntry { binding: 4, resource: out_buffer.as_entire_binding() },
99            wgpu::BindGroupEntry { binding: 5, resource: uniform_buffer.as_entire_binding() },
100        ], label: None });
101        
102        let mut pipeline = Self {
103            context: context.clone(), width: 0, height: 0, aligned_stride: 0, upload_data: Vec::new(), cfa_texture: dummy_u16, vh_texture: dummy_f32, pq_texture: dummy_storage,
104            lp_texture: device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Float, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING, view_formats: &[] }),
105            out_buffer, readback_buffer, conv_pipeline, conv_bind_group, fill_pipeline, fill_bind_group, conv_bgl, fill_bgl, uniform_buffer, sampler,
106        };
107        pipeline.resize(width, height)?;
108        Ok(pipeline)
109    }
110
111    pub fn process(&mut self, bayer: &[u16], filters: u32, black_level: f32, white_level: f32, stride_width: u32, offset_x: u32, offset_y: u32, fused_ccm: &[f32; 9], as_shot_neutral: &[f32; 3], tf: &crate::color::TransferFunction) -> Result<Vec<u8>> {
112        let device = &self.context.device; let queue = &self.context.queue;
113        let mut ccm_row0 = [0.0f32; 4]; let mut ccm_row1 = [0.0f32; 4]; let mut ccm_row2 = [0.0f32; 4];
114        ccm_row0[..3].copy_from_slice(&fused_ccm[0..3]); ccm_row1[..3].copy_from_slice(&fused_ccm[3..6]); ccm_row2[..3].copy_from_slice(&fused_ccm[6..9]);
115        let raw_wb_r = if as_shot_neutral[0] > 1e-6 { as_shot_neutral[1] / as_shot_neutral[0] } else { 1.0 };
116        let raw_wb_b = if as_shot_neutral[2] > 1e-6 { as_shot_neutral[1] / as_shot_neutral[2] } else { 1.0 };
117        let wb_r = raw_wb_r.clamp(0.1, 10.0);
118        let wb_b = raw_wb_b.clamp(0.1, 10.0);
119        if (wb_r - raw_wb_r).abs() > 1e-3 || (wb_b - raw_wb_b).abs() > 1e-3 {
120            tracing::warn!(
121                "WB gains clamped: as_shot_neutral={:?} raw=[{:.3},{:.3}] clamped=[{:.3},{:.3}]",
122                as_shot_neutral, raw_wb_r, raw_wb_b, wb_r, wb_b
123            );
124        }
125        // Per-channel black level. When the decoder supplies only one
126        // value, all four channels use that single black level.
127        let bl = black_level;
128        let uniforms = GpuUniforms {
129            width: self.width, height: self.height, filters, gamma_mode: transfer_to_gamma_mode(tf),
130            black_level: bl, white_level,
131            wb_r, wb_b,
132            black_r: bl, black_g: bl, black_b: bl, _black_pad: 0.0,
133            ccm_row0, ccm_row1, ccm_row2,
134            phase_x: (offset_x & 1) as i32, phase_y: (offset_y & 1) as i32, _pad: [0u32; 2],
135        };
136        queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
137        
138        let bayer_bytes = bytemuck::cast_slice(bayer); let row_bytes = self.width as usize * 2;
139        self.upload_data.fill(0);
140        for row in 0..self.height as usize {
141            let src_off = ((offset_y as usize + row) * stride_width as usize + offset_x as usize) * 2;
142            let dst_off = row * self.aligned_stride as usize;
143            self.upload_data[dst_off..dst_off + row_bytes].copy_from_slice(&bayer_bytes[src_off..src_off + row_bytes]);
144        }
145        queue.write_texture(wgpu::TexelCopyTextureInfo { texture: &self.cfa_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &self.upload_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(self.aligned_stride), rows_per_image: Some(self.height) }, wgpu::Extent3d { width: self.width, height: self.height, depth_or_array_layers: 1 });
146        
147        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("RCD Encoder") });
148        let wg_conv_x = (self.width + 15) / 16; let wg_conv_y = (self.height + 15) / 16;
149        let valid_x = 128u32.saturating_sub(18); let valid_y = 32u32.saturating_sub(18);
150        let wg_fill_x = (self.width + valid_x - 1) / valid_x; let wg_fill_y = (self.height + valid_y - 1) / valid_y;
151        
152        { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("RCD Conv"), timestamp_writes: None }); cpass.set_pipeline(&self.conv_pipeline); cpass.set_bind_group(0, &self.conv_bind_group, &[]); cpass.dispatch_workgroups(wg_conv_x, wg_conv_y, 1); }
153        { let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("RCD Fill"), timestamp_writes: None }); cpass.set_pipeline(&self.fill_pipeline); cpass.set_bind_group(0, &self.fill_bind_group, &[]); cpass.dispatch_workgroups(wg_fill_x.max(1), wg_fill_y.max(1), 1); }
154
155        // FIXED: 16-bit output size (8 bytes per pixel)
156        let out_size = (self.width as u64) * (self.height as u64) * 8;
157        encoder.copy_buffer_to_buffer(&self.out_buffer, 0, &self.readback_buffer, 0, out_size);
158        queue.submit(Some(encoder.finish()));
159
160        let buffer_slice = self.readback_buffer.slice(..);
161        let (tx, rx) = std::sync::mpsc::channel();
162        buffer_slice.map_async(wgpu::MapMode::Read, move |result| { let _ = tx.send(result); });
163        device.poll(wgpu::Maintain::Wait);
164        rx.recv().map_err(|_| anyhow!("Readback recv failed"))?.map_err(|e| anyhow!("Buffer map failed: {:?}", e))?;
165        let data = buffer_slice.get_mapped_range();
166        let u32_data: &[u32] = bytemuck::cast_slice(&data);
167        let pixel_count = (self.width * self.height) as usize;
168
169        // A6: pack to RGB48LE bytes in a tight loop while the mapped range
170        // is live. Per the WGSL: first u32 = (R | G<<16), second u32 = (B | pad<<16).
171        let mut frame_bytes = vec![0u8; pixel_count * 6];
172        for pi in 0..pixel_count {
173            let p0 = u32_data[pi * 2];
174            let p1 = u32_data[pi * 2 + 1];
175            let r = (p0 & 0xFFFF) as u16;
176            let g = ((p0 >> 16) & 0xFFFF) as u16;
177            let b = (p1 & 0xFFFF) as u16;
178            let o = pi * 6;
179            frame_bytes[o]     = r as u8;
180            frame_bytes[o + 1] = (r >> 8) as u8;
181            frame_bytes[o + 2] = g as u8;
182            frame_bytes[o + 3] = (g >> 8) as u8;
183            frame_bytes[o + 4] = b as u8;
184            frame_bytes[o + 5] = (b >> 8) as u8;
185        }
186        drop(data); self.readback_buffer.unmap();
187        Ok(frame_bytes)
188    }
189
190    pub fn resize(&mut self, width: u32, height: u32) -> Result<()> {
191        let device = &self.context.device;
192        let tex_desc = |format: wgpu::TextureFormat, usage: wgpu::TextureUsages| wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage, view_formats: &[] };
193        self.cfa_texture = device.create_texture(&tex_desc(wgpu::TextureFormat::R16Uint, wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST));
194        self.vh_texture = device.create_texture(&tex_desc(wgpu::TextureFormat::R32Float, wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING));
195        // Both P/Q and LPF pyramid are written at half-res in both X
196        // and Y by the conv shader (LuisSR steps 1 and 4). Allocating
197        // half-height too avoids wasting the upper half of the texture.
198        let half_w = (width + 1) / 2;
199        let half_h = (height + 1) / 2;
200        let half_desc = |format: wgpu::TextureFormat, usage: wgpu::TextureUsages| wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: half_w, height: half_h, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, usage, view_formats: &[] };
201        self.pq_texture = device.create_texture(&half_desc(wgpu::TextureFormat::R32Float, wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING));
202        self.lp_texture = device.create_texture(&half_desc(wgpu::TextureFormat::R32Float, wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING));
203        
204        // FIXED: 16-bit output size (8 bytes per pixel)
205        let out_size = (width as u64) * (height as u64) * 8;
206        self.out_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("RCD Out Buffer"), size: out_size, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false });
207        self.readback_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("RCD Readback Buffer"), size: out_size, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false });
208        // A3: Hoist upload_data Vec<u8> + aligned_stride to fields, allocated once per resize.
209        const ALIGN: u32 = 256;
210        let src_stride = width * 2;
211        self.aligned_stride = ((src_stride + ALIGN - 1) / ALIGN) * ALIGN;
212        self.upload_data = vec![0u8; self.aligned_stride as usize * height as usize];
213        self.width = width; self.height = height;
214        
215        self.conv_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.conv_pipeline.get_bind_group_layout(0), entries: &[
216            wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.cfa_texture.create_view(&Default::default())) },
217            wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.sampler) },
218            wgpu::BindGroupEntry { binding: 2, resource: self.uniform_buffer.as_entire_binding() },
219            wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.vh_texture.create_view(&Default::default())) },
220            wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.pq_texture.create_view(&Default::default())) },
221            wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.lp_texture.create_view(&Default::default())) },
222        ], label: None });
223        
224        self.fill_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.fill_pipeline.get_bind_group_layout(0), entries: &[
225            wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.cfa_texture.create_view(&Default::default())) },
226            wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&self.vh_texture.create_view(&Default::default())) },
227            wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.pq_texture.create_view(&Default::default())) },
228            wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.lp_texture.create_view(&Default::default())) },
229            wgpu::BindGroupEntry { binding: 4, resource: self.out_buffer.as_entire_binding() },
230            wgpu::BindGroupEntry { binding: 5, resource: self.uniform_buffer.as_entire_binding() },
231        ], label: None });
232        Ok(())
233    }
234}