2025-04-18 15:53:56 -04:00
|
|
|
use crate::model::parse_obj;
|
2025-04-17 23:04:24 -04:00
|
|
|
use bytemuck::bytes_of;
|
2025-04-18 15:53:56 -04:00
|
|
|
use image::EncodableLayout;
|
2025-04-16 01:24:10 +00:00
|
|
|
use std::collections::BTreeMap;
|
2025-04-17 20:33:28 -04:00
|
|
|
use std::sync::Arc;
|
2025-04-18 15:53:56 -04:00
|
|
|
use tobj::LoadOptions;
|
2025-04-17 20:33:28 -04:00
|
|
|
use tracing::info;
|
|
|
|
use wgpu::util::DeviceExt;
|
2025-04-18 15:53:56 -04:00
|
|
|
use wgpu::{Backends, BufferUsages, Device, FragmentState, IndexFormat, Instance, InstanceDescriptor, PipelineCompilationOptions};
|
2025-04-16 01:24:10 +00:00
|
|
|
use winit::application::ApplicationHandler;
|
2025-04-16 16:52:19 +00:00
|
|
|
use winit::event::{ElementState, MouseButton};
|
|
|
|
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
2025-04-17 23:04:24 -04:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
use winit::platform::android::activity::AndroidApp;
|
2025-04-16 01:24:10 +00:00
|
|
|
use winit::window::{Window, WindowAttributes, WindowId};
|
2025-04-16 16:52:19 +00:00
|
|
|
use zlog::LogLevel;
|
2025-04-17 20:33:28 -04:00
|
|
|
use zlog::config::LoggerConfig;
|
2025-04-18 15:53:56 -04:00
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
pub mod camera;
|
|
|
|
pub mod model;
|
|
|
|
pub mod texture;
|
2025-04-16 01:24:10 +00:00
|
|
|
struct WindowContext<'window> {
|
|
|
|
window: Arc<Window>,
|
|
|
|
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,
|
2025-04-17 20:33:28 -04:00
|
|
|
vertex_buffer: wgpu::Buffer,
|
|
|
|
index_buffer: wgpu::Buffer,
|
|
|
|
diffuse_bind_group: wgpu::BindGroup,
|
2025-04-18 15:53:56 -04:00
|
|
|
|
|
|
|
depth_texture: wgpu::Texture,
|
|
|
|
depth_texture_view: wgpu::TextureView,
|
2025-04-17 20:33:28 -04:00
|
|
|
camera: camera::Camera,
|
|
|
|
camera_uniform: camera::CameraUniform,
|
|
|
|
camera_buffer: wgpu::Buffer,
|
|
|
|
camera_bind_group: wgpu::BindGroup,
|
2025-04-18 15:53:56 -04:00
|
|
|
index_count: u32,
|
2025-04-17 20:33:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2025-04-18 14:22:52 -04:00
|
|
|
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
|
2025-04-17 20:33:28 -04:00
|
|
|
store: wgpu::StoreOp::Store,
|
|
|
|
},
|
|
|
|
})],
|
2025-04-18 15:53:56 -04:00
|
|
|
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,
|
|
|
|
}),
|
2025-04-17 20:33:28 -04:00
|
|
|
occlusion_query_set: None,
|
2025-04-18 15:53:56 -04:00
|
|
|
timestamp_writes: None,
|
2025-04-17 20:33:28 -04:00
|
|
|
});
|
|
|
|
rpass.set_pipeline(&self.render_pipeline);
|
2025-04-18 15:53:56 -04:00
|
|
|
rpass.set_bind_group(0, &self.diffuse_bind_group, &[]);
|
2025-04-17 20:33:28 -04:00
|
|
|
|
|
|
|
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);
|
2025-04-18 15:53:56 -04:00
|
|
|
rpass.draw_indexed(0..self.index_count, 0, 0..1);
|
2025-04-17 20:33:28 -04:00
|
|
|
}
|
|
|
|
self.queue.submit(Some(encoder.finish()));
|
|
|
|
surface_texture.present()
|
|
|
|
}
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct App<'window> {
|
|
|
|
state: WgpuState,
|
|
|
|
windows: BTreeMap<WindowId, WindowContext<'window>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl App<'_> {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
state: WgpuState::new(),
|
|
|
|
windows: BTreeMap::new(),
|
|
|
|
}
|
|
|
|
}
|
2025-04-17 20:33:28 -04:00
|
|
|
|
2025-04-16 16:52:19 +00:00
|
|
|
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);
|
|
|
|
}
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct WgpuState {
|
|
|
|
instance: wgpu::Instance,
|
|
|
|
}
|
2025-04-17 20:33:28 -04:00
|
|
|
unsafe impl bytemuck::Pod for Vertex {}
|
|
|
|
unsafe impl bytemuck::Zeroable for Vertex {}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
2025-04-16 01:24:10 +00:00
|
|
|
#[repr(C)]
|
|
|
|
struct Vertex {
|
2025-04-17 20:33:28 -04:00
|
|
|
position: cgmath::Vector3<f32>,
|
|
|
|
color: cgmath::Vector3<f32>,
|
|
|
|
normal: cgmath::Vector3<f32>,
|
|
|
|
tex_coords: cgmath::Vector2<f32>,
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Vertex {
|
2025-04-17 20:33:28 -04:00
|
|
|
const ATTRIBS: [wgpu::VertexAttribute; 4] = [
|
2025-04-16 01:24:10 +00:00
|
|
|
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,
|
|
|
|
},
|
2025-04-17 20:33:28 -04:00
|
|
|
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,
|
|
|
|
},
|
2025-04-16 01:24:10 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
|
|
|
wgpu::VertexBufferLayout {
|
|
|
|
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
|
|
|
step_mode: wgpu::VertexStepMode::Vertex,
|
|
|
|
attributes: &Self::ATTRIBS,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-04-17 20:33:28 -04:00
|
|
|
static ICON: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Badge.png"));
|
2025-04-18 15:53:56 -04:00
|
|
|
static PUMPKIN: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/Pumpkin.obj"));
|
|
|
|
|
2025-04-16 01:24:10 +00:00
|
|
|
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<Window>) -> 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);
|
2025-04-17 20:33:28 -04:00
|
|
|
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"),
|
|
|
|
});
|
2025-04-16 01:24:10 +00:00
|
|
|
#[repr(align(4))]
|
|
|
|
struct ShaderCode<const N: usize>([u8; N]);
|
|
|
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
|
|
label: Some("Pipeline Layout"),
|
2025-04-17 20:33:28 -04:00
|
|
|
bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout],
|
2025-04-16 01:24:10 +00:00
|
|
|
push_constant_ranges: &[],
|
|
|
|
});
|
|
|
|
|
|
|
|
let vert_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
|
|
label: Some("Vertex Shader"),
|
|
|
|
source: unsafe {
|
2025-04-17 20:33:28 -04:00
|
|
|
static SHADER_CODE: &[u8] =
|
2025-04-16 01:24:10 +00:00
|
|
|
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/vert.spv"))).0;
|
|
|
|
// assert!(bytes.len() % 4 == 0);
|
2025-04-17 20:33:28 -04:00
|
|
|
let shader = SHADER_CODE.align_to::<u32>().1;
|
2025-04-16 01:24:10 +00:00
|
|
|
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
|
|
|
|
},
|
|
|
|
});
|
|
|
|
let frag_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
|
|
label: Some("Fragment Shader"),
|
|
|
|
source: unsafe {
|
2025-04-17 20:33:28 -04:00
|
|
|
static SHADER_CODE: &[u8] =
|
2025-04-16 01:24:10 +00:00
|
|
|
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/frag.spv"))).0;
|
2025-04-17 20:33:28 -04:00
|
|
|
debug_assert!(SHADER_CODE.len() % 4 == 0);
|
|
|
|
let shader = SHADER_CODE.align_to::<u32>().1;
|
2025-04-16 01:24:10 +00:00
|
|
|
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
|
|
|
|
},
|
|
|
|
});
|
|
|
|
let var_name = [Some(wgpu::ColorTargetState {
|
|
|
|
format: surface.get_capabilities(&adapter).formats[0],
|
2025-04-18 14:22:52 -04:00
|
|
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
2025-04-16 01:24:10 +00:00
|
|
|
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,
|
|
|
|
},
|
2025-04-18 15:53:56 -04:00
|
|
|
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(),
|
|
|
|
}),
|
2025-04-16 01:24:10 +00:00
|
|
|
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);
|
2025-04-18 15:53:56 -04:00
|
|
|
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();
|
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
let diffuse_texture =
|
|
|
|
texture::Texture::from_bytes(&device, &queue, ICON, "zenyx-icon").unwrap();
|
2025-04-18 15:53:56 -04:00
|
|
|
let model = tobj::load_obj_buf(
|
|
|
|
&mut PUMPKIN.as_bytes(),
|
|
|
|
&LoadOptions {
|
|
|
|
triangulate: true,
|
|
|
|
single_index: true,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
|_| Ok(Default::default()),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
|
|
label: Some("Vertex buffer"),
|
2025-04-18 15:53:56 -04:00
|
|
|
contents: bytemuck::cast_slice(vertices.as_ref()),
|
2025-04-17 20:33:28 -04:00
|
|
|
usage: BufferUsages::VERTEX,
|
|
|
|
});
|
|
|
|
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
|
|
label: Some("Index buffer"),
|
2025-04-18 15:53:56 -04:00
|
|
|
contents: bytemuck::cast_slice(indices.as_ref()),
|
2025-04-17 20:33:28 -04:00
|
|
|
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,
|
2025-04-18 15:53:56 -04:00
|
|
|
zfar: 1000.0,
|
2025-04-17 20:33:28 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
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"),
|
|
|
|
});
|
|
|
|
|
2025-04-16 01:24:10 +00:00
|
|
|
let render_pipeline = device.create_render_pipeline(&pipeline_descriptor);
|
2025-04-18 15:53:56 -04:00
|
|
|
|
2025-04-16 01:24:10 +00:00
|
|
|
WgpuRenderer {
|
|
|
|
surface,
|
|
|
|
surface_config,
|
2025-04-17 20:33:28 -04:00
|
|
|
diffuse_bind_group,
|
2025-04-18 15:53:56 -04:00
|
|
|
depth_texture,
|
2025-04-16 01:24:10 +00:00
|
|
|
render_pipeline,
|
|
|
|
device,
|
2025-04-17 20:33:28 -04:00
|
|
|
vertex_buffer,
|
|
|
|
index_buffer,
|
|
|
|
camera,
|
2025-04-16 01:24:10 +00:00
|
|
|
queue,
|
2025-04-17 20:33:28 -04:00
|
|
|
camera_uniform,
|
|
|
|
camera_buffer,
|
|
|
|
camera_bind_group,
|
2025-04-18 15:53:56 -04:00
|
|
|
index_count: indices.len() as u32,
|
|
|
|
depth_texture_view,
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
}
|
2025-04-18 15:53:56 -04:00
|
|
|
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
2025-04-18 15:53:56 -04:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
static VERTICES: [Vertex; 4] = {
|
2025-04-16 01:24:10 +00:00
|
|
|
[
|
|
|
|
Vertex {
|
2025-04-17 20:33:28 -04:00
|
|
|
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),
|
2025-04-16 01:24:10 +00:00
|
|
|
},
|
|
|
|
Vertex {
|
2025-04-17 20:33:28 -04:00
|
|
|
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),
|
2025-04-16 01:24:10 +00:00
|
|
|
},
|
|
|
|
Vertex {
|
2025-04-17 20:33:28 -04:00
|
|
|
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),
|
2025-04-16 01:24:10 +00:00
|
|
|
},
|
|
|
|
]
|
2025-04-17 20:33:28 -04:00
|
|
|
};
|
2025-04-16 01:24:10 +00:00
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
static INDICIES: [u32; 6] = [0, 1, 2, 2, 3, 0];
|
2025-04-16 01:24:10 +00:00
|
|
|
|
2025-04-17 20:33:28 -04:00
|
|
|
impl ApplicationHandler for App<'_> {
|
2025-04-16 01:24:10 +00:00
|
|
|
fn window_event(
|
|
|
|
&mut self,
|
|
|
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
|
|
window_id: WindowId,
|
|
|
|
event: winit::event::WindowEvent,
|
|
|
|
) {
|
|
|
|
match event {
|
|
|
|
winit::event::WindowEvent::RedrawRequested => {
|
2025-04-17 20:33:28 -04:00
|
|
|
let window_ctx = self.windows.get_mut(&window_id).unwrap();
|
|
|
|
window_ctx.renderer.draw()
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
winit::event::WindowEvent::CloseRequested => {
|
2025-04-16 16:52:19 +00:00
|
|
|
let _ = self.windows.remove(&window_id);
|
|
|
|
if self.windows.is_empty() {
|
|
|
|
event_loop.exit();
|
|
|
|
}
|
|
|
|
}
|
2025-04-17 20:33:28 -04:00
|
|
|
winit::event::WindowEvent::MouseInput { state, button, .. } => {
|
2025-04-16 16:52:19 +00:00
|
|
|
if button == MouseButton::Left && state == ElementState::Pressed {
|
|
|
|
self.spawn_window(event_loop);
|
|
|
|
}
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
|
|
|
winit::event::WindowEvent::Resized(size) => {
|
2025-04-17 20:33:28 -04:00
|
|
|
if let Some(window_ctx) = self.windows.get_mut(&window_id) {
|
2025-04-16 01:24:10 +00:00
|
|
|
if size.width == 0 || size.height == 0 {
|
|
|
|
return;
|
|
|
|
}
|
2025-04-17 20:33:28 -04:00
|
|
|
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),
|
|
|
|
);
|
2025-04-16 01:24:10 +00:00
|
|
|
let mut new_config = window_ctx.renderer.surface_config.clone();
|
|
|
|
new_config.width = size.width;
|
|
|
|
new_config.height = size.height;
|
2025-04-18 15:53:56 -04:00
|
|
|
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;
|
2025-04-17 20:33:28 -04:00
|
|
|
|
2025-04-16 01:24:10 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2025-04-18 14:06:38 -04:00
|
|
|
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
|
|
|
|
self.windows.clear();
|
|
|
|
}
|
2025-04-16 01:24:10 +00:00
|
|
|
}
|
2025-04-17 23:04:24 -04:00
|
|
|
pub fn main() -> Result<(), terminator::Terminator> {
|
2025-04-15 20:08:58 +00:00
|
|
|
let config = LoggerConfig::default()
|
2025-04-12 21:03:31 -04:00
|
|
|
.colored_stdout(true)
|
2025-04-15 20:08:58 +00:00
|
|
|
.log_to_stdout(true)
|
2025-04-13 16:08:18 -04:00
|
|
|
.file_include_time(true)
|
2025-04-15 20:08:58 +00:00
|
|
|
.log_to_file(true)
|
2025-04-16 16:52:19 +00:00
|
|
|
.level(LogLevel::Info)
|
2025-04-15 20:08:58 +00:00
|
|
|
.log_path("zenyx.log");
|
|
|
|
let _logger = zlog::Logger::new(config);
|
2025-04-17 23:04:24 -04:00
|
|
|
#[cfg(not(target_os = "android"))]
|
|
|
|
{
|
|
|
|
_main()
|
|
|
|
}
|
2025-04-18 14:06:38 -04:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
}
|
2025-04-17 23:04:24 -04:00
|
|
|
}
|
|
|
|
pub fn run_app(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), terminator::Terminator> {
|
2025-04-16 01:24:10 +00:00
|
|
|
let mut app = App::new();
|
2025-04-17 23:04:24 -04:00
|
|
|
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
|
2025-04-16 01:24:10 +00:00
|
|
|
event_loop.run_app(&mut app)?;
|
2025-04-17 23:04:24 -04:00
|
|
|
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)?;
|
2025-04-17 20:33:28 -04:00
|
|
|
info!("Exiting...");
|
2025-04-16 01:24:10 +00:00
|
|
|
Ok(())
|
2025-04-11 20:17:24 -04:00
|
|
|
}
|