`Primitive::SAMPLES` says what a primitive samples and how often it has to be bound: `Atlas` once for a list, `Image` for one instance alone. Group 2 is that, whichever it is, so a rect's pipeline has no texture and no sampler in its layout and the prelude hands out neither. The two differ only in the view dimension and in what binds them, so they share `sampled_layout` and `sampled_group`; `GpuPages` owns its bind group again and rebuilds it when the array grows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
120 lines
3.6 KiB
Rust
120 lines
3.6 KiB
Rust
use wgpu::*;
|
|
|
|
use crate::GlyphAtlas;
|
|
|
|
use super::{
|
|
atlas::PAGE,
|
|
texture::{sampled_group, write_region},
|
|
};
|
|
|
|
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
|
/// `GlyphAtlas` packs.
|
|
///
|
|
/// One array rather than a texture per page because a layer index is ordinary
|
|
/// Vulkan 1.0 / GLES sampling, where a `binding_array` would need
|
|
/// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack.
|
|
pub struct GpuPages {
|
|
device: Device,
|
|
queue: Queue,
|
|
texture: Texture,
|
|
group: BindGroup,
|
|
}
|
|
|
|
impl GpuPages {
|
|
pub fn new(
|
|
device: &Device,
|
|
queue: &Queue,
|
|
layout: &BindGroupLayout,
|
|
sampler: &Sampler,
|
|
) -> Self {
|
|
let texture = create_array(device, 1);
|
|
Self {
|
|
device: device.clone(),
|
|
queue: queue.clone(),
|
|
group: atlas_group(device, layout, &texture, sampler),
|
|
texture,
|
|
}
|
|
}
|
|
|
|
pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
if atlas.page_count() > self.texture.depth_or_array_layers() {
|
|
self.grow(atlas.page_count(), layout, sampler);
|
|
}
|
|
for (upload, page) in atlas.uploads() {
|
|
let dst = TexelCopyTextureInfo {
|
|
texture: &self.texture,
|
|
mip_level: 0,
|
|
origin: Origin3d {
|
|
x: upload.rect.x,
|
|
y: upload.rect.y,
|
|
z: upload.layer,
|
|
},
|
|
aspect: TextureAspect::All,
|
|
};
|
|
write_region(&self.queue, dst, page, upload.rect);
|
|
}
|
|
}
|
|
|
|
pub fn group(&self) -> &BindGroup {
|
|
&self.group
|
|
}
|
|
|
|
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
|
/// The new texture stales the group, so that is rebuilt here.
|
|
fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
let old = self.texture.depth_or_array_layers();
|
|
let mut layers = old;
|
|
while layers < needed {
|
|
layers *= 2;
|
|
}
|
|
let texture = create_array(&self.device, layers);
|
|
let mut encoder = self
|
|
.device
|
|
.create_command_encoder(&CommandEncoderDescriptor {
|
|
label: Some("atlas grow"),
|
|
});
|
|
encoder.copy_texture_to_texture(
|
|
self.texture.as_image_copy(),
|
|
texture.as_image_copy(),
|
|
Extent3d {
|
|
width: PAGE,
|
|
height: PAGE,
|
|
depth_or_array_layers: old,
|
|
},
|
|
);
|
|
self.queue.submit(std::iter::once(encoder.finish()));
|
|
self.group = atlas_group(&self.device, layout, &texture, sampler);
|
|
self.texture = texture;
|
|
}
|
|
}
|
|
|
|
fn atlas_group(
|
|
device: &Device,
|
|
layout: &BindGroupLayout,
|
|
texture: &Texture,
|
|
sampler: &Sampler,
|
|
) -> BindGroup {
|
|
let view = texture.create_view(&TextureViewDescriptor {
|
|
dimension: Some(TextureViewDimension::D2Array),
|
|
..Default::default()
|
|
});
|
|
sampled_group(device, layout, &view, sampler, "ui atlas")
|
|
}
|
|
|
|
fn create_array(device: &Device, layers: u32) -> Texture {
|
|
device.create_texture(&TextureDescriptor {
|
|
label: Some("glyph atlas"),
|
|
size: Extent3d {
|
|
width: PAGE,
|
|
height: PAGE,
|
|
depth_or_array_layers: layers,
|
|
},
|
|
mip_level_count: 1,
|
|
sample_count: 1,
|
|
dimension: TextureDimension::D2,
|
|
format: TextureFormat::Rgba8Unorm,
|
|
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
|
|
view_formats: &[],
|
|
})
|
|
}
|