use crate::ecs::{Camera, Rotation, Scale, Shear, SpriteTexture, Translation}; use crate::rendering::assets::Assets; use crate::rendering::mipmap::MipGenerator; use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet}; use crate::rendering::texture; use crate::rendering::ui::UiRenderable; use bevy_ecs::schedule::Schedule; use bevy_ecs::world::World; use egui::ViewportId; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::sync::Arc; use tracing::info; use web_time::Instant; use wgpu::SurfaceConfiguration; use wgpu::{ include_wgsl, Adapter, Backends, BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer, BufferDescriptor, BufferUsages, Color, ColorTargetState, CommandEncoderDescriptor, Device, DeviceDescriptor, Features, FragmentState, Instance, InstanceDescriptor, Limits, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, RequestAdapterOptions, ShaderModule, StoreOp, Surface, TextureViewDescriptor, VertexState, }; use winit::dpi::{LogicalSize, PhysicalSize}; use winit::window::Window; #[allow(unused_attributes, dead_code)] pub struct Renderer { pub last_frame_time: Instant, pub surface: Surface<'static>, pub surface_configuration: SurfaceConfiguration, pub device: Device, pub queue: Queue, pub adapter: Adapter, pub sprite_shader_module: ShaderModule, pub sprite_pipeline: RenderPipeline, pub world: World, pub update_schedule: Schedule, pub gui_ctx: egui::Context, pub gui_winit: egui_winit::State, pub gui_renderer: egui_wgpu::Renderer, pub gui_renderable: T, pub textures: HashMap, pub mip_generator: MipGenerator, pub frame_uniform: Buffer, pub local_uniform: Buffer, pub scale_factor: f64, pub window: Arc, pub size: PhysicalSize, pub logical_size: LogicalSize, } #[allow(clippy::large_enum_variant)] pub enum RenderInitRes { Initialized(Renderer), NotReadyYet(World, Schedule, T), } impl Debug for RenderInitRes { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Initialized(..) => write!(f, "[initialized renderer]"), NotReadyYet(..) => write!(f, "[pending initialization]"), } } } impl Renderer { pub async fn try_init( window: Arc, world: World, update_schedule: Schedule, gui_renderable: T, ) -> RenderInitRes { let size = window.inner_size(); if size.width == 0 || size.height == 0 { return NotReadyYet(world, update_schedule, gui_renderable); } // First, create an instance. This is our handle to wgpu, and is the equivalent of navigator.gpu in WebGPU let instance = Instance::new(InstanceDescriptor { backends: Backends::all(), // Select the appropriate backend for the HAL to use. Defined in the platform module, as it's platform-specific ..Default::default() // Other fields aren't relevant here }); // Next, get our render surface let surface = instance.create_surface(window.clone()).unwrap(); // Next, request out adapter let adapter = instance .request_adapter(&RequestAdapterOptions { power_preference: Default::default(), // Don't care force_fallback_adapter: false, // We want a real GPU compatible_surface: Some(&surface), // Find an adapter that is able to render to our window }) .await .unwrap(); let mut limits = Limits::downlevel_webgl2_defaults(); limits.max_texture_dimension_1d = 8192; limits.max_texture_dimension_2d = 8192; limits.max_texture_dimension_3d = 2048; let (device, queue) = adapter .request_device( &DeviceDescriptor { label: Some("Basic render device"), required_features: Features::default(), required_limits: limits, memory_hints: Default::default(), }, None, ) .await .unwrap(); let format = surface .get_default_config(&adapter, size.width, size.height) .unwrap(); surface.configure(&device, &format); let sprite_shader_module = device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl")); let sprite_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { label: Some("Sprite pipeline"), layout: None, vertex: VertexState { module: &sprite_shader_module, entry_point: Some("vs"), compilation_options: Default::default(), buffers: &[], }, primitive: Default::default(), depth_stencil: None, multisample: Default::default(), fragment: Some(FragmentState { module: &sprite_shader_module, entry_point: Some("fs"), compilation_options: Default::default(), targets: &[Some(ColorTargetState { format: format.format, blend: None, write_mask: Default::default(), })], }), multiview: None, cache: None, }); let gui_context = egui::Context::default(); let gui_winit = egui_winit::State::new( gui_context.clone(), ViewportId::ROOT, &window, Some(window.scale_factor() as f32), None, Some(device.limits().max_texture_dimension_2d as usize), ); let gui_renderer = egui_wgpu::Renderer::new(&device, format.format, None, 1, false); let frame_uniform = device.create_buffer(&BufferDescriptor { label: Some("frame uniforms"), size: 48 + 16, // mat3x3f, vec2f usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, mapped_at_creation: false, }); let local_uniform = device.create_buffer(&BufferDescriptor { label: Some("local uniforms"), size: 48, // mat3x3f usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, mapped_at_creation: false, }); Initialized(Self { mip_generator: MipGenerator::new(&device), textures: HashMap::new(), last_frame_time: Instant::now(), surface, device, queue, adapter, sprite_shader_module, sprite_pipeline, world, update_schedule, gui_ctx: gui_context, gui_winit, gui_renderer, gui_renderable, logical_size: LogicalSize::from_physical(size, window.scale_factor()), scale_factor: window.scale_factor(), window, frame_uniform, local_uniform, size, surface_configuration: format, }) } pub fn render(&mut self) { // update the world self.update_schedule.run(&mut self.world); // update the UI let egui_output = self .gui_ctx .run(self.gui_winit.take_egui_input(&self.window), |ctx| { self.gui_renderable.render(ctx, &mut self.world) }); self.gui_winit .handle_platform_output(&self.window, egui_output.platform_output); let output = self.surface.get_current_texture().unwrap(); let view = output .texture .create_view(&TextureViewDescriptor::default()); let render_pass_descriptor = RenderPassDescriptor { label: Some("basic render pass"), color_attachments: &[Some(RenderPassColorAttachment { view: &view, resolve_target: None, ops: Operations { load: LoadOp::Clear(Color { r: 0.3, g: 0.3, b: 0.3, a: 1.0, }), store: StoreOp::Store, }, })], ..Default::default() }; let mut encoder = self .device .create_command_encoder(&CommandEncoderDescriptor { label: Some("command encoder"), }); let mut sprites_to_render: Vec<(Translation, Shear, Scale, SpriteTexture, Rotation)> = vec![]; let mut things_to_render = self.world.query::<(&Translation, &Shear, &Scale, &SpriteTexture, &Rotation)>(); for thing in things_to_render.iter_mut(&mut self.world) { sprites_to_render.push((*thing.0, *thing.1, *thing.2, thing.3.clone(), *thing.4)); } let cam = self.world.resource::(); let mut frame_uniform = vec![]; let frame_uniform_values = [ cam.zoom, cam.shear_y,0.0, 0.0, cam.shear_x,cam.zoom, 0.0, 0.0, cam.x, cam.y, 1.0, 0.0, self.logical_size.width as f32, self.logical_size.height as f32, 0.0, 0.0]; for i in frame_uniform_values { let mut bytes = i.to_ne_bytes().to_vec(); frame_uniform.append(&mut bytes); } self.queue.write_buffer(&self.frame_uniform, 0, &frame_uniform); for (pos, shear, scale, tex, rot) in sprites_to_render { let tex = self.textures.entry(tex.texture.clone()).or_insert({ info!("loading texture {}", &tex.texture); let assets = self.world.get_resource::().unwrap(); let b = match tex.texture.as_str() { "f" => match assets.get("f.png") { Some(b) => b, None => continue, } "happy-tree" => match assets.get("happy-tree.png") { Some(b) => b, None => continue, } "uv" => match assets.get("uv.png") { Some(b) => b, None => continue, } u => panic!("unknown texture {u}, has it been added in rendering::renderer::::render()?") }; tracing::warn!("{:?}", b); /*let b: &[u8] = match tex.texture.as_str() { "f" => include_bytes!("../textures/f.png"), "happy-tree" => include_bytes!("../textures/happy-tree.png"), "uv" => include_bytes!("../textures/uv.png"), u => panic!("unknown texture {u}, has it been added in rendering::renderer::::render()?") };*/ texture::Texture::new( &b, &tex.texture, &self.device, &self.queue, &mut self.mip_generator, ) }); let xy_matrix = pos.as_matrix(); let shear_matrix = shear.as_matrix(); let rot_matrix = rot.as_matrix(); let scale_matrix = scale.as_matrix(); let transform_matrix = scale_matrix * shear_matrix * rot_matrix * xy_matrix; let mut local_buffer = vec![]; let local_buffer_data = [ transform_matrix.m11, transform_matrix.m12, transform_matrix.m13, 0.0, transform_matrix.m21, transform_matrix.m22, transform_matrix.m23, 0.0, transform_matrix.m31, transform_matrix.m32, transform_matrix.m33, 0.0, ]; for i in local_buffer_data { let mut bytes = i.to_ne_bytes().to_vec(); local_buffer.append(&mut bytes); } self.queue .write_buffer(&self.local_uniform, 0, &local_buffer); let bind_group = self.device.create_bind_group(&BindGroupDescriptor { label: Some("test_bind_group"), layout: &self.sprite_pipeline.get_bind_group_layout(0), entries: &[ BindGroupEntry { binding: 0, resource: BindingResource::Sampler(&tex.sampler), }, BindGroupEntry { binding: 1, resource: BindingResource::TextureView( &tex.texture.create_view(&TextureViewDescriptor::default()), ), }, BindGroupEntry { binding: 2, resource: BindingResource::Buffer( self.frame_uniform.as_entire_buffer_binding(), ), }, BindGroupEntry { binding: 3, resource: BindingResource::Buffer( self.local_uniform.as_entire_buffer_binding(), ), }, ], }); let mut pass = encoder.begin_render_pass(&render_pass_descriptor); pass.set_pipeline(&self.sprite_pipeline); pass.set_bind_group(0, Some(&bind_group), &[]); pass.draw(0..6, 0..1); } // main game rendering done // next up: egui UI rendering for (id, image_delta) in &egui_output.textures_delta.set { self.gui_renderer .update_texture(&self.device, &self.queue, *id, image_delta); } { let paint_jobs = self .gui_ctx .tessellate(egui_output.shapes, self.scale_factor as f32); let screen_descriptor = egui_wgpu::ScreenDescriptor { size_in_pixels: [self.size.width, self.size.height], pixels_per_point: self.scale_factor as f32, }; self.gui_renderer.update_buffers( &self.device, &self.queue, &mut encoder, &paint_jobs, &screen_descriptor, ); let render_pass = encoder.begin_render_pass(&RenderPassDescriptor { label: Some("ui render pass"), color_attachments: &[Some(RenderPassColorAttachment { view: &view, resolve_target: None, ops: Operations { load: LoadOp::Load, store: StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); let mut forgotten_render_pass = render_pass.forget_lifetime(); self.gui_renderer .render(&mut forgotten_render_pass, &paint_jobs, &screen_descriptor); for id in egui_output.textures_delta.free { self.gui_renderer.free_texture(&id); } } let buffer = encoder.finish(); self.queue.submit(std::iter::once(buffer)); output.present(); } }