use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
include!(concat!(env!("OUT_DIR"), "/textures_gen.rs"));
pub struct GpuTexture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub width: u32,
pub height: u32,
}
pub struct TextureRegistry {
textures: HashMap<String, Arc<GpuTexture>>,
sampler: wgpu::Sampler,
layouts: RefCell<HashMap<usize, Arc<wgpu::BindGroupLayout>>>,
}
impl TextureRegistry {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("brush-graph-texture-sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
..Default::default()
});
let mut reg = Self {
textures: HashMap::new(),
sampler,
layouts: RefCell::new(HashMap::new()),
};
register_builtin_textures(&mut reg, device, queue);
reg
}
pub fn register_rgba8(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
name: &str,
width: u32,
height: u32,
rgba: &[u8],
) {
debug_assert_eq!(
rgba.len(),
(width * height * 4) as usize,
"rgba length must equal width * height * 4"
);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(&format!("brush-graph-texture/{name}")),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
self.textures.insert(
name.to_string(),
Arc::new(GpuTexture {
texture,
view,
width,
height,
}),
);
}
pub fn register_image(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
name: &str,
bytes: &[u8],
) {
match image::load_from_memory(bytes) {
Ok(img) => {
let rgba = img.to_rgba8();
let (w, h) = rgba.dimensions();
self.register_rgba8(device, queue, name, w, h, &rgba);
}
Err(e) => {
log::warn!("TextureRegistry: failed to decode `{name}`: {e}");
}
}
}
pub fn contains(&self, name: &str) -> bool {
self.textures.contains_key(name)
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.textures.keys().map(String::as_str)
}
pub fn sampler(&self) -> &wgpu::Sampler {
&self.sampler
}
pub fn layout_for_count(&self, device: &wgpu::Device, n: usize) -> Arc<wgpu::BindGroupLayout> {
let mut layouts = self.layouts.borrow_mut();
layouts
.entry(n)
.or_insert_with(|| {
let mut entries: Vec<wgpu::BindGroupLayoutEntry> = Vec::with_capacity(n + 1);
entries.push(wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
});
for i in 0..n {
entries.push(wgpu::BindGroupLayoutEntry {
binding: 1 + i as u32,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
});
}
Arc::new(
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(&format!("brush-graph-textures-bgl-{n}")),
entries: &entries,
}),
)
})
.clone()
}
pub fn make_bind_group(
&self,
device: &wgpu::Device,
names: &[String],
) -> (Arc<wgpu::BindGroupLayout>, wgpu::BindGroup) {
let fallback = self
.textures
.get(FALLBACK_TEXTURE)
.expect("TextureRegistry must register _fallback at init");
let textures: Vec<&Arc<GpuTexture>> = names
.iter()
.map(|n| {
self.textures.get(n).unwrap_or_else(|| {
log::warn!("TextureRegistry: no texture `{n}`, substituting `_fallback`",);
fallback
})
})
.collect();
let layout = self.layout_for_count(device, names.len());
let mut entries: Vec<wgpu::BindGroupEntry> = Vec::with_capacity(names.len() + 1);
entries.push(wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&self.sampler),
});
for (i, t) in textures.iter().enumerate() {
entries.push(wgpu::BindGroupEntry {
binding: 1 + i as u32,
resource: wgpu::BindingResource::TextureView(&t.view),
});
}
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-graph-textures-bg"),
layout: &layout,
entries: &entries,
});
(layout, bg)
}
}
pub const FALLBACK_TEXTURE: &str = "_fallback";
fn register_builtin_textures(
reg: &mut TextureRegistry,
device: &wgpu::Device,
queue: &wgpu::Queue,
) {
reg.register_rgba8(
device,
queue,
FALLBACK_TEXTURE,
1,
1,
&[255u8, 255, 255, 255],
);
for (name, bytes) in BUILTIN_TEXTURES {
reg.register_image(device, queue, name, bytes);
}
}