//! Textura de cobertura de un canal para el camino GPU-directo. //! //! Es el recurso que le faltaba a [`crate::gpu::GpuBatch`] para pintar texto: //! una textura `R8Unorm` donde alguien —hoy `llimphi-glifos`— deja mapas de //! cobertura ya rasterizados, y un pipeline que dibuja quads muestreándola. //! //! **A propósito no sabe nada de fuentes.** Acá adentro no hay glifo ni layout //! ni parley: hay bytes de cobertura y un rect UV. Eso mantiene `llimphi-raster` //! como lo que es —el que habla con wgpu— y deja la tipografía del lado que //! corresponde. La misma textura sirve igual para máscaras de sprites o para un //! atlas de íconos monocromos, sin tocar una línea. //! //! El ciclo típico por frame: //! //! ```ignore //! // Una vez, y se guarda entre frames: recrearla por cuadro tiraría el caché. //! let mut tex = AtlasTextura::new(&device, &pipelines, atlas.lado()); //! // Por frame: subir SÓLO lo que cambió desde la última vez. //! if let Some(s) = atlas.tomar_sucio() { //! tex.subir(&queue, atlas.pixeles(), (s.x, s.y, s.ancho, s.alto)); //! } //! let mut batch = GpuBatch::new(&pipelines); //! batch.atlas(&tex); //! for q in quads { batch.add_glyph(q.x, q.y, q.w, q.h, (q.u0, q.v0, q.u1, q.v1), color); } //! batch.flush(..); //! ``` use llimphi_hal::wgpu; /// El formato del atlas: un byte de cobertura por píxel. Se muestrea como /// `.r` y multiplica el alfa del color del quad. pub const FORMATO_ATLAS: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm; /// Una textura de cobertura viva en GPU, con su bind group listo. /// /// Se construye una vez y se mantiene entre frames: subir el atlas entero cada /// cuadro anularía todo el punto de cachear los glifos. pub struct AtlasTextura { textura: wgpu::Texture, pub(crate) bind_group: wgpu::BindGroup, lado: u32, } impl AtlasTextura { /// Crea la textura cuadrada y su bind group contra el layout del pipeline /// de quads. `lado` es el mismo del atlas de CPU. pub fn new(device: &wgpu::Device, pipelines: &crate::gpu::GpuPipelines, lado: u32) -> Self { let lado = lado.max(1); let textura = device.create_texture(&wgpu::TextureDescriptor { label: Some("llimphi-raster-gpu-atlas"), size: wgpu::Extent3d { width: lado, height: lado, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: FORMATO_ATLAS, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); let vista = textura.create_view(&wgpu::TextureViewDescriptor::default()); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("llimphi-raster-gpu-atlas-bg"), layout: pipelines.atlas_layout(), entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&vista), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(pipelines.atlas_sampler()), }, ], }); Self { textura, bind_group, lado } } pub fn lado(&self) -> u32 { self.lado } /// Sube una región rectangular del atlas de CPU. `pixeles` es el atlas /// **entero** (filas de `lado` bytes); `rect` es qué parte de él subir. /// /// Subir sólo el rect sucio y no el atlas completo no es microoptimización: /// un atlas de 1024² son 1 MiB, y hacerlo por frame sería mover un megabyte /// por PCIe para agregar una letra. pub fn subir( &mut self, queue: &wgpu::Queue, pixeles: &[u8], rect: (u32, u32, u32, u32), ) { let (x, y, w, h) = rect; if w == 0 || h == 0 { return; } // Clamp: un rect fuera de la textura sería un panic de wgpu, y quien // pasa el rect viene de otro crate. let x = x.min(self.lado); let y = y.min(self.lado); let w = w.min(self.lado - x); let h = h.min(self.lado - y); if w == 0 || h == 0 { return; } // Las filas del rect no son contiguas en el atlas de CPU, así que se // compactan. `write_texture` no exige alineación de fila (a diferencia // de `copy_buffer_to_texture`), así que `bytes_per_row = w` va. let mut compacto = Vec::with_capacity((w * h) as usize); for fila in 0..h { let ini = ((y + fila) * self.lado + x) as usize; compacto.extend_from_slice(&pixeles[ini..ini + w as usize]); } queue.write_texture( wgpu::TexelCopyTextureInfo { texture: &self.textura, mip_level: 0, origin: wgpu::Origin3d { x, y, z: 0 }, aspect: wgpu::TextureAspect::All, }, &compacto, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(w), rows_per_image: Some(h), }, wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1, }, ); } }