use crate::model::parse_obj; use bytemuck::bytes_of; use image::EncodableLayout; use std::collections::BTreeMap; use std::sync::Arc; use tobj::LoadOptions; use tracing::info; use wgpu::util::DeviceExt; use wgpu::{Backends, BufferUsages, Device, FragmentState, IndexFormat, Instance, InstanceDescriptor, PipelineCompilationOptions}; use winit::application::ApplicationHandler; use winit::event::{ElementState, MouseButton}; use winit::event_loop::{ActiveEventLoop, EventLoop}; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; use winit::window::{Window, WindowAttributes, WindowId}; use zlog::LogLevel; use zlog::config::LoggerConfig; pub mod camera; pub mod model; pub mod texture; struct WindowContext<'window> { window: Arc, renderer: WgpuRenderer<'window>, } impl std::ops::Deref for WindowContext<'_> { type Target = winit::window::Window; fn deref(&self) -> &Self::Target { self.window.as_ref() } } struct WgpuRenderer<'surface> { device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'surface>, surface_config: wgpu::SurfaceConfiguration, render_pipeline: wgpu::RenderPipeline, vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, diffuse_bind_group: wgpu::BindGroup, depth_texture: wgpu::Texture, depth_texture_view: wgpu::TextureView, camera: camera::Camera, camera_uniform: camera::CameraUniform, camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, index_count: u32, } impl WgpuRenderer<'_> { pub fn draw(&mut self) { let surface_texture = self.surface.get_current_texture().unwrap(); let view = surface_texture.texture.create_view(&Default::default()); let mut encoder = self.device .create_command_encoder(&wgpu::wgt::CommandEncoderDescriptor { label: Some("Render encoder"), }); { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_texture_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, }), occlusion_query_set: None, timestamp_writes: None, }); rpass.set_pipeline(&self.render_pipeline); rpass.set_bind_group(0, &self.diffuse_bind_group, &[]); rpass.set_bind_group(1, &self.camera_bind_group, &[]); rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); rpass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32); rpass.draw_indexed(0..self.index_count, 0, 0..1); } self.queue.submit(Some(encoder.finish())); surface_texture.present() } } struct App<'window> { state: WgpuState, windows: BTreeMap>, } impl App<'_> { fn new() -> Self { Self { state: WgpuState::new(), windows: BTreeMap::new(), } } pub fn spawn_window(&mut self, event_loop: &ActiveEventLoop) { let attr = WindowAttributes::default() .with_title("Zenyx - SubWindow") .with_min_inner_size(winit::dpi::LogicalSize::new(1, 1)); let window = event_loop.create_window(attr).unwrap(); let window = Arc::new(window); let renderer = self.state.create_renderer(window.clone()); let window_ctx = WindowContext { renderer: smol::block_on(renderer), window: window.clone(), }; let window_id = window.id(); self.windows.insert(window_id, window_ctx); } } struct WgpuState { instance: wgpu::Instance, } unsafe impl bytemuck::Pod for Vertex {} unsafe impl bytemuck::Zeroable for Vertex {} #[derive(Copy, Clone, Debug)] #[repr(C)] struct Vertex { position: cgmath::Vector3, color: cgmath::Vector3, normal: cgmath::Vector3, tex_coords: cgmath::Vector2, } impl Vertex { const ATTRIBS: [wgpu::VertexAttribute; 4] = [ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: std::mem::offset_of!(Vertex, color) as u64, shader_location: 1, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: std::mem::offset_of!(Vertex, normal) as u64, shader_location: 2, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: std::mem::offset_of!(Vertex, tex_coords) as u64, shader_location: 3, format: wgpu::VertexFormat::Float32x2, }, ]; const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &Self::ATTRIBS, } } } static ICON: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Badge.png")); static PUMPKIN: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/Pumpkin.obj")); impl WgpuState { fn new() -> Self { let backends = Backends::PRIMARY; let instance_descriptor = InstanceDescriptor { backends, ..Default::default() }; let instance = Instance::new(&instance_descriptor); Self { instance } } async fn create_renderer<'surface>(&self, window: Arc) -> WgpuRenderer<'surface> { let surface = self.instance.create_surface(window.clone()).unwrap(); let adapter = self .instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), ..Default::default() }) .await .unwrap(); let device_descriptor = wgpu::DeviceDescriptor::default(); let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap(); let size = window.inner_size(); let width = size.width.max(1); let height = size.height.max(1); 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, // This should match the filterable field of the // corresponding Texture entry above. ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], label: Some("texture_bind_group_layout"), }); let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], label: Some("Camera Bind group layout"), }); #[repr(align(4))] struct ShaderCode([u8; N]); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Pipeline Layout"), bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], push_constant_ranges: &[], }); let vert_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Vertex Shader"), source: unsafe { static SHADER_CODE: &[u8] = &ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/vert.spv"))).0; // assert!(bytes.len() % 4 == 0); let shader = SHADER_CODE.align_to::().1; wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader)) }, }); let frag_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Fragment Shader"), source: unsafe { static SHADER_CODE: &[u8] = &ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/frag.spv"))).0; debug_assert!(SHADER_CODE.len() % 4 == 0); let shader = SHADER_CODE.align_to::().1; wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader)) }, }); let var_name = [Some(wgpu::ColorTargetState { format: surface.get_capabilities(&adapter).formats[0], blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })]; let pipeline_descriptor = wgpu::RenderPipelineDescriptor { label: Some("Main pipeline"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &vert_shader, entry_point: Some("main"), buffers: &[Vertex::desc()], compilation_options: Default::default(), }, 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: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, fragment: Some(FragmentState { module: &frag_shader, entry_point: Some("main"), compilation_options: PipelineCompilationOptions::default(), targets: &var_name, }), multiview: None, cache: None, }; // todo!(); let surface_caps = surface.get_capabilities(&adapter); let present_mode = if surface_caps .present_modes .contains(&wgpu::PresentMode::Mailbox) { wgpu::PresentMode::Mailbox } else { wgpu::PresentMode::Fifo }; let surface_config = wgpu::SurfaceConfiguration { width, height, format: surface_caps.formats[0], present_mode, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], usage: wgpu::TextureUsages::RENDER_ATTACHMENT, desired_maximum_frame_latency: 3, }; surface.configure(&device, &surface_config); let pumpkin = tobj::load_obj("Pumpkin.obj", &tobj::GPU_LOAD_OPTIONS).unwrap(); let (vertices, indices) = parse_obj(&pumpkin.0); let (depth_texture, depth_texture_view) = create_depth_texture(&device, surface_config.width, surface_config.height); let materials = pumpkin.1.unwrap(); let diffuse_texture = texture::Texture::from_bytes(&device, &queue, ICON, "zenyx-icon").unwrap(); let model = tobj::load_obj_buf( &mut PUMPKIN.as_bytes(), &LoadOptions { triangulate: true, single_index: true, ..Default::default() }, |_| Ok(Default::default()), ) .unwrap(); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex buffer"), contents: bytemuck::cast_slice(vertices.as_ref()), usage: BufferUsages::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Index buffer"), contents: bytemuck::cast_slice(indices.as_ref()), usage: BufferUsages::INDEX, }); let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), }, ], label: Some("diffuse_bind_group"), }); let camera = camera::Camera { // position the camera 1 unit up and 2 units back // +z is out of the screen eye: (0.0, 0.0, 2.0).into(), // have it look at the origin target: (0.0, 0.0, 0.0).into(), // which way is "up" up: cgmath::Vector3::unit_y(), aspect: surface_config.width as f32 / surface_config.height as f32, fovy: 45.0, znear: 0.1, zfar: 1000.0, }; let mut camera_uniform = camera::CameraUniform::default(); camera_uniform.update_view_proj(&camera); let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), contents: bytemuck::cast_slice(&[camera_uniform]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &camera_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: camera_buffer.as_entire_binding(), }], label: Some("Camera Bind Group"), }); let render_pipeline = device.create_render_pipeline(&pipeline_descriptor); WgpuRenderer { surface, surface_config, diffuse_bind_group, depth_texture, render_pipeline, device, vertex_buffer, index_buffer, camera, queue, camera_uniform, camera_buffer, camera_bind_group, index_count: indices.len() as u32, depth_texture_view, } } } fn create_depth_texture( device: &wgpu::Device, width: u32, height: u32, ) -> (wgpu::Texture, wgpu::TextureView) { let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1, }; let desc = wgpu::TextureDescriptor { label: Some("Depth Texture"), size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }; let texture = device.create_texture(&desc); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); (texture, view) } static VERTICES: [Vertex; 4] = { [ Vertex { position: cgmath::vec3(-0.5, -0.5, 0.0), color: cgmath::vec3(1.0, 0.0, 0.0), normal: cgmath::vec3(0.0, 0.0, 0.0), tex_coords: cgmath::vec2(1.0, 0.0), }, Vertex { position: cgmath::vec3(0.5, -0.5, 0.0), color: cgmath::vec3(0.0, 1.0, 0.0), normal: cgmath::vec3(0.0, 0.0, 0.0), tex_coords: cgmath::vec2(0.0, 0.0), }, Vertex { position: cgmath::vec3(0.5, 0.5, 0.0), color: cgmath::vec3(0.0, 0.0, 1.0), normal: cgmath::vec3(0.0, 0.0, 0.0), tex_coords: cgmath::vec2(0.0, 1.0), }, Vertex { position: cgmath::vec3(-0.5, 0.5, 0.0), color: cgmath::vec3(0.0, 0.0, 1.0), normal: cgmath::vec3(0.0, 0.0, 0.0), tex_coords: cgmath::vec2(1.0, 1.0), }, ] }; static INDICIES: [u32; 6] = [0, 1, 2, 2, 3, 0]; impl ApplicationHandler for App<'_> { fn window_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, window_id: WindowId, event: winit::event::WindowEvent, ) { match event { winit::event::WindowEvent::RedrawRequested => { let window_ctx = self.windows.get_mut(&window_id).unwrap(); window_ctx.renderer.draw() } winit::event::WindowEvent::CloseRequested => { let _ = self.windows.remove(&window_id); if self.windows.is_empty() { event_loop.exit(); } } winit::event::WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left && state == ElementState::Pressed { self.spawn_window(event_loop); } } winit::event::WindowEvent::Resized(size) => { if let Some(window_ctx) = self.windows.get_mut(&window_id) { if size.width == 0 || size.height == 0 { return; } window_ctx .renderer .camera .update_aspect(size.width as f32 / size.height as f32); window_ctx .renderer .camera_uniform .update_view_proj(&window_ctx.renderer.camera); window_ctx.renderer.queue.write_buffer( &window_ctx.renderer.camera_buffer, 0, bytes_of(&window_ctx.renderer.camera_uniform), ); let mut new_config = window_ctx.renderer.surface_config.clone(); new_config.width = size.width; new_config.height = size.height; let (depth_texture, depth_view) = create_depth_texture(&window_ctx.renderer.device, size.width, size.height); window_ctx.renderer.depth_texture = depth_texture; window_ctx.renderer.depth_texture_view = depth_view; window_ctx .renderer .surface .configure(&window_ctx.renderer.device, &new_config) } } _ => (), } } fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { let attr = WindowAttributes::default() .with_title("Zenyx") .with_min_inner_size(winit::dpi::LogicalSize::new(1, 1)); if self.windows.is_empty() { let window = event_loop.create_window(attr).unwrap(); let window = Arc::new(window); let renderer = self.state.create_renderer(window.clone()); let window_ctx = WindowContext { renderer: smol::block_on(renderer), window: window.clone(), }; let window_id = window.id(); self.windows.insert(window_id, window_ctx); } } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { self.windows.clear(); } } pub fn main() -> Result<(), terminator::Terminator> { let config = LoggerConfig::default() .colored_stdout(true) .log_to_stdout(true) .file_include_time(true) .log_to_file(true) .level(LogLevel::Info) .log_path("zenyx.log"); let _logger = zlog::Logger::new(config); #[cfg(not(target_os = "android"))] { _main() } #[cfg(target_os = "android")] { Ok(()) } } pub fn run_app(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), terminator::Terminator> { let mut app = App::new(); event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); event_loop.run_app(&mut app)?; Ok(()) } #[unsafe(no_mangle)] #[cfg(target_os = "android")] extern "C" fn android_main(app: AndroidApp) { use winit::event_loop::EventLoopBuilder; use winit::platform::android::EventLoopBuilderExtAndroid; let event_loop = EventLoopBuilder::default() .with_android_app(app) .build() .unwrap(); run_app(event_loop).unwrap() } fn _main() -> Result<(), terminator::Terminator> { let event_loop = EventLoop::new()?; run_app(event_loop)?; info!("Exiting..."); Ok(()) }