mod utils;
use ab_glyph::FontRef;
use wgpu::{BindGroupLayout, TextureFormat};
use winit::{event::WindowEvent, window::Window};
use crate::builder::Builder;
use crate::{info::Info, painter::Painter};
use crate::vertex::Vertex;
use crate::App;
const BACKEND_TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb;
pub struct State {
pub(super) instance: wgpu::Instance,
pub(super) adapter: wgpu::Adapter,
pub(super) device: wgpu::Device,
pub(super) queue: wgpu::Queue,
pub(super) size: winit::dpi::PhysicalSize<u32>,
pub(super) window: Window,
pub(super) surface: Option<wgpu::Surface>,
pub(super) config: Option<wgpu::SurfaceConfiguration>,
pub(super) flat_render_pipeline: wgpu::RenderPipeline,
pub(super) texture_render_pipeline: wgpu::RenderPipeline,
pub(super) texture_bind_group_layout: BindGroupLayout,
pub(super) font: FontRef<'static>,
}
impl State {
pub(super) async fn new<T: App + ?Sized>(window: Window, app: &mut T) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: utils::BACKENDS,
dx12_shader_compiler: Default::default(),
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.unwrap();
let (mut device, mut queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: utils::limits(),
label: None,
},
None, )
.await
.unwrap();
let flat_shader = device.create_shader_module(wgpu::include_wgsl!("../assets/flat.wgsl"));
let flat_render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Flat Render Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let flat_render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Flat Render Pipeline"),
layout: Some(&flat_render_pipeline_layout),
vertex: wgpu::VertexState {
module: &flat_shader,
entry_point: "vs_main", buffers: &[Vertex::desc()], },
fragment: Some(wgpu::FragmentState {
module: &flat_shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: BACKEND_TEXTURE_FORMAT,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None,
front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None, multisample: wgpu::MultisampleState {
count: 1, mask: !0, alpha_to_coverage_enabled: false, },
multiview: None, });
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("texture_bind_group_layout"),
});
let texture_shader =
device.create_shader_module(wgpu::include_wgsl!("../assets/texture.wgsl"));
let texture_render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Texture Render Pipeline Layout"),
bind_group_layouts: &[&texture_bind_group_layout],
push_constant_ranges: &[],
});
let texture_render_pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Texture Render Pipeline"),
layout: Some(&texture_render_pipeline_layout),
vertex: wgpu::VertexState {
module: &texture_shader,
entry_point: "vs_main", buffers: &[Vertex::desc()], },
fragment: Some(wgpu::FragmentState {
module: &texture_shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: BACKEND_TEXTURE_FORMAT,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None,
front_face: wgpu::FrontFace::Ccw, cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None, multisample: wgpu::MultisampleState {
count: 1, mask: !0, alpha_to_coverage_enabled: false, },
multiview: None, });
let font =
FontRef::try_from_slice(include_bytes!("../assets/FiraMono-Regular.otf")).unwrap();
{
let info = Info { window: &window };
let mut builder = Builder::new(
&mut device,
&mut queue,
&texture_bind_group_layout,
&font,
&info,
);
app.init(&mut builder);
}
Self {
instance,
adapter,
device,
queue,
size,
window,
surface: None,
config: None,
flat_render_pipeline,
texture_render_pipeline,
texture_bind_group_layout,
font,
}
}
pub fn create_surface(&mut self) {
self.size = self.window.inner_size();
let surface = unsafe { self.instance.create_surface(&self.window) }.unwrap();
let surface_caps = surface.get_capabilities(&self.adapter);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: BACKEND_TEXTURE_FORMAT,
width: self.size.width,
height: self.size.height,
present_mode: wgpu::PresentMode::Fifo, alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&self.device, &config);
self.surface = Some(surface);
self.config = Some(config);
}
pub fn window(&self) -> &Window {
&self.window
}
pub(super) fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
let config = self.config.as_mut().unwrap();
let surface = self.surface.as_mut().unwrap();
config.width = new_size.width;
config.height = new_size.height;
surface.configure(&self.device, &config);
}
}
pub(super) fn event<T: App + ?Sized>(
&mut self,
app: &mut T,
event: &WindowEvent,
) -> crate::EventResult {
app.event(event)
}
pub(super) fn update<T: App + ?Sized>(&mut self, app: &mut T) {
let info = Info {
window: &self.window,
};
let mut builder = Builder::new(
&mut self.device,
&mut self.queue,
&self.texture_bind_group_layout,
&self.font,
&info,
);
app.update(&mut builder, &info);
}
pub(super) fn render<T: App + ?Sized>(
&mut self,
app: &mut T,
) -> Result<(), wgpu::SurfaceError> {
if self.surface.is_none() {
return Err(wgpu::SurfaceError::Lost);
}
let info = Info {
window: &self.window,
};
let mut builder = Builder::new(
&mut self.device,
&mut self.queue,
&self.texture_bind_group_layout,
&self.font,
&info,
);
let mut painter = Painter::new();
app.render(&mut painter, &mut builder, &info);
let output = self.surface.as_ref().unwrap().get_current_texture()?;
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(painter.clear_color.into()),
store: true,
},
})],
depth_stencil_attachment: None,
});
let mut last_was_texture = false;
let mut first_shape = true;
for s in &painter.shapes {
if let Some(bindgroup) = &s.bindgroup {
render_pass.set_pipeline(&self.texture_render_pipeline);
render_pass.set_bind_group(0, bindgroup, &[]);
last_was_texture = true;
} else if last_was_texture || first_shape {
render_pass.set_pipeline(&self.flat_render_pipeline);
last_was_texture = false;
}
render_pass.set_vertex_buffer(0, s.vertex_buffer.slice(..));
render_pass.set_index_buffer(s.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..s.index_count, 0, 0..1);
first_shape = false;
}
}
self.queue.submit([encoder.finish()]);
output.present();
Ok(())
}
}