use crate::gradient::{Gradient, RgbColor};
use wgpu::{
BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, BindingType, Device,
Extent3d, Queue, Texture, TextureFormat,
};
#[derive(Debug)]
pub(crate) struct LutTextureResources {
texture: Texture,
bind_group: BindGroup,
bind_group_layout: BindGroupLayout,
pending_gradient_update: Option<Gradient>,
}
impl LutTextureResources {
pub(crate) fn bind_group(&self) -> &BindGroup {
&self.bind_group
}
}
impl LutTextureResources {
const SIZE: u32 = 64;
pub(crate) fn new(device: &Device) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: Extent3d {
width: Self::SIZE,
..Default::default()
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D1,
format: TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: Some("lut_texture"),
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
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::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D1,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&view),
},
BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: Some("texture_bind_group"),
});
let lut = Gradient::new([(0., RgbColor::BLACK), (1., RgbColor::WHITE)]);
LutTextureResources {
texture,
bind_group,
bind_group_layout,
pending_gradient_update: Some(lut),
}
}
fn texel_copy_texture_info(&self) -> wgpu::TexelCopyTextureInfo {
wgpu::TexelCopyTextureInfo {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
}
}
fn texel_copy_buffer_layout(&self) -> wgpu::TexelCopyBufferLayout {
let format = self.texture.format();
assert_eq!(
format.block_dimensions(),
(1, 1),
"invalid texture dimension"
);
let bytes_per_row = Self::SIZE * format.block_copy_size(None).unwrap();
assert_eq!(bytes_per_row, Self::SIZE * 4, "invalid texture row size");
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: None,
}
}
pub(crate) fn prepare(&mut self, queue: &Queue) {
let Some(lut) = &self.pending_gradient_update.take() else {
return;
};
let gradient_sampled: Vec<u8> = lut
.linear_eval(LutTextureResources::SIZE as usize)
.into_iter()
.map(Into::<RgbColor<u8>>::into)
.flat_map(|c| [c.r, c.g, c.b, 255u8])
.collect();
queue.write_texture(
self.texel_copy_texture_info(),
&gradient_sampled,
self.texel_copy_buffer_layout(),
Extent3d {
width: LutTextureResources::SIZE,
..Default::default()
},
);
}
pub(crate) fn bind_group_layout(&self) -> &BindGroupLayout {
&self.bind_group_layout
}
pub(crate) fn update_lut(&mut self, gradient: Gradient) {
self.pending_gradient_update = Some(gradient)
}
}