diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-04-22 13:35:18 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 14:46:13 +0000 |
| commit | 9fd2105289ccc5a7c12af56bad4d33ef768d0f16 (patch) | |
| tree | a4e6ff2c64fd0f1d4c73d74b3d62327630ff6206 /src | |
| parent | add tsv-video-buffer (diff) | |
| download | wgsl-view-9fd2105289ccc5a7c12af56bad4d33ef768d0f16.tar.gz wgsl-view-9fd2105289ccc5a7c12af56bad4d33ef768d0f16.zip | |
samplers/textures as resources
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/wgsl_render.rs | 122 | ||||
| -rw-r--r-- | src/gpu.rs | 34 | ||||
| -rw-r--r-- | src/osc.rs | 47 | ||||
| -rw-r--r-- | src/renderer.rs | 74 | ||||
| -rw-r--r-- | src/uniform.rs | 405 |
5 files changed, 500 insertions, 182 deletions
diff --git a/src/bin/wgsl_render.rs b/src/bin/wgsl_render.rs index 29b5d5d..da28816 100644 --- a/src/bin/wgsl_render.rs +++ b/src/bin/wgsl_render.rs @@ -61,52 +61,35 @@ fn main() { loop { let frame_start = Instant::now(); - let handle_msg = |msg: rosc::OscMessage| { - let path = &msg.addr; - - if path == "/shader" { - match &msg.args[..] { - [OscType::String(code)] => { - if let Err(e) = renderer.load_shader(&device, code) { - log::error!("shader error: {e}"); - } - } - _ => log::warn!("/shader: expected single string argument"), + let handle_msg = |msg: rosc::OscMessage| -> Result<(), String> { + let path: Vec<_> = msg.addr.strip_prefix("/").unwrap().split('/').collect(); + + match (&path[..], &msg.args[..]) { + (["shader"], [OscType::String(code)]) => renderer.load_shader(&device, code), + (["uniform", rest @ ..], args) => { + set_uniform(renderer.uniforms(), rest, args).map_err(|e| e.to_string()) + } + (["texture", id], [OscType::String(tsv_name)]) => { + renderer.create_texture_resource(&device, id, tsv_name); + Ok(()) } - } else if let Some(rest) = path.strip_prefix("/uniform/") { - if let Err(e) = set_uniform(renderer.uniforms(), rest, &msg.args) { - log::warn!("uniform error: {e}"); + (["sampler", id], [OscType::String(filter), OscType::String(clamp)]) => { + let (filter_mode, address_mode) = parse_sampler_modes(filter, clamp)?; + renderer.create_sampler_resource(&device, id, filter_mode, address_mode); + Ok(()) } - } else if let Some(tex_name) = path.strip_prefix("/texture/") { - match &msg.args[..] { - [OscType::String(tsv_name)] => { - match renderer.uniforms().texture_slot_mut(tex_name) { - Some(slot) => { - slot.set_tsv_name(tsv_name.clone()); - log::info!("texture '{tex_name}' → tsv '{tsv_name}'"); - } - None => log::warn!("texture '{tex_name}' not found"), - } - } - _ => log::warn!("/texture/{tex_name}: expected string (TSV image name)"), + (["texture", id, "destroy"], []) => { + renderer.destroy_texture_resource(&device, id); + Ok(()) } - } else if let Some(smp_name) = path.strip_prefix("/sampler/") { - match &msg.args[..] { - [OscType::String(filter), OscType::String(clamp)] => { - if let Err(e) = configure_sampler( - renderer.uniforms(), - &device, - smp_name, - filter, - clamp, - ) { - log::warn!("sampler error: {e}"); - } - } - _ => log::warn!("/sampler/{smp_name}: expected two strings (filter, clamp)"), + (["sampler", id, "destroy"], []) => { + renderer.destroy_sampler_resource(&device, id); + Ok(()) } - } else { - log::debug!("unhandled OSC: {path}"); + (["binding", name], [OscType::String(target)]) => { + bind_resource(&mut renderer, &device, name, target) + } + _ => Err(format!("unhandled OSC message {} {:?}", msg.addr, msg.args)), } }; @@ -118,7 +101,7 @@ fn main() { }; if dirty { - gpu::refresh_textures(renderer.uniforms(), &device, &mut client, fence); + renderer.refresh_textures(&device, &mut client, fence); renderer.render(&device, &queue); device.poll(wgpu::PollType::wait_indefinitely()).unwrap(); @@ -146,13 +129,13 @@ fn main() { /// Set a uniform value from OSC args, navigating the path. fn set_uniform( cache: &mut wgsl_view::uniform::UniformCache, - path: &str, + path: &[&str], args: &[OscType], -) -> Result<(), Box<dyn std::error::Error>> { - let mut parts = path.split('/'); - let name = parts.next().ok_or("missing uniform name")?; +) -> Result<(), UniformError> { + let mut parts = path.iter(); + let name = parts.next().ok_or(UniformError::NotFound)?; - let mut uref = cache.get(name).ok_or("uniform not found")?; + let mut uref = cache.get(name).ok_or(UniformError::NotFound)?; for component in parts { uref = uref.field(component)?; } @@ -198,38 +181,43 @@ fn set_uniform( .collect::<Result<_, _>>()?; uref.set_u32(&values)?; } - _ => return Err(Box::new(UniformError::TypeMismatch)), + _ => return Err(UniformError::TypeMismatch), } Ok(()) } -/// Configure a named sampler from string filter/clamp mode names. -fn configure_sampler( - cache: &mut wgsl_view::uniform::UniformCache, - device: &wgpu::Device, - name: &str, +fn parse_sampler_modes( filter: &str, - clamp: &str, -) -> Result<(), String> { + address: &str, +) -> Result<(wgpu::FilterMode, wgpu::AddressMode), String> { let filter_mode = match filter { "linear" => wgpu::FilterMode::Linear, "nearest" => wgpu::FilterMode::Nearest, - _ => return Err(format!("unknown filter mode '{filter}' (linear|nearest)")), + _ => return Err(format!("unknown filter mode '{filter}'")), }; - let address_mode = match clamp { + let address_mode = match address { "clamp" => wgpu::AddressMode::ClampToEdge, "repeat" => wgpu::AddressMode::Repeat, "mirror" => wgpu::AddressMode::MirrorRepeat, - _ => return Err(format!("unknown clamp mode '{clamp}' (clamp|repeat|mirror)")), + _ => return Err(format!("unknown address mode mode '{address}'")), }; - let slot = cache - .sampler_slot_mut(name) - .ok_or_else(|| format!("sampler '{name}' not found"))?; - slot.configure(device, filter_mode, address_mode); - cache.rebuild_bind_group(device); - log::info!("sampler '{name}' → {filter}, {clamp}"); - Ok(()) + Ok((filter_mode, address_mode)) } - +fn bind_resource( + renderer: &mut Renderer, + device: &wgpu::Device, + name: &str, + target: &str, +) -> Result<(), String> { + if let Some(id) = target.strip_prefix("/texture/") { + renderer.bind_texture_resource(device, name, id)?; + Ok(()) + } else if let Some(id) = target.strip_prefix("/sampler/") { + renderer.bind_sampler_resource(device, name, id)?; + Ok(()) + } else { + Err(format!("invalid binding target '{target}'")) + } +} @@ -178,20 +178,20 @@ pub fn destroy_fence(client: &VkClient, fence: vk::Fence) { /// Refresh all texture inputs from TSV shared images. /// Handles format detection and texture recreation internally. pub fn refresh_textures( - cache: &mut crate::uniform::UniformCache, + pools: &mut crate::uniform::ResourcePools, device: &wgpu::Device, client: &mut VkClient, fence: vk::Fence, -) { +) -> bool { let mut needs_rebind = false; - for slot in cache.texture_slots_mut() { - let tsv_name = match slot.tsv_name() { + for texture in pools.textures_mut() { + let tsv_name = match texture.tsv_name() { Some(n) => n.to_string(), None => continue, }; - if !slot.tsv_registered() { + if !texture.tsv_registered() { if let Err(e) = client.find_image(&tsv_name, true) { log::debug!("tsv find '{tsv_name}': {e}"); continue; @@ -200,11 +200,14 @@ pub fn refresh_textures( match client.find_image_data(&tsv_name, true) { Ok(Some((_lock, data))) => { let format = img_format_to_wgpu(data.format); - if slot.resize( + let view_dimension = + img_type_to_view_dimension(data.image_type, data.depth_or_array_layers); + if texture.resize( device, data.width, data.height, data.depth_or_array_layers, + view_dimension, format, ) { needs_rebind = true; @@ -213,10 +216,10 @@ pub fn refresh_textures( _ => continue, } - slot.set_tsv_registered(); + texture.set_tsv_registered(); } - let raw = unsafe { raw_image(slot.texture()) }; + let raw = unsafe { raw_image(texture.texture()) }; match client.recv_image( &tsv_name, raw, @@ -224,13 +227,22 @@ pub fn refresh_textures( vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, fence, ) { - Ok(Some(())) => log::trace!("texture '{}' updated from '{tsv_name}'", slot.name()), + Ok(Some(())) => log::trace!("texture '{}' updated from '{tsv_name}'", texture.id()), Ok(None) => {} Err(e) => log::warn!("recv_image '{tsv_name}': {e}"), } } - if needs_rebind { - cache.rebuild_bind_group(device); + needs_rebind +} + +fn img_type_to_view_dimension( + image_type: ImgType, + depth_or_array_layers: u32, +) -> wgpu::TextureViewDimension { + match image_type { + ImgType::D3 => wgpu::TextureViewDimension::D3, + ImgType::D2 if depth_or_array_layers > 1 => wgpu::TextureViewDimension::D2Array, + _ => wgpu::TextureViewDimension::D2, } } @@ -1,4 +1,4 @@ -use std::net::UdpSocket; +use std::{fmt::Display, net::UdpSocket}; use rosc::{OscMessage, OscPacket}; @@ -20,16 +20,24 @@ impl OscServer { /// Drains all pending OSC messages, calling `on_message` for each. /// Returns `true` if any messages were dispatched. - pub fn poll(&mut self, mut on_message: impl FnMut(OscMessage)) -> bool { + pub fn poll<E: Display>( + &mut self, + mut on_message: impl FnMut(OscMessage) -> Result<(), E>, + ) -> bool { let mut received = false; loop { match self.socket.recv_from(&mut self.buf) { Ok((size, _addr)) => { let data = &self.buf[..size]; - match rosc::decoder::decode_udp(data) { - Ok((_, packet)) => dispatch(packet, &mut on_message), - Err(e) => log::warn!("OSC decode error: {}", e), - } + let res = rosc::decoder::decode_udp(data) + .map_err(|e| format!("OSC edecode error: {}", e)) + .and_then(|(_, packet)| { + dispatch(packet, &mut on_message).map_err(|e| e.to_string()) + }); + if let Err(e) = res { + log::warn!("{}", e); + }; + received = true; } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break, @@ -43,15 +51,22 @@ impl OscServer { } /// Blocks until at least one OSC message arrives, then drains all pending. - pub fn recv(&mut self, mut on_message: impl FnMut(OscMessage)) -> bool { + pub fn recv<E: Display>( + &mut self, + mut on_message: impl FnMut(OscMessage) -> Result<(), E>, + ) -> bool { self.socket.set_nonblocking(false).expect("set blocking"); match self.socket.recv_from(&mut self.buf) { Ok((size, _addr)) => { let data = &self.buf[..size]; - match rosc::decoder::decode_udp(data) { - Ok((_, packet)) => dispatch(packet, &mut on_message), - Err(e) => log::warn!("OSC decode error: {}", e), - } + let res = rosc::decoder::decode_udp(data) + .map_err(|e| format!("OSC edecode error: {}", e)) + .and_then(|(_, packet)| { + dispatch(packet, &mut on_message).map_err(|e| e.to_string()) + }); + if let Err(e) = res { + log::warn!("{}", e); + }; } Err(e) => { log::warn!("OSC recv error: {}", e); @@ -63,13 +78,17 @@ impl OscServer { } } -fn dispatch(packet: OscPacket, on_message: &mut impl FnMut(OscMessage)) { +fn dispatch<E>( + packet: OscPacket, + on_message: &mut impl FnMut(OscMessage) -> Result<(), E>, +) -> Result<(), E> { match packet { - OscPacket::Message(msg) => on_message(msg), + OscPacket::Message(msg) => on_message(msg)?, OscPacket::Bundle(bundle) => { for p in bundle.content { - dispatch(p, on_message); + dispatch(p, on_message)?; } } } + Ok(()) } diff --git a/src/renderer.rs b/src/renderer.rs index 454e81a..3717945 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,4 +1,5 @@ -use crate::uniform::UniformCache; +use crate::gpu; +use crate::uniform::{ResourcePools, UniformCache}; const CANVAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; @@ -37,6 +38,7 @@ pub struct Renderer { vertex_bgl: wgpu::BindGroupLayout, vertex_bg: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, + resources: ResourcePools, uniforms: UniformCache, } @@ -101,12 +103,14 @@ impl Renderer { bytemuck::cast_slice(&[width as f32, height as f32]), ); + let resources = ResourcePools::new(); let mut uniforms = UniformCache::new(); let render_pipeline = build_pipeline( device, &vertex_module, &vertex_bgl, DEFAULT_FRAGMENT, + &resources, &mut uniforms, ) .expect("default shader"); @@ -118,6 +122,7 @@ impl Renderer { vertex_bgl, vertex_bg, render_pipeline, + resources, uniforms, } } @@ -140,6 +145,7 @@ impl Renderer { &self.vertex_module, &self.vertex_bgl, source, + &self.resources, &mut self.uniforms, )?; self.render_pipeline = pipeline; @@ -147,6 +153,70 @@ impl Renderer { Ok(()) } + pub fn create_texture_resource(&mut self, device: &wgpu::Device, id: &str, tsv_name: &str) { + self.resources.create_texture(device, id, tsv_name); + self.uniforms.rebuild_bind_group(device, &self.resources); + } + + pub fn destroy_texture_resource(&mut self, device: &wgpu::Device, id: &str) { + let removed = self.resources.destroy_texture(id); + let unbound = self.uniforms.unbind_texture_resource(id); + if removed || unbound { + self.uniforms.rebuild_bind_group(device, &self.resources); + } + } + + pub fn create_sampler_resource( + &mut self, + device: &wgpu::Device, + id: &str, + filter: wgpu::FilterMode, + address_mode: wgpu::AddressMode, + ) { + self.resources + .create_sampler(device, id, filter, address_mode); + self.uniforms.rebuild_bind_group(device, &self.resources); + } + + pub fn destroy_sampler_resource(&mut self, device: &wgpu::Device, id: &str) { + let removed = self.resources.destroy_sampler(id); + let unbound = self.uniforms.unbind_sampler_resource(id); + if removed || unbound { + self.uniforms.rebuild_bind_group(device, &self.resources); + } + } + + pub fn bind_texture_resource( + &mut self, + device: &wgpu::Device, + name: &str, + id: &str, + ) -> Result<(), String> { + self.uniforms + .bind_texture_resource(device, &self.resources, name, id) + } + + pub fn bind_sampler_resource( + &mut self, + device: &wgpu::Device, + name: &str, + id: &str, + ) -> Result<(), String> { + self.uniforms + .bind_sampler_resource(device, &self.resources, name, id) + } + + pub fn refresh_textures( + &mut self, + device: &wgpu::Device, + client: &mut texture_share_vk_client::VkClient, + fence: ash::vk::Fence, + ) { + if gpu::refresh_textures(&mut self.resources, device, client, fence) { + self.uniforms.rebuild_bind_group(device, &self.resources); + } + } + pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { self.uniforms.flush(queue); @@ -188,6 +258,7 @@ fn build_pipeline( vertex_module: &wgpu::ShaderModule, vertex_bgl: &wgpu::BindGroupLayout, fragment_source: &str, + resources: &ResourcePools, cache: &mut UniformCache, ) -> Result<wgpu::RenderPipeline, String> { let module = naga::front::wgsl::parse_str(fragment_source) @@ -201,6 +272,7 @@ fn build_pipeline( .map_err(|e| format!("WGSL validation error: {e}"))?; cache.refresh(module, device); + cache.rebuild_bind_group(device, resources); let fragment_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("fragment_shader"), diff --git a/src/uniform.rs b/src/uniform.rs index 44fb541..8f8b172 100644 --- a/src/uniform.rs +++ b/src/uniform.rs @@ -397,10 +397,9 @@ pub fn types_compatible( } } -/// A texture input slot, backed by a wgpu::Texture that can be fed from TSV. -pub struct TextureSlot { - name: String, - binding: u32, +/// A pooled TSV texture resource. +pub struct TextureResource { + id: String, tsv_name: Option<String>, tsv_registered: bool, texture: wgpu::Texture, @@ -414,23 +413,18 @@ pub struct TextureSlot { multisampled: bool, } -impl TextureSlot { - fn new( - device: &wgpu::Device, - name: String, - binding: u32, - view_dimension: wgpu::TextureViewDimension, - sample_type: wgpu::TextureSampleType, - multisampled: bool, - ) -> Self { +impl TextureResource { + pub fn new(device: &wgpu::Device, id: String, tsv_name: String) -> Self { let format = wgpu::TextureFormat::Rgba8UnormSrgb; + let view_dimension = wgpu::TextureViewDimension::D2; + let sample_type = wgpu::TextureSampleType::Float { filterable: true }; + let multisampled = false; let dimension = view_dimension_to_texture_dimension(view_dimension); let (texture, view) = - create_input_texture(device, &name, 1, 1, 1, dimension, view_dimension, format); + create_input_texture(device, &id, 1, 1, 1, dimension, view_dimension, format); Self { - name, - binding, - tsv_name: None, + id, + tsv_name: Some(tsv_name), tsv_registered: false, texture, view, @@ -444,8 +438,8 @@ impl TextureSlot { } } - pub fn name(&self) -> &str { - &self.name + pub fn id(&self) -> &str { + &self.id } pub fn tsv_name(&self) -> Option<&str> { @@ -471,6 +465,22 @@ impl TextureSlot { &self.texture } + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn view_dimension(&self) -> wgpu::TextureViewDimension { + self.view_dimension + } + + pub fn sample_type(&self) -> wgpu::TextureSampleType { + self.sample_type + } + + pub fn multisampled(&self) -> bool { + self.multisampled + } + pub fn width(&self) -> u32 { self.width } @@ -486,24 +496,26 @@ impl TextureSlot { width: u32, height: u32, depth_or_array_layers: u32, + view_dimension: wgpu::TextureViewDimension, format: wgpu::TextureFormat, ) -> bool { if self.width == width && self.height == height && self.depth_or_array_layers == depth_or_array_layers + && self.view_dimension == view_dimension && self.format == format { return false; } - let dimension = view_dimension_to_texture_dimension(self.view_dimension); + let dimension = view_dimension_to_texture_dimension(view_dimension); let (texture, view) = create_input_texture( device, - &self.name, + &self.id, width, height, depth_or_array_layers, dimension, - self.view_dimension, + view_dimension, format, ); self.texture = texture; @@ -512,6 +524,9 @@ impl TextureSlot { self.width = width; self.height = height; self.depth_or_array_layers = depth_or_array_layers; + self.view_dimension = view_dimension; + self.sample_type = wgpu::TextureSampleType::Float { filterable: true }; + self.multisampled = false; true } } @@ -558,16 +573,32 @@ fn create_input_texture( (texture, view) } -pub struct SamplerSlot { - name: String, - binding: u32, +pub struct SamplerResource { + id: String, sampler: wgpu::Sampler, - binding_type: wgpu::SamplerBindingType, } -impl SamplerSlot { - pub fn name(&self) -> &str { - &self.name +impl SamplerResource { + pub fn new( + device: &wgpu::Device, + id: String, + filter: wgpu::FilterMode, + address_mode: wgpu::AddressMode, + ) -> Self { + let mut this = Self { + sampler: create_sampler(device, &id, filter, address_mode), + id, + }; + this.configure(device, filter, address_mode); + this + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler } /// Recreate the sampler with new filter and address modes. @@ -577,22 +608,111 @@ impl SamplerSlot { filter: wgpu::FilterMode, address_mode: wgpu::AddressMode, ) { - self.sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some(&self.name), - mag_filter: filter, - min_filter: filter, - mipmap_filter: match filter { - wgpu::FilterMode::Linear => wgpu::MipmapFilterMode::Linear, - wgpu::FilterMode::Nearest => wgpu::MipmapFilterMode::Nearest, - }, - address_mode_u: address_mode, - address_mode_v: address_mode, - address_mode_w: address_mode, - ..Default::default() - }); + self.sampler = create_sampler(device, &self.id, filter, address_mode); + } +} + +fn create_sampler( + device: &wgpu::Device, + label: &str, + filter: wgpu::FilterMode, + address_mode: wgpu::AddressMode, +) -> wgpu::Sampler { + device.create_sampler(&wgpu::SamplerDescriptor { + label: Some(label), + mag_filter: filter, + min_filter: filter, + mipmap_filter: match filter { + wgpu::FilterMode::Linear => wgpu::MipmapFilterMode::Linear, + wgpu::FilterMode::Nearest => wgpu::MipmapFilterMode::Nearest, + }, + address_mode_u: address_mode, + address_mode_v: address_mode, + address_mode_w: address_mode, + ..Default::default() + }) +} + +pub struct ResourcePools { + textures: HashMap<String, TextureResource>, + samplers: HashMap<String, SamplerResource>, +} + +impl Default for ResourcePools { + fn default() -> Self { + Self::new() + } +} + +impl ResourcePools { + pub fn new() -> Self { + Self { + textures: HashMap::new(), + samplers: HashMap::new(), + } + } + + pub fn create_texture(&mut self, device: &wgpu::Device, id: &str, tsv_name: &str) { + self.textures.insert( + id.to_string(), + TextureResource::new(device, id.to_string(), tsv_name.to_string()), + ); + } + + pub fn destroy_texture(&mut self, id: &str) -> bool { + self.textures.remove(id).is_some() + } + + pub fn texture(&self, id: &str) -> Option<&TextureResource> { + self.textures.get(id) + } + + pub fn texture_mut(&mut self, id: &str) -> Option<&mut TextureResource> { + self.textures.get_mut(id) + } + + pub fn textures_mut(&mut self) -> impl Iterator<Item = &mut TextureResource> { + self.textures.values_mut() + } + + pub fn create_sampler( + &mut self, + device: &wgpu::Device, + id: &str, + filter: wgpu::FilterMode, + address_mode: wgpu::AddressMode, + ) { + self.samplers.insert( + id.to_string(), + SamplerResource::new(device, id.to_string(), filter, address_mode), + ); + } + + pub fn destroy_sampler(&mut self, id: &str) -> bool { + self.samplers.remove(id).is_some() + } + + pub fn sampler(&self, id: &str) -> Option<&SamplerResource> { + self.samplers.get(id) } } +struct TextureBindingSlot { + name: String, + binding: u32, + view_dimension: wgpu::TextureViewDimension, + sample_type: wgpu::TextureSampleType, + multisampled: bool, + resource_id: Option<String>, +} + +struct SamplerBindingSlot { + name: String, + binding: u32, + binding_type: wgpu::SamplerBindingType, + resource_id: Option<String>, +} + fn image_view_dimension(dim: naga::ImageDimension, arrayed: bool) -> wgpu::TextureViewDimension { match (dim, arrayed) { (naga::ImageDimension::D1, false) => wgpu::TextureViewDimension::D1, @@ -633,8 +753,8 @@ pub struct UniformCache { pub layouter: Layouter, uniforms: HashMap<String, UniformMember>, buffers: Vec<BufferState>, - textures: Vec<TextureSlot>, - samplers: Vec<SamplerSlot>, + textures: Vec<TextureBindingSlot>, + samplers: Vec<SamplerBindingSlot>, pub bind_group_layout: Option<wgpu::BindGroupLayout>, pub bind_group: Option<wgpu::BindGroup>, dirty: bool, @@ -685,11 +805,12 @@ impl UniformCache { let old_uniforms = std::mem::take(&mut self.uniforms); let old_buffers = std::mem::take(&mut self.buffers); let old_textures = std::mem::take(&mut self.textures); + let old_samplers = std::mem::take(&mut self.samplers); let mut new_uniforms: HashMap<String, UniformMember> = HashMap::new(); let mut new_buffers: Vec<BufferState> = Vec::new(); - let mut new_textures: Vec<TextureSlot> = Vec::new(); - let mut new_samplers: Vec<SamplerSlot> = Vec::new(); + let mut new_textures: Vec<TextureBindingSlot> = Vec::new(); + let mut new_samplers: Vec<SamplerBindingSlot> = Vec::new(); for (_handle, var) in new_module.global_variables.iter() { let binding = match &var.binding { @@ -734,34 +855,21 @@ impl UniformCache { let sample_type = image_sample_type(class); let multisampled = image_multisampled(class); - let mut slot = TextureSlot::new( - device, - name.clone(), - binding.binding, + let mut slot = TextureBindingSlot { + name: name.clone(), + binding: binding.binding, view_dimension, sample_type, multisampled, - ); + resource_id: None, + }; - // Transfer TSV association from old texture with same name if let Some(old) = old_textures.iter().find(|t| t.name == name) { if old.view_dimension == slot.view_dimension && old.sample_type == slot.sample_type + && old.multisampled == slot.multisampled { - slot.tsv_name = old.tsv_name.clone(); - slot.tsv_registered = old.tsv_registered; - if old.width > 1 - || old.height > 1 - || old.depth_or_array_layers > 1 - { - slot.resize( - device, - old.width, - old.height, - old.depth_or_array_layers, - old.format, - ); - } + slot.resource_id = old.resource_id.clone(); } } @@ -774,18 +882,18 @@ impl UniformCache { wgpu::SamplerBindingType::Filtering }; let name = var.name.clone().unwrap_or_default(); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some(&name), - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - new_samplers.push(SamplerSlot { + let mut slot = SamplerBindingSlot { name, binding: binding.binding, - sampler, binding_type, - }); + resource_id: None, + }; + if let Some(old) = old_samplers.iter().find(|s| { + s.name == slot.name && s.binding_type == slot.binding_type + }) { + slot.resource_id = old.resource_id.clone(); + } + new_samplers.push(slot); } _ => {} } @@ -826,28 +934,77 @@ impl UniformCache { self.buffers = new_buffers; self.textures = new_textures; self.samplers = new_samplers; - self.build_bind_group(device); self.dirty = true; } - pub fn texture_slot_mut(&mut self, name: &str) -> Option<&mut TextureSlot> { - self.textures.iter_mut().find(|t| t.name == name) + pub fn bind_texture_resource( + &mut self, + device: &wgpu::Device, + pools: &ResourcePools, + name: &str, + resource_id: &str, + ) -> Result<(), String> { + if pools.texture(resource_id).is_none() { + return Err(format!("texture '{resource_id}' not found")); + } + let slot = self + .textures + .iter_mut() + .find(|t| t.name == name) + .ok_or_else(|| format!("texture binding '{name}' not found"))?; + slot.resource_id = Some(resource_id.to_string()); + self.build_bind_group(device, pools); + Ok(()) } - pub fn texture_slots_mut(&mut self) -> &mut [TextureSlot] { - &mut self.textures + pub fn bind_sampler_resource( + &mut self, + device: &wgpu::Device, + pools: &ResourcePools, + name: &str, + resource_id: &str, + ) -> Result<(), String> { + if pools.sampler(resource_id).is_none() { + return Err(format!("sampler '{resource_id}' not found")); + } + let slot = self + .samplers + .iter_mut() + .find(|s| s.name == name) + .ok_or_else(|| format!("sampler binding '{name}' not found"))?; + slot.resource_id = Some(resource_id.to_string()); + self.build_bind_group(device, pools); + Ok(()) } - pub fn sampler_slot_mut(&mut self, name: &str) -> Option<&mut SamplerSlot> { - self.samplers.iter_mut().find(|s| s.name == name) + pub fn unbind_texture_resource(&mut self, id: &str) -> bool { + let mut changed = false; + for slot in &mut self.textures { + if slot.resource_id.as_deref() == Some(id) { + slot.resource_id = None; + changed = true; + } + } + changed + } + + pub fn unbind_sampler_resource(&mut self, id: &str) -> bool { + let mut changed = false; + for slot in &mut self.samplers { + if slot.resource_id.as_deref() == Some(id) { + slot.resource_id = None; + changed = true; + } + } + changed } /// Rebuild the bind group (e.g. after a texture resize). - pub fn rebuild_bind_group(&mut self, device: &wgpu::Device) { - self.build_bind_group(device); + pub fn rebuild_bind_group(&mut self, device: &wgpu::Device, pools: &ResourcePools) { + self.build_bind_group(device, pools); } - fn build_bind_group(&mut self, device: &wgpu::Device) { + fn build_bind_group(&mut self, device: &wgpu::Device, pools: &ResourcePools) { let has_bindings = !self.buffers.is_empty() || !self.textures.is_empty() || !self.samplers.is_empty(); @@ -900,6 +1057,29 @@ impl UniformCache { }); let mut bg_entries: Vec<wgpu::BindGroupEntry> = Vec::new(); + let fallback_textures_and_views: Vec<_> = self + .textures + .iter() + .map(|tex| create_placeholder_texture(device, tex)) + .collect(); + let fallback_samplers: Vec<_> = self + .samplers + .iter() + .map(|smp| { + if smp.binding_type == wgpu::SamplerBindingType::Comparison { + device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("comparison_sampler_fallback"), + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }) + } else { + device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("sampler_fallback"), + ..Default::default() + }) + } + }) + .collect(); for buf in &self.buffers { bg_entries.push(wgpu::BindGroupEntry { @@ -908,17 +1088,36 @@ impl UniformCache { }); } - for tex in &self.textures { + for (idx, tex) in self.textures.iter().enumerate() { + let resource = tex + .resource_id + .as_deref() + .and_then(|id| pools.texture(id)) + .filter(|res| texture_resource_matches(tex, res)); + let texture_view = if let Some(resource) = resource { + resource.view() + } else { + &fallback_textures_and_views[idx].1 + }; bg_entries.push(wgpu::BindGroupEntry { binding: tex.binding, - resource: wgpu::BindingResource::TextureView(&tex.view), + resource: wgpu::BindingResource::TextureView(texture_view), }); } - for smp in &self.samplers { + for (idx, smp) in self.samplers.iter().enumerate() { + let sampler = if smp.binding_type == wgpu::SamplerBindingType::Comparison { + &fallback_samplers[idx] + } else if let Some(resource) = + smp.resource_id.as_deref().and_then(|id| pools.sampler(id)) + { + resource.sampler() + } else { + &fallback_samplers[idx] + }; bg_entries.push(wgpu::BindGroupEntry { binding: smp.binding, - resource: wgpu::BindingResource::Sampler(&smp.sampler), + resource: wgpu::BindingResource::Sampler(sampler), }); } @@ -962,3 +1161,31 @@ impl UniformCache { self.dirty = false; } } + +fn texture_resource_matches(slot: &TextureBindingSlot, resource: &TextureResource) -> bool { + slot.view_dimension == resource.view_dimension() + && slot.sample_type == resource.sample_type() + && slot.multisampled == resource.multisampled() +} + +fn create_placeholder_texture( + device: &wgpu::Device, + slot: &TextureBindingSlot, +) -> (wgpu::Texture, wgpu::TextureView) { + let format = match slot.sample_type { + wgpu::TextureSampleType::Float { .. } => wgpu::TextureFormat::Rgba8UnormSrgb, + wgpu::TextureSampleType::Sint => wgpu::TextureFormat::Rgba8Sint, + wgpu::TextureSampleType::Uint => wgpu::TextureFormat::Rgba8Uint, + wgpu::TextureSampleType::Depth => wgpu::TextureFormat::Depth32Float, + }; + create_input_texture( + device, + &format!("placeholder_{}", slot.name), + 1, + 1, + 1, + view_dimension_to_texture_dimension(slot.view_dimension), + slot.view_dimension, + format, + ) +} |
