dear-imgui-wgpu 0.16.0-alpha.2

WGPU renderer backend for dear-imgui-rs (native + WebAssembly)
Documentation
//! Render resources management for the WGPU renderer
//!
//! This module handles shared render resources like samplers, uniforms, and bind groups,
//! corresponding to the RenderResources struct in imgui_impl_wgpu.cpp

use crate::{RendererError, RendererResult, UniformBuffer};
use dear_imgui_rs::TextureId;
use std::collections::HashMap;
use wgpu::*;

/// Shared render resources
///
/// This corresponds to the RenderResources struct in the C++ implementation.
/// Contains samplers and bind group layouts shared across all frames.
pub struct RenderResources {
    /// Linear texture sampler
    pub sampler: Option<Sampler>,
    /// Nearest/point texture sampler
    pub sampler_nearest: Option<Sampler>,
    /// Common layout shared by per-pass uniform and sampler bindings.
    pub common_bind_group_layout: Option<BindGroupLayout>,
    /// Image bind groups cache (texture_id -> bind_group)
    pub image_bind_groups: HashMap<TextureId, BindGroup>,
    /// Image bind group layout (cached for efficiency)
    pub image_bind_group_layout: Option<BindGroupLayout>,
}

impl RenderResources {
    /// Create new empty render resources
    pub fn new() -> Self {
        Self {
            sampler: None,
            sampler_nearest: None,
            common_bind_group_layout: None,
            image_bind_groups: HashMap::new(),
            image_bind_group_layout: None,
        }
    }

    /// Initialize render resources
    pub fn initialize(&mut self, device: &Device) -> RendererResult<()> {
        #[cfg(feature = "wgpu-27")]
        fn linear_mipmap_filter() -> FilterMode {
            FilterMode::Linear
        }
        #[cfg(feature = "wgpu-27")]
        fn nearest_mipmap_filter() -> FilterMode {
            FilterMode::Nearest
        }
        #[cfg(any(feature = "wgpu-28", feature = "wgpu-29", feature = "wgpu-30"))]
        fn linear_mipmap_filter() -> MipmapFilterMode {
            MipmapFilterMode::Linear
        }
        #[cfg(any(feature = "wgpu-28", feature = "wgpu-29", feature = "wgpu-30"))]
        fn nearest_mipmap_filter() -> MipmapFilterMode {
            MipmapFilterMode::Nearest
        }

        // Create linear texture sampler (matches imgui_impl_wgpu.cpp sampler setup)
        // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines'
        // or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling
        let sampler = device.create_sampler(&SamplerDescriptor {
            label: Some("Dear ImGui Texture Sampler"),
            address_mode_u: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
            address_mode_v: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
            address_mode_w: AddressMode::ClampToEdge, // matches WGPUAddressMode_ClampToEdge
            mag_filter: FilterMode::Linear,           // matches WGPUFilterMode_Linear
            min_filter: FilterMode::Linear,           // matches WGPUFilterMode_Linear
            mipmap_filter: linear_mipmap_filter(),    // matches WGPUMipmapFilterMode_Linear
            anisotropy_clamp: 1,                      // matches maxAnisotropy = 1
            ..Default::default()
        });

        let sampler_nearest = device.create_sampler(&SamplerDescriptor {
            label: Some("Dear ImGui Texture Sampler Nearest"),
            address_mode_u: AddressMode::ClampToEdge,
            address_mode_v: AddressMode::ClampToEdge,
            address_mode_w: AddressMode::ClampToEdge,
            mag_filter: FilterMode::Nearest,
            min_filter: FilterMode::Nearest,
            mipmap_filter: nearest_mipmap_filter(),
            anisotropy_clamp: 1,
            ..Default::default()
        });

        let common_bind_group_layout = UniformBuffer::create_bind_group_layout(device);

        // Create image bind group layout (for texture views)
        let image_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
            label: Some("Dear ImGui Image Bind Group Layout"),
            entries: &[BindGroupLayoutEntry {
                binding: 0,
                visibility: ShaderStages::FRAGMENT,
                ty: BindingType::Texture {
                    multisampled: false,
                    sample_type: TextureSampleType::Float { filterable: true },
                    view_dimension: TextureViewDimension::D2,
                },
                count: None,
            }],
        });

        self.sampler = Some(sampler);
        self.sampler_nearest = Some(sampler_nearest);
        self.common_bind_group_layout = Some(common_bind_group_layout);
        self.image_bind_group_layout = Some(image_bind_group_layout);

        Ok(())
    }

    /// Create an image bind group for a texture
    pub fn create_image_bind_group(
        &self,
        device: &Device,
        texture_view: &TextureView,
    ) -> RendererResult<BindGroup> {
        let layout = self.image_bind_group_layout.as_ref().ok_or_else(|| {
            RendererError::InvalidRenderState("Image bind group layout not initialized".to_string())
        })?;

        let bind_group = device.create_bind_group(&BindGroupDescriptor {
            label: Some("Dear ImGui Image Bind Group"),
            layout,
            entries: &[BindGroupEntry {
                binding: 0,
                resource: BindingResource::TextureView(texture_view),
            }],
        });

        Ok(bind_group)
    }

    /// Get or create an image bind group for a texture
    pub fn get_or_create_image_bind_group(
        &mut self,
        device: &Device,
        texture_id: TextureId,
        texture_view: &TextureView,
    ) -> RendererResult<&BindGroup> {
        if !self.image_bind_groups.contains_key(&texture_id) {
            let bind_group = self.create_image_bind_group(device, texture_view)?;
            self.image_bind_groups.insert(texture_id, bind_group);
        }

        self.image_bind_groups.get(&texture_id).ok_or_else(|| {
            RendererError::InvalidRenderState("Image bind group missing after creation".to_string())
        })
    }

    pub(crate) fn create_frame_bindings(
        &self,
        device: &Device,
    ) -> RendererResult<(UniformBuffer, BindGroup)> {
        let sampler = self.sampler.as_ref().ok_or_else(|| {
            RendererError::InvalidRenderState("Linear sampler not initialized".to_owned())
        })?;
        let nearest_sampler = self.sampler_nearest.as_ref().ok_or_else(|| {
            RendererError::InvalidRenderState("Nearest sampler not initialized".to_owned())
        })?;
        let layout = self.common_bind_group_layout().ok_or_else(|| {
            RendererError::InvalidRenderState("Common bind group layout not initialized".to_owned())
        })?;
        let uniform = UniformBuffer::new_with_layout(device, sampler, layout);
        let nearest = device.create_bind_group(&BindGroupDescriptor {
            label: Some("Dear ImGui Frame Bind Group Nearest Sampler"),
            layout,
            entries: &[
                BindGroupEntry {
                    binding: 0,
                    resource: uniform.buffer().as_entire_binding(),
                },
                BindGroupEntry {
                    binding: 1,
                    resource: BindingResource::Sampler(nearest_sampler),
                },
            ],
        });
        Ok((uniform, nearest))
    }

    /// Remove an image bind group
    pub fn remove_image_bind_group(&mut self, texture_id: TextureId) {
        self.image_bind_groups.remove(&texture_id);
    }

    /// Get the common layout used by every per-pass binding.
    pub fn common_bind_group_layout(&self) -> Option<&BindGroupLayout> {
        self.common_bind_group_layout.as_ref()
    }

    /// Get the image bind group layout
    pub fn image_bind_group_layout(&self) -> Option<&BindGroupLayout> {
        self.image_bind_group_layout.as_ref()
    }
}

impl Default for RenderResources {
    fn default() -> Self {
        Self::new()
    }
}