use cvkg_core::Rect;
use std::sync::Arc;
pub use accesskit::{
ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler,
Node, NodeId, Role, Tree, TreeId, TreeUpdate,
};
pub use accesskit_winit::Adapter as ShieldWallAdapter;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub position: [f32; 2],
pub uv: [f32; 2],
pub color: [f32; 4],
pub mode: u32,
}
impl Vertex {
const ATTRIBUTES: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x4,
3 => Uint32
];
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBUTES,
}
}
}
pub struct SurtrRenderer {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
pipeline: wgpu::RenderPipeline,
bloom_extract_pipeline: wgpu::RenderPipeline,
blur_h_pipeline: wgpu::RenderPipeline,
blur_v_pipeline: wgpu::RenderPipeline,
composite_pipeline: wgpu::RenderPipeline,
blur_texture_a: wgpu::TextureView,
blur_texture_b: wgpu::TextureView,
blur_bind_group_a: wgpu::BindGroup,
blur_bind_group_b: wgpu::BindGroup,
font_system: cosmic_text::FontSystem,
swash_cache: cosmic_text::SwashCache,
dummy_bind_group: wgpu::BindGroup,
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
vertices: Vec<Vertex>,
indices: Vec<u16>,
}
const MAX_VERTICES: usize = 10000;
const MAX_INDICES: usize = 15000;
impl SurtrRenderer {
pub async fn forge(window: Arc<winit::window::Window>) -> Self {
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}).await.expect("Failed to find a suitable GPU for Surtr");
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("Surtr Forge"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
).await.expect("Failed to create Surtr device");
let device = Arc::new(device);
let queue = Arc::new(queue);
let size = window.inner_size();
let config = surface.get_default_config(&adapter, size.width, size.height).unwrap();
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Muspelheim Main Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders.wgsl").into()),
});
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("Niflheim Texture Bind Group Layout"),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Surtr Pipeline Layout"),
bind_group_layouts: &[&texture_bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Surtr Main Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let bloom_extract_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Muspelheim Bloom Extract"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_fullscreen",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_bloom_extract",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Muspelheim Horizontal Blur"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_fullscreen",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_blur_h",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Muspelheim Vertical Blur"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_fullscreen",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_blur_v",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Muspelheim Composite"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_fullscreen",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_composite",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let blur_tex_desc = wgpu::TextureDescriptor {
label: Some("Muspelheim Intermediate"),
size: wgpu::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let blur_texture_a_obj = device.create_texture(&blur_tex_desc);
let blur_texture_b_obj = device.create_texture(&blur_tex_desc);
let blur_texture_a = blur_texture_a_obj.create_view(&wgpu::TextureViewDescriptor::default());
let blur_texture_b = blur_texture_b_obj.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_a) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
],
label: Some("Blur Bind Group A"),
});
let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_b) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
],
label: Some("Blur Bind Group B"),
});
let dummy_size = wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
};
let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Niflheim Dummy Texture"),
size: dummy_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &dummy_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[255, 255, 255, 255],
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: Some(1),
},
dummy_size,
);
let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let dummy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&dummy_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&dummy_sampler),
},
],
label: Some("Niflheim Dummy Bind Group"),
});
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Surtr Vertex Anvil"),
size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Surtr Index Anvil"),
size: (MAX_INDICES * std::mem::size_of::<u16>()) as u64,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
device,
queue,
surface,
config,
pipeline,
bloom_extract_pipeline,
blur_h_pipeline,
blur_v_pipeline,
composite_pipeline,
blur_texture_a,
blur_texture_b,
blur_bind_group_a,
blur_bind_group_b,
font_system: cosmic_text::FontSystem::new(),
swash_cache: cosmic_text::SwashCache::new(),
dummy_bind_group,
vertex_buffer,
index_buffer,
vertices: Vec::with_capacity(MAX_VERTICES),
indices: Vec::with_capacity(MAX_INDICES),
}
}
pub fn begin_frame(&mut self) -> wgpu::CommandEncoder {
self.vertices.clear();
self.indices.clear();
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Surtr's Flaming Sword"),
})
}
pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
self.queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
self.queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
let frame = self.surface.get_current_texture()
.expect("Surtr: failed to acquire surface texture");
let screen = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
{
let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Surtr P1 Base"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &screen,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }), store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
if !self.indices.is_empty() {
p.set_pipeline(&self.pipeline);
p.set_bind_group(0, &self.dummy_bind_group, &[]);
p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
p.draw_indexed(0..self.indices.len() as u32, 0, 0..1);
}
}
{
let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Surtr P2 Bloom Src"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.blur_texture_a,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
if !self.indices.is_empty() {
p.set_pipeline(&self.pipeline);
p.set_bind_group(0, &self.dummy_bind_group, &[]);
p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
p.draw_indexed(0..self.indices.len() as u32, 0, 0..1);
}
}
let blur_iters: u32 = 6;
for i in 0..blur_iters {
{
let label = format!("Surtr Blur H iter {}", i);
let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&label),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.blur_texture_b,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
p.set_pipeline(&self.blur_h_pipeline);
p.set_bind_group(0, &self.blur_bind_group_a, &[]);
p.draw(0..3, 0..1);
}
{
let label = format!("Surtr Blur V iter {}", i);
let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&label),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.blur_texture_a,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
p.set_pipeline(&self.blur_v_pipeline);
p.set_bind_group(0, &self.blur_bind_group_b, &[]);
p.draw(0..3, 0..1);
}
}
{
let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Surtr P7 Composite"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &screen,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
p.set_pipeline(&self.composite_pipeline);
p.set_bind_group(0, &self.blur_bind_group_a, &[]);
p.draw(0..3, 0..1);
}
self.queue.submit(Some(encoder.finish()));
frame.present();
}
pub fn fill_rect(&mut self, rect: Rect, color: [f32; 4], mode: u32) {
let base_idx = self.vertices.len() as u16;
let x1 = (rect.x / 400.0) - 1.0;
let y1 = 1.0 - (rect.y / 300.0);
let x2 = ((rect.x + rect.width) / 400.0) - 1.0;
let y2 = 1.0 - ((rect.y + rect.height) / 300.0);
self.vertices.push(Vertex { position: [x1, y1], uv: [0.0, 0.0], color, mode });
self.vertices.push(Vertex { position: [x2, y1], uv: [1.0, 0.0], color, mode });
self.vertices.push(Vertex { position: [x2, y2], uv: [1.0, 1.0], color, mode });
self.vertices.push(Vertex { position: [x1, y2], uv: [0.0, 1.0], color, mode });
self.indices.extend_from_slice(&[
base_idx, base_idx + 1, base_idx + 2,
base_idx, base_idx + 2, base_idx + 3,
]);
}
}