use crate::core::Rgba;
use crate::gpu::atlas::TextureAtlas;
use crate::gpu::blur::BlurState;
use crate::gpu::context::{Error, GpuContext};
use crate::gpu::instance_buffer::InstanceBuffer;
use crate::gpu::pipelines::{GLOBALS_SIZE, Pipelines};
use crate::gpu::scene::{Batch, PrimitiveKind, Scene};
use crate::gpu::text::GlyphTarget;
use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Globals {
viewport: [f32; 2],
padding: [f32; 2],
matrix: [f32; 4],
offset: [f32; 4],
}
struct GlobalsSlots {
buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
capacity: usize,
stride: u64,
scratch: Vec<u8>,
}
impl GlobalsSlots {
fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, capacity: usize) -> Self {
let stride = GLOBALS_SIZE.next_multiple_of(u64::from(device.limits().min_uniform_buffer_offset_alignment));
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("aura_globals"),
size: stride * capacity as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("aura_globals"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &buffer,
offset: 0,
size: wgpu::BufferSize::new(GLOBALS_SIZE),
}),
}],
});
Self { buffer, bind_group, capacity, stride, scratch: Vec::new() }
}
fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
scene: &Scene,
viewport: [f32; 2],
) {
let slots = scene.transforms.len().max(1);
if slots > self.capacity {
*self = Self::new(device, layout, slots.next_power_of_two());
}
self.scratch.clear();
self.scratch.resize(slots * self.stride as usize, 0);
for slot in 0..slots {
let transform = scene.transforms.get(slot).copied().unwrap_or_default();
let globals = Globals {
viewport,
padding: [0.0; 2],
matrix: [transform.a, transform.b, transform.c, transform.d],
offset: [transform.tx, transform.ty, transform.scale_factor(), 0.0],
};
let start = slot * self.stride as usize;
self.scratch[start..start + GLOBALS_SIZE as usize].copy_from_slice(bytemuck::bytes_of(&globals));
}
queue.write_buffer(&self.buffer, 0, &self.scratch);
}
}
#[path = "renderer_blur.rs"]
mod blur_frame;
pub struct GpuRenderer {
pub context: GpuContext,
pipelines: Pipelines,
globals: GlobalsSlots,
blur: Option<BlurState>,
monochrome_atlas: TextureAtlas,
polychrome_atlas: TextureAtlas,
monochrome_bind_group: wgpu::BindGroup,
polychrome_bind_group: wgpu::BindGroup,
shadows: InstanceBuffer,
quads: InstanceBuffer,
underlines: InstanceBuffer,
monochrome_sprites: InstanceBuffer,
polychrome_sprites: InstanceBuffer,
}
impl GpuRenderer {
pub fn new(context: GpuContext) -> Result<Self, Error> {
let device = &context.device;
let pipelines = Pipelines::new(device, context.config.format)?;
let globals = GlobalsSlots::new(device, &pipelines.globals_layout, 1);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let monochrome_atlas = TextureAtlas::monochrome(device);
let polychrome_atlas = TextureAtlas::polychrome(device);
let atlas_bind_group = |atlas: &TextureAtlas, label: &str| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout: &pipelines.atlas_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(atlas.view()) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
],
})
};
let monochrome_bind_group = atlas_bind_group(&monochrome_atlas, "aura_mono_bind");
let polychrome_bind_group = atlas_bind_group(&polychrome_atlas, "aura_color_bind");
let layout = &pipelines.instances_layout;
Ok(Self {
shadows: InstanceBuffer::new(device, layout, "aura_shadows"),
quads: InstanceBuffer::new(device, layout, "aura_quads"),
underlines: InstanceBuffer::new(device, layout, "aura_underlines"),
monochrome_sprites: InstanceBuffer::new(device, layout, "aura_mono_sprites"),
polychrome_sprites: InstanceBuffer::new(device, layout, "aura_color_sprites"),
pipelines,
globals,
blur: None,
monochrome_atlas,
polychrome_atlas,
monochrome_bind_group,
polychrome_bind_group,
context,
})
}
pub fn resize(&mut self, width: u32, height: u32) {
self.context.resize(width, height);
}
pub fn take_atlas_overflow(&mut self) -> bool {
let mono = self.monochrome_atlas.take_overflow();
let color = self.polychrome_atlas.take_overflow();
mono || color
}
pub fn glyph_target(&mut self) -> GlyphTarget<'_> {
GlyphTarget {
queue: Some(&self.context.queue),
monochrome_atlas: &mut self.monochrome_atlas,
polychrome_atlas: &mut self.polychrome_atlas,
}
}
pub fn render(&mut self, scene: &Scene, scale: f32, clear: Rgba) -> Result<bool, Error> {
let frame = match self.context.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame) => frame,
wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
self.context.resize(self.context.config.width, self.context.config.height);
frame
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => return Ok(false),
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
self.context.resize(self.context.config.width, self.context.config.height);
return Ok(false);
}
other => return Err(Error(format!("no se pudo adquirir el frame: {other:?}"))),
};
self.upload(scene, scale);
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let encoder = self.encode(&view, scene, clear);
self.context.queue.submit(Some(encoder.finish()));
self.context.queue.present(frame);
Ok(true)
}
pub(crate) fn encode(&self, view: &wgpu::TextureView, scene: &Scene, clear: Rgba) -> wgpu::CommandEncoder {
let mut encoder =
self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("aura_frame") });
if let Some(blur) = self.blur.as_ref().filter(|_| scene.has_blur()) {
self.encode_blurred(&mut encoder, blur, view, scene, clear);
return encoder;
}
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("aura_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: clear.r as f64,
g: clear.g as f64,
b: clear.b as f64,
a: clear.a as f64,
}),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
self.draw_batches(&mut pass, &scene.batches);
}
encoder
}
pub(crate) fn upload(&mut self, scene: &Scene, scale: f32) {
let viewport = [self.context.config.width as f32 / scale, self.context.config.height as f32 / scale];
let (device, queue) = (&self.context.device, &self.context.queue);
self.globals.upload(device, queue, &self.pipelines.globals_layout, scene, viewport);
self.prepare_blur(scene, scale);
let (device, queue) = (&self.context.device, &self.context.queue);
let layout = &self.pipelines.instances_layout;
self.shadows.upload(device, queue, layout, &scene.shadows);
self.quads.upload(device, queue, layout, &scene.quads);
self.underlines.upload(device, queue, layout, &scene.underlines);
self.monochrome_sprites.upload(device, queue, layout, &scene.monochrome_sprites);
self.polychrome_sprites.upload(device, queue, layout, &scene.polychrome_sprites);
}
fn draw_batches(&self, pass: &mut wgpu::RenderPass<'_>, batches: &[Batch]) {
let mut bound = None;
for batch in batches {
if bound != Some(batch.transform) {
bound = Some(batch.transform);
pass.set_bind_group(
0,
&self.globals.bind_group,
&[(u64::from(batch.transform) * self.globals.stride) as u32],
);
}
let (pipeline, instances, atlas) = match batch.kind {
PrimitiveKind::Blur => {
let Some(blur) = &self.blur else { continue };
(blur.composite(), blur.instances(), blur.blurred_bind())
}
PrimitiveKind::Shadow => (&self.pipelines.shadow, &self.shadows, &self.monochrome_bind_group),
PrimitiveKind::Quad => (&self.pipelines.quad, &self.quads, &self.monochrome_bind_group),
PrimitiveKind::Underline => (&self.pipelines.underline, &self.underlines, &self.monochrome_bind_group),
PrimitiveKind::Monochrome => {
(&self.pipelines.monochrome, &self.monochrome_sprites, &self.monochrome_bind_group)
}
PrimitiveKind::Polychrome => {
(&self.pipelines.polychrome, &self.polychrome_sprites, &self.polychrome_bind_group)
}
};
pass.set_pipeline(pipeline);
pass.set_bind_group(1, instances.bind_group(), &[]);
pass.set_bind_group(2, atlas, &[]);
pass.draw(0..4, batch.range.clone());
}
}
pub fn shrink_caches(&mut self) {
let (device, layout) = (&self.context.device, &self.pipelines.instances_layout);
self.shadows.shrink(device, layout);
self.quads.shrink(device, layout);
self.underlines.shrink(device, layout);
self.monochrome_sprites.shrink(device, layout);
self.polychrome_sprites.shrink(device, layout);
self.monochrome_atlas.clear();
self.polychrome_atlas.clear();
if let Some(blur) = &mut self.blur {
blur.release();
}
}
}