Galactica/crates/render/src/gpustate.rs

323 lines
8.8 KiB
Rust

use std::rc::Rc;
use anyhow::Result;
use bytemuck;
use galactica_content::Content;
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
use log::debug;
use wgpu;
use winit;
use crate::{
globaluniform::{GlobalDataContent, GlobalUniform},
pipeline::PipelineBuilder,
renderscene::{LandedScene, RenderScene, SystemScene},
shaderprocessor::preprocess_shader,
starfield::Starfield,
texturearray::TextureArray,
ui::{UiManager, UiScene},
RenderInput, RenderScenes, RenderState, VertexBuffers,
};
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
pub struct GPUState {
pub(crate) device: wgpu::Device,
pub(crate) config: wgpu::SurfaceConfiguration,
pub(crate) surface: wgpu::Surface,
pub(crate) object_pipeline: wgpu::RenderPipeline,
pub(crate) starfield_pipeline: wgpu::RenderPipeline,
pub(crate) ui_pipeline: wgpu::RenderPipeline,
pub(crate) radialbar_pipeline: wgpu::RenderPipeline,
pub(crate) starfield: Starfield,
pub(crate) texture_array: TextureArray,
pub(crate) state: RenderState,
pub(crate) ui: UiManager,
pub(crate) scene: RenderScenes,
}
impl GPUState {
/// Make a new GPUState that draws on `window`
pub async fn new(window: winit::window::Window, ct: Rc<Content>) -> Result<Self> {
let window_size = window.inner_size();
let window_aspect = window_size.width as f32 / window_size.height as f32;
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = unsafe { instance.create_surface(&window) }.unwrap();
// Basic setup
let device;
let queue;
let config;
{
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
(device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
// TODO: remove nonuniform sampled textures
features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
// We may need limits if we compile for wasm
limits: wgpu::Limits::default(),
label: Some("gpu device"),
},
None,
)
.await
.unwrap();
// Assume sRGB
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.filter(|f| f.is_srgb())
.filter(|f| f.has_stencil_aspect())
.next()
.unwrap_or(surface_caps.formats[0]);
config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: window_size.width,
height: window_size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &config);
}
let vertex_buffers = VertexBuffers::new(&device, &ct);
// Load uniforms
let global_uniform = GlobalUniform::new(&device);
let texture_array = TextureArray::new(&device, &queue, &ct)?;
// Make sure these match the indices in each shader
let bind_group_layouts = &[
&texture_array.bind_group_layout,
&global_uniform.bind_group_layout,
];
// Text renderer
let mut text_atlas = TextAtlas::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb);
let mut text_font_system = FontSystem::new_with_locale_and_db(
"en-US".to_string(),
glyphon::fontdb::Database::new(),
);
let conf = ct.get_config();
for font in &conf.font_files {
text_font_system.db_mut().load_font_file(font)?;
}
// TODO: nice error if no family with this name is found
text_font_system
.db_mut()
.set_sans_serif_family(conf.font_sans.clone());
text_font_system
.db_mut()
.set_serif_family(conf.font_serif.clone());
text_font_system
.db_mut()
.set_monospace_family(conf.font_mono.clone());
//text_font_system
// .db_mut()
// .set_cursive_family(conf.font_cursive.clone());
//text_font_system
// .db_mut()
// .set_fantasy_family(conf.font_fantasy.clone());
let text_cache = SwashCache::new();
let text_renderer = TextRenderer::new(
&mut text_atlas,
&device,
wgpu::MultisampleState::default(),
None,
);
// Create render pipelines
let object_pipeline = PipelineBuilder::new("object", &device)
.set_shader(&preprocess_shader(
&include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shaders/",
"object.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format)
.set_triangle(true)
.set_vertex_buffer(vertex_buffers.get_object())
.set_bind_group_layouts(bind_group_layouts)
.build();
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
.set_shader(&preprocess_shader(
&include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shaders/",
"starfield.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format)
.set_triangle(true)
.set_vertex_buffer(vertex_buffers.get_starfield())
.set_bind_group_layouts(bind_group_layouts)
.build();
let ui_pipeline = PipelineBuilder::new("ui", &device)
.set_shader(&preprocess_shader(
&include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")),
&global_uniform,
1,
))
.set_format(config.format)
.set_triangle(true)
.set_vertex_buffer(vertex_buffers.get_ui())
.set_bind_group_layouts(bind_group_layouts)
.build();
let radialbar_pipeline = PipelineBuilder::new("radialbar", &device)
.set_shader(&preprocess_shader(
&include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shaders/",
"radialbar.wgsl"
)),
&global_uniform,
1,
))
.set_format(config.format)
.set_triangle(true)
.set_vertex_buffer(vertex_buffers.get_radialbar())
.set_bind_group_layouts(bind_group_layouts)
.build();
let mut starfield = Starfield::new();
starfield.regenerate(&ct);
return Ok(Self {
ui: UiManager::new(ct),
device,
config,
surface,
starfield,
texture_array,
object_pipeline,
starfield_pipeline,
ui_pipeline,
radialbar_pipeline,
scene: RenderScenes::Landed,
state: RenderState {
queue,
window,
window_size,
window_aspect,
global_uniform,
vertex_buffers,
text_atlas,
text_cache,
text_font_system,
text_renderer,
},
});
}
}
impl GPUState {
/// Get the window we are attached to
pub fn window(&self) -> &winit::window::Window {
&self.state.window
}
/// Change the current scenection
pub fn set_scene(&mut self, scene: RenderScenes) {
debug!("switching to {:?}", scene);
match scene {
RenderScenes::Landed => self.ui.set_scene(&mut self.state, UiScene::Landed).unwrap(),
RenderScenes::System => self.ui.set_scene(&mut self.state, UiScene::Flying).unwrap(),
};
self.scene = scene;
}
/// Update window size.
/// This should be called whenever our window is resized.
pub fn resize(&mut self, ct: &Content) {
let new_size = self.state.window.inner_size();
if new_size.width > 0 && new_size.height > 0 {
self.state.window_size = new_size;
self.state.window_aspect = new_size.width as f32 / new_size.height as f32;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
self.starfield.update_buffer(ct, &mut self.state);
}
/// Initialize the rendering engine
pub fn init(&mut self, ct: &Content) {
// Update global values
self.state.queue.write_buffer(
&self.state.global_uniform.atlas_buffer,
0,
bytemuck::cast_slice(&[self.texture_array.texture_atlas]),
);
self.starfield.update_buffer(ct, &mut self.state);
}
/// Main render function. Draws sprites on a window.
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
// Update global values
self.state.queue.write_buffer(
&self.state.global_uniform.data_buffer,
0,
bytemuck::cast_slice(&[GlobalDataContent {
camera_position_x: input.camera_pos.x,
camera_position_y: input.camera_pos.y,
camera_zoom: input.camera_zoom,
camera_zoom_min: input.ct.get_config().zoom_min,
camera_zoom_max: input.ct.get_config().zoom_max,
window_size_w: self.state.window_size.width as f32,
window_size_h: self.state.window_size.height as f32,
window_scale: self.state.window.scale_factor() as f32,
window_aspect: self.state.window_aspect,
starfield_sprite: input.ct.get_config().starfield_texture.into(),
starfield_tile_size: input.ct.get_config().starfield_size,
starfield_size_min: input.ct.get_config().starfield_min_size,
starfield_size_max: input.ct.get_config().starfield_max_size,
}]),
);
self.state.frame_reset();
match self.scene {
RenderScenes::System => SystemScene::render(self, &input).unwrap(),
RenderScenes::Landed => LandedScene::render(self, &input).unwrap(),
}
return Ok(());
}
}