aura-gui 0.3.5

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Storage buffer de instancias que crece por potencias de dos y evita transferencias redundantes.

use bytemuck::Pod;
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};

const MINIMUM_CAPACITY: u64 = 4096;

pub struct InstanceBuffer {
    buffer: wgpu::Buffer,
    bind_group: wgpu::BindGroup,
    capacity: u64,
    label: &'static str,
    last_hash: Option<(usize, u64)>,
}

fn hash_slice(bytes: &[u8]) -> (usize, u64) {
    let mut hasher = FxHasher::default();
    bytes.hash(&mut hasher);
    (bytes.len(), hasher.finish())
}

impl InstanceBuffer {
    pub fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, label: &'static str) -> Self {
        Self::with_capacity(device, layout, label, MINIMUM_CAPACITY)
    }

    fn with_capacity(
        device: &wgpu::Device,
        layout: &wgpu::BindGroupLayout,
        label: &'static str,
        capacity: u64,
    ) -> Self {
        let buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some(label),
            size: capacity,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some(label),
            layout,
            entries: &[wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() }],
        });
        Self { buffer, bind_group, capacity, label, last_hash: None }
    }

    pub fn bind_group(&self) -> &wgpu::BindGroup {
        &self.bind_group
    }

    /// Sube `items` a la GPU solo si el contenido de este buffer cambió respecto al frame anterior.
    pub fn upload<T: Pod>(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        layout: &wgpu::BindGroupLayout,
        items: &[T],
    ) {
        let bytes: &[u8] = bytemuck::cast_slice(items);
        if bytes.is_empty() {
            return;
        }
        let signature = hash_slice(bytes);
        let required = bytes.len() as u64;
        if required > self.capacity {
            *self = Self::with_capacity(device, layout, self.label, required.next_power_of_two());
        } else if self.last_hash == Some(signature) {
            return;
        }
        self.last_hash = Some(signature);
        queue.write_buffer(&self.buffer, 0, bytes);
    }

    /// Vuelve al tamaño mínimo para devolver VRAM tras un pico de primitivas.
    pub fn shrink(&mut self, device: &wgpu::Device, layout: &wgpu::BindGroupLayout) {
        if self.capacity > MINIMUM_CAPACITY {
            *self = Self::new(device, layout, self.label);
        } else {
            self.last_hash = None;
        }
    }
}