diff --git a/assets/skybox/Maskonaive/readme.txt b/assets/skybox/Maskonaive/readme.txt new file mode 100644 index 0000000..d9bd514 --- /dev/null +++ b/assets/skybox/Maskonaive/readme.txt @@ -0,0 +1,13 @@ +Author +====== + +This is the work of Emil Persson, aka Humus. +http://www.humus.name + + + +License +======= + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. +http://creativecommons.org/licenses/by/3.0/ diff --git a/assets/skybox/water_scene/back.jpg b/assets/skybox/water_scene/back.jpg new file mode 100644 index 0000000..470a679 Binary files /dev/null and b/assets/skybox/water_scene/back.jpg differ diff --git a/assets/skybox/water_scene/bottom.jpg b/assets/skybox/water_scene/bottom.jpg new file mode 100644 index 0000000..3780f09 Binary files /dev/null and b/assets/skybox/water_scene/bottom.jpg differ diff --git a/assets/skybox/water_scene/front.jpg b/assets/skybox/water_scene/front.jpg new file mode 100644 index 0000000..4e17b77 Binary files /dev/null and b/assets/skybox/water_scene/front.jpg differ diff --git a/assets/skybox/water_scene/left.jpg b/assets/skybox/water_scene/left.jpg new file mode 100644 index 0000000..5750b91 Binary files /dev/null and b/assets/skybox/water_scene/left.jpg differ diff --git a/assets/skybox/water_scene/right.jpg b/assets/skybox/water_scene/right.jpg new file mode 100644 index 0000000..8963037 Binary files /dev/null and b/assets/skybox/water_scene/right.jpg differ diff --git a/assets/skybox/water_scene/top.jpg b/assets/skybox/water_scene/top.jpg new file mode 100644 index 0000000..9685e15 Binary files /dev/null and b/assets/skybox/water_scene/top.jpg differ diff --git a/src/camera.rs b/src/camera.rs index c107f9c..52c76f8 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -2,32 +2,45 @@ use glam::{Mat4, Vec3}; pub struct Camera { pub position: Vec3, - pub target: Vec3, - pub direction: Vec3, pub front: Vec3, pub up: Vec3, pub yaw: f32, pub pitch: f32, + pub fov: f32, + pub aspect: f32, + pub near: f32, + pub far: f32, } impl Camera { pub fn new( position: Vec3, - target: Vec3, - direction: Vec3, front: Vec3, up: Vec3, yaw: f32, pitch: f32, + fov: f32, + aspect: f32, + near: f32, + far: f32, ) -> Self { Self { position, - target, - direction, front, up, yaw, pitch, + fov, + aspect, + near, + far, } } + + pub fn view_proj(&self) -> Mat4 { + let view = Mat4::look_at_rh(self.position, self.position + self.front, self.up); + let projection = + Mat4::perspective_rh(self.fov.to_radians(), self.aspect, self.near, self.far); + projection * view + } } diff --git a/src/global.rs b/src/global.rs new file mode 100644 index 0000000..9b1a993 --- /dev/null +++ b/src/global.rs @@ -0,0 +1,4 @@ +pub const WINDOW_W: f64 = 800.0; +pub const WINDOW_H: f64 = 600.0; + +pub const GLTF_NAME: &str = "DamagedHelmet"; diff --git a/src/input.rs b/src/input.rs index 2e35e6c..4849f7c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -18,6 +18,7 @@ pub enum Key { C = 8, R = 15, F = 3, + ESC = 53, } impl Key { diff --git a/src/main.rs b/src/main.rs index e54ba6f..26b0921 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,56 +5,37 @@ mod input; mod platform; mod render; mod resource; +mod world; -// TODO: What? -use objc2::AnyThread; -use objc2::runtime::AnyObject; +use std::cell::Cell; use crate::camera::Camera; use crate::input::Key; -use crate::platform::{Delegate, Ivars}; -use crate::render::{Asset, Mesh, RenderPass, SinglePass, Uniforms}; -use crate::resource::{Buffer, BufferKind, Device, ShaderLibrary}; - -use objc2::MainThreadOnly; - -use std::cell::RefCell; +use crate::platform::{AppState, Delegate, Ivars}; +use crate::render::{create_cube_mesh, Mesh, SinglePass, SkyboxPass}; +use crate::resource::{ + Buffer, BufferKind, Device, ShaderLibrary, VertexAttribute, VertexDescriptor, +}; +use crate::world::{World, GLTF_NAME, WINDOW_H, WINDOW_W}; use objc2::rc::Retained; +use objc2::runtime::AnyObject; use objc2::runtime::ProtocolObject; -use objc2::{MainThreadMarker, msg_send}; +use objc2::AnyThread; +use objc2::MainThreadOnly; +use objc2::{msg_send, MainThreadMarker}; +use std::cell::RefCell; use glam::{Mat4, Vec3}; - -use objc2_foundation::{ - NSDate, NSDictionary, NSNumber, NSPoint, NSRect, NSSize, NSString, NSUInteger, NSURL, ns_string, -}; - use objc2_app_kit::{ NSApplication, NSApplicationActivationPolicy, NSBackingStoreType, NSWindow, NSWindowStyleMask, }; - +use objc2_foundation::{ + ns_string, NSDictionary, NSNumber, NSPoint, NSRect, NSSize, NSString, NSUInteger, NSURL, +}; use objc2_metal::*; - use objc2_metal_kit::{MTKTextureLoader, MTKTextureLoaderOptionAllocateMipmaps, MTKView}; -const WINDOW_W: f64 = 800.0; -const WINDOW_H: f64 = 600.0; - -const GLTF_NAME: &str = "Sponza"; - -pub struct AppState { - start_date: Retained, - pub device: Device, - model: Asset, - // RefCell? In frame() an immutable reference to AppState is passed in. - // But camera state needs to mutate when input is pressed - // RefCell allows for mutable borrows at runtime, even when the data is immutable - // Maybe move out of app state - camera: RefCell, - pass: SinglePass, -} - pub fn init() -> (AppState, Retained, Retained) { let mtm = MainThreadMarker::new().unwrap(); @@ -88,7 +69,6 @@ pub fn init() -> (AppState, Retained, Retained) { mtk_view }; - // TODO: move to resource.rs let pipeline_descriptor = MTLRenderPipelineDescriptor::new(); unsafe { pipeline_descriptor @@ -149,7 +129,6 @@ pub fn init() -> (AppState, Retained, Retained) { .blitCommandEncoder() .expect("Failed to create mipmap blit encoder"); - // FIXME: This is kind of horible for mesh in document.meshes() { for primitive in mesh.primitives() { let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); @@ -255,7 +234,7 @@ pub fn init() -> (AppState, Retained, Retained) { let mut all_buffers = Vec::new(); all_buffers.push(buffer); - let model = Mat4::from_rotation_x(f32::to_radians(-15.0)); + let model = Mat4::IDENTITY; let mut materials = Vec::new(); materials.push(texture); @@ -278,66 +257,164 @@ pub fn init() -> (AppState, Retained, Retained) { // TODO: Move to resource module // A MTLVertexDescriptor has attributes and layouts - let vertex_descriptor = MTLVertexDescriptor::new(); - - // Attribute 0: position (float3) at offset 0 in buffer(1) - unsafe { - let pos_attr = vertex_descriptor.attributes().objectAtIndexedSubscript(0); - pos_attr.setFormat(MTLVertexFormat::Float3); - pos_attr.setOffset(0); - pos_attr.setBufferIndex(1); - - let norm_attr = vertex_descriptor.attributes().objectAtIndexedSubscript(1); - norm_attr.setFormat(MTLVertexFormat::Float3); - norm_attr.setOffset(12); - norm_attr.setBufferIndex(1); - - let uv_attr = vertex_descriptor.attributes().objectAtIndexedSubscript(2); - uv_attr.setFormat(MTLVertexFormat::Float2); - uv_attr.setOffset(24); - uv_attr.setBufferIndex(1); - } - - unsafe { - let layout = vertex_descriptor.layouts().objectAtIndexedSubscript(1); - layout.setStride(std::mem::size_of::<[f32; 8]>() as NSUInteger); - layout.setStepFunction(MTLVertexStepFunction::PerVertex); - layout.setStepRate(1); - } + // Mesh vertex descriptor + let vertex_descriptor = VertexDescriptor::new(vec![ + VertexAttribute { + format: MTLVertexFormat::Float3, + offset: 0, + index: 0, + buffer_id: 1, + }, + VertexAttribute { + format: MTLVertexFormat::Float3, + offset: 12, + index: 1, + buffer_id: 1, + }, + VertexAttribute { + format: MTLVertexFormat::Float2, + offset: 24, + index: 2, + buffer_id: 1, + }, + ]); - // Attached vertex spec to pipeline pipeline_descriptor.setVertexDescriptor(Some(&vertex_descriptor)); let pipeline_state = device .newRenderPipelineStateWithDescriptor_error(&pipeline_descriptor) .expect("Failed to create pipeline state"); - let cam_position = Vec3::new(0.0, 10.0, 0.0); - let cam_target = Vec3::new(0.0, 0.0, 0.0); + let cam_position = Vec3::new(0.0, 0.0, 2.0); let camera = Camera::new( cam_position, - cam_target, - Vec3::normalize(cam_position - cam_target), // direction - Vec3::new(0.0, 0.0, -1.0), // front, Looking at -Z - Vec3::new(0.0, 1.0, 0.0), // up - -90.0, // yaw - 0.0, // pitch + Vec3::new(0.0, 0.0, -1.0), // front, Looking at -Z + Vec3::new(0.0, 1.0, 0.0), // up + -90.0, // yaw + 0.0, // pitch + 60.0, // fov + 800.0 / 600.0, // aspect + 0.025, // near + 8000.0, //far + ); + + let pass = SinglePass { + pipeline: pipeline_state, + depth_stencil_state, + }; + + let skybox_shader_lib = ShaderLibrary::new( + String::from("Skybox shader library"), + String::from("./src/shaders/skybox.metallib"), + &device, ); - let pass = SinglePass::new(pipeline_state, depth_stencil_state); + let skybox_vertex_descriptor = VertexDescriptor::new(vec![VertexAttribute { + format: MTLVertexFormat::Float3, + offset: 0, + index: 0, + buffer_id: 1, + }]); + + let skybox_pipeline_descriptor = MTLRenderPipelineDescriptor::new(); + skybox_pipeline_descriptor.setVertexDescriptor(Some(&skybox_vertex_descriptor)); + + unsafe { + skybox_pipeline_descriptor + .colorAttachments() + .objectAtIndexedSubscript(0) + .setPixelFormat(view.colorPixelFormat()); + } + skybox_pipeline_descriptor.setVertexFunction(Some(&skybox_shader_lib.vertex)); + skybox_pipeline_descriptor.setFragmentFunction(Some(&skybox_shader_lib.fragment)); + skybox_pipeline_descriptor.setVertexDescriptor(Some(&skybox_vertex_descriptor)); + skybox_pipeline_descriptor.setDepthAttachmentPixelFormat(MTLPixelFormat::Depth32Float); + + let skybox_pipeline_state = device + .newRenderPipelineStateWithDescriptor_error(&skybox_pipeline_descriptor) + .expect("Failed to create skybox pipeline state"); + + let skybox_depth_descriptor = MTLDepthStencilDescriptor::new(); + skybox_depth_descriptor.setDepthCompareFunction(MTLCompareFunction::LessEqual); + skybox_depth_descriptor.setDepthWriteEnabled(false); // Don't write depth for skybox + let skybox_depth_state = device + .newDepthStencilStateWithDescriptor(&skybox_depth_descriptor) + .expect("Failed to create skybox depth stencil state"); + + // Load cube map textures + let cube_texture = { + let texture_descriptor = unsafe { + MTLTextureDescriptor::textureCubeDescriptorWithPixelFormat_size_mipmapped( + MTLPixelFormat::BGRA8Unorm, + 2048, + false, + ) + }; + texture_descriptor.setUsage(MTLTextureUsage::ShaderRead); + texture_descriptor.setStorageMode(MTLStorageMode::Private); + + let cube_tex = device + .newTextureWithDescriptor(&texture_descriptor) + .expect("Failed to create cube texture"); + + // TODO: CLean up + let face_names = ["left", "right", "top", "bottom", "back", "front"]; + for (slice, name) in face_names.iter().enumerate() { + let texture_path = format!("./assets/skybox/water_scene/{}.jpg", name); + let path = NSString::from_str(&texture_path); + let url = NSURL::fileURLWithPath(&path); + + let temp_texture = unsafe { + mtk_tex_loader + .newTextureWithContentsOfURL_options_error(&url, None) + .expect(&format!("Failed to load skybox texture: {}", name)) + }; + + let skybox_command_buffer = command_queue + .commandBuffer() + .expect("Failed to create blit command buffer"); + let skybox_encoder = skybox_command_buffer + .blitCommandEncoder() + .expect("Failed to create blit encoder"); + + unsafe { + skybox_encoder.copyFromTexture_sourceSlice_sourceLevel_toTexture_destinationSlice_destinationLevel_sliceCount_levelCount( + &temp_texture, + 0, + 0, + &cube_tex, + slice, + 0, + 1, + 1, + ); + } + + skybox_encoder.endEncoding(); + skybox_command_buffer.commit(); + skybox_command_buffer.waitUntilCompleted(); + } + + cube_tex + }; + + let skybox = SkyboxPass { + pipeline: skybox_pipeline_state, + depth_stencil_state: skybox_depth_state, + cube_mesh: create_cube_mesh(&device), + cube_texture, + }; + + let world = World { meshes: all_meshes }; let app_state = AppState { - start_date: NSDate::now(), device: Device { device, command_queue, }, - model: Asset { - meshes: all_meshes, - name: "Box".to_string(), - }, + world: Box::new(world), camera: RefCell::new(camera), - pass, + passes: vec![Box::new(skybox), Box::new(pass)], }; (app_state, window, view) } @@ -345,7 +422,7 @@ pub fn init() -> (AppState, Retained, Retained) { pub fn frame(view: &MTKView, state: &AppState) { let mut camera = state.camera.borrow_mut(); - let move_speed = 4.0; + let move_speed = 0.3; let direction = Vec3::new( f32::cos(f32::to_radians(camera.yaw)) * f32::cos(f32::to_radians(camera.pitch)), @@ -357,8 +434,6 @@ pub fn frame(view: &MTKView, state: &AppState) { let right = front.cross(camera.up).normalize(); let up = camera.up; - // TODO: add a tiny event queue? :) - // if Key::W.is_pressed() { camera.position += front * move_speed; } @@ -394,6 +469,12 @@ pub fn frame(view: &MTKView, state: &AppState) { camera.pitch += pitch_sens; } + if Key::ESC.is_pressed() { + let mtm = MainThreadMarker::new().unwrap(); + let app = NSApplication::sharedApplication(mtm); + app.terminate(None); + } + if camera.pitch > 89.0 { camera.pitch = 89.0; } @@ -414,28 +495,9 @@ pub fn frame(view: &MTKView, state: &AppState) { return; }; - // https://learnopengl.com/Getting-started/Camera - let aspect_ratio = WINDOW_W as f32 / WINDOW_H as f32; - let projection = glam::Mat4::perspective_rh( - f32::to_radians(60.0), - aspect_ratio, - 0.025, // near plane - 8000.0, // far plane - ); - - // Update camera uniform - let view = Mat4::look_at_rh(camera.position, camera.position + camera.front, camera.up); - let view_proj = projection * view; - let time = state.start_date.timeIntervalSinceNow() as f32; - - let model = Mat4::ZERO; - let uniforms = Uniforms { - view_proj, - time, - model, - }; - - state.pass.render(&encoder, &uniforms, &state.model, time); + for pass in &state.passes { + pass.render(&state.world, &camera, &encoder); + } encoder.endEncoding(); command_buffer.presentDrawable(ProtocolObject::from_ref(&*drawable)); diff --git a/src/platform.rs b/src/platform.rs index e0a3afe..b843b92 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -1,9 +1,14 @@ #![deny(unsafe_op_in_unsafe_fn)] -use crate::{AppState, frame, init}; +use crate::{frame, init}; +use std::cell::Cell; use std::ptr; +use crate::camera::Camera; use crate::input::KEYSTATE; +use crate::render::RenderPass; +use crate::resource::Device; +use crate::world::World; use objc2::DefinedClass; @@ -20,6 +25,16 @@ use objc2_metal_kit::{MTKView, MTKViewDelegate}; use block2::RcBlock; +pub struct AppState { + pub device: Device, + // RefCell? In frame() an immutable reference to AppState is passed in. + // But camera state needs to mutate when input is pressed + // RefCell allows for mutable borrows at runtime, even when the data is immutable + // Maybe move out of app state + pub world: Box, + pub camera: RefCell, + pub passes: Vec>, +} pub struct Ivars { pub state: RefCell>, diff --git a/src/render.rs b/src/render.rs index de9d0d6..6991587 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,12 +1,15 @@ -use glam::{Mat4, Vec3}; +use glam::Mat4; use objc2::rc::Retained; use objc2::runtime::ProtocolObject; -use objc2_foundation::{ns_string, NSString, NSUInteger, NSURL}; +use objc2_foundation::NSUInteger; use objc2_metal::*; use std::ptr::NonNull; use crate::resource::{Buffer, BufferKind}; +use crate::camera::Camera; +use crate::world::World; + #[derive(Copy, Clone)] #[repr(C)] pub struct Uniforms { @@ -16,73 +19,100 @@ pub struct Uniforms { } pub trait RenderPass { - // TODO: make Generic fn render( &self, + world: &World, + camera: &Camera, encoder: &ProtocolObject, - uniforms: &Uniforms, - model: &Asset, - time: f32, ); } -// The pass owns the resources pub struct SinglePass { - pipeline: Retained>, - depth_stencil_state: Retained>, -} - -impl SinglePass { - pub fn new( - pipeline: Retained>, - depth_stencil_state: Retained>, - ) -> Self { - Self { - pipeline, - depth_stencil_state, - } - } + pub pipeline: Retained>, + pub depth_stencil_state: Retained>, } impl RenderPass for SinglePass { fn render( &self, + world: &World, + camera: &Camera, encoder: &ProtocolObject, - uniforms: &Uniforms, - model: &Asset, - time: f32, ) { encoder.setRenderPipelineState(&self.pipeline); encoder.setDepthStencilState(Some(&self.depth_stencil_state)); - for mesh in &model.meshes { + let view_proj = camera.view_proj(); + + for mesh in &world.meshes { + let uniforms = Uniforms { + view_proj, + model: mesh.model, + time: 0.0, + }; + unsafe { - // uplaod uniforms - let m_uniforms = Uniforms { - view_proj: uniforms.view_proj, - time: uniforms.time, - model: mesh.model, - }; encoder.setVertexBytes_length_atIndex( - NonNull::from(&m_uniforms).cast(), - std::mem::size_of_val(&m_uniforms), + NonNull::from(&uniforms).cast(), + std::mem::size_of::(), 0, ); } - unsafe { - for material in &mesh.materials { - if let Some(texture) = material { - encoder.setFragmentTexture_atIndex(Some(texture), 0); - } else { - encoder.setFragmentTexture_atIndex(None, 0); - } + + for material in &mesh.materials { + unsafe { + encoder.setFragmentTexture_atIndex(material.as_ref().map(|t| &**t), 0); } } + mesh.draw(encoder); } } } +pub struct SkyboxPass { + pub pipeline: Retained>, + pub depth_stencil_state: Retained>, + pub cube_mesh: Mesh, + pub cube_texture: Retained>, +} + +impl RenderPass for SkyboxPass { + fn render( + &self, + _world: &World, + camera: &Camera, + encoder: &ProtocolObject, + ) { + encoder.setRenderPipelineState(&self.pipeline); + encoder.setDepthStencilState(Some(&self.depth_stencil_state)); + + let view = Mat4::look_at_rh(camera.position, camera.position + camera.front, camera.up); + let (_, rotation, _) = view.to_scale_rotation_translation(); + let view_no_translation = Mat4::from_quat(rotation); + let projection = Mat4::perspective_rh(f32::to_radians(60.0), 800.0 / 600.0, 0.025, 8000.0); + let view_proj = projection * view_no_translation; + + let uniforms = Uniforms { + view_proj, + model: Mat4::IDENTITY, + time: 0.0, + }; + + unsafe { + encoder.setVertexBytes_length_atIndex( + NonNull::from(&uniforms).cast(), + std::mem::size_of::(), + 0, + ); + + encoder.setFragmentTexture_atIndex(Some(&self.cube_texture), 0); + } + + self.cube_mesh.draw(encoder); + } +} + // Mesh, Asset, should be omved somewhere else. leave this file for MTL resources pub struct Mesh { pub buffers: Vec, @@ -133,10 +163,69 @@ impl Mesh { } } -// i.e. glTF -pub struct Asset { - // TODO: constructors - pub meshes: Vec, - // TODO: materials - pub name: String, +pub fn create_cube_mesh(device: &Retained>) -> Mesh { + // Cube vertices (8 corners, size 1.0, centered at origin) + #[rustfmt::skip] + let positions: [f32; 24] = [ + -1.0, 1.0, 1.0, // 0: front-top-left + 1.0, 1.0, 1.0, // 1: front-top-right + 1.0, -1.0, 1.0, // 2: front-bottom-right + -1.0, -1.0, 1.0, // 3: front-bottom-left + -1.0, 1.0, -1.0, // 4: back-top-left + 1.0, 1.0, -1.0, // 5: back-top-right + 1.0, -1.0, -1.0, // 6: back-bottom-right + -1.0, -1.0, -1.0, // 7: back-bottom-left + ]; + + // 36 indices for 12 triangles (2 per face, 6 faces) + // Winding order is set to render from inside the cube + #[rustfmt::skip] + let indices: [u32; 36] = [ + // Front face (Z+) + 0, 1, 2, 0, 2, 3, + // Back face (Z-) + 5, 4, 7, 5, 7, 6, + // Top face (Y+) + 4, 5, 1, 4, 1, 0, + // Bottom face (Y-) + 3, 2, 6, 3, 6, 7, + // Right face (X+) + 1, 5, 6, 1, 6, 2, + // Left face (X-) + 4, 0, 3, 4, 3, 7, + ]; + + let position_buffer = unsafe { + device + .newBufferWithBytes_length_options( + NonNull::new(positions.as_ptr() as *mut _).unwrap(), + std::mem::size_of_val(&positions), + MTLResourceOptions::StorageModeManaged, + ) + .expect("Failed to create position buffer") + }; + + let buffer = Buffer { + buffer: position_buffer, + binding: BufferKind::POSITIONS, + }; + + let index_buffer = unsafe { + device + .newBufferWithBytes_length_options( + NonNull::new(indices.as_ptr() as *mut _).unwrap(), + std::mem::size_of_val(&indices), + MTLResourceOptions::StorageModeManaged, + ) + .expect("Failed to create index buffer") + }; + + Mesh::new( + vec![buffer], + index_buffer, + vec![], + indices.len(), + MTLPrimitiveType::Triangle, + Mat4::IDENTITY, + ) } diff --git a/src/resource.rs b/src/resource.rs index a4920ba..9f17943 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,6 +1,6 @@ use objc2::rc::Retained; use objc2::runtime::ProtocolObject; -use objc2_foundation::{ns_string, NSString, NSUInteger, NSURL}; +use objc2_foundation::{NSString, NSUInteger, NSURL, ns_string}; use objc2_metal::*; pub struct Device { @@ -11,12 +11,10 @@ pub struct Device { #[derive(Copy, Clone)] pub enum BufferKind { POSITIONS = 1, - UV = 2, } pub struct Buffer { pub buffer: Retained>, - // NOTE: bindless coming soon pub binding: BufferKind, } @@ -42,7 +40,7 @@ impl Buffer { pub struct ShaderLibrary { pub vertex: Retained>, pub fragment: Retained>, - name: String, + _name: String, } impl ShaderLibrary { @@ -68,7 +66,59 @@ impl ShaderLibrary { Self { vertex: vertex_fn, fragment: fragment_fn, - name, + _name: name, } } } + +pub struct VertexAttribute { + pub format: MTLVertexFormat, + pub offset: u8, + pub index: u8, + pub buffer_id: u8, +} + +pub struct VertexDescriptor {} + +fn format_size(format: MTLVertexFormat) -> u8 { + match format { + MTLVertexFormat::Float => 4, + MTLVertexFormat::Float2 => 8, + MTLVertexFormat::Float3 => 12, + MTLVertexFormat::Float4 => 16, + _ => panic!("Unhandled vertex format"), + } +} + +impl VertexDescriptor { + pub fn new(attributes: Vec) -> Retained { + let desc = MTLVertexDescriptor::new(); + + let mut stride: u8 = 0; + + unsafe { + for attr in &attributes { + let a = desc + .attributes() + .objectAtIndexedSubscript(attr.index as NSUInteger); + a.setFormat(attr.format); + a.setOffset(attr.offset as NSUInteger); + a.setBufferIndex(attr.buffer_id as NSUInteger); + + let end = attr.offset + format_size(attr.format); + if end > stride { + stride = end; + } + } + } + + unsafe { + let layout = desc.layouts().objectAtIndexedSubscript(1); + layout.setStride(stride as NSUInteger); + layout.setStepFunction(MTLVertexStepFunction::PerVertex); + layout.setStepRate(1); + } + + desc + } +} diff --git a/src/shaders/skybox.metal b/src/shaders/skybox.metal new file mode 100644 index 0000000..914139e --- /dev/null +++ b/src/shaders/skybox.metal @@ -0,0 +1,36 @@ +#include +#include "shadertypes.h" +using namespace metal; + +struct SkyboxUniforms { + float4x4 view_proj; +}; + +struct SkyboxVertexIn { + float3 position [[attribute(0)]]; +}; + +struct SkyboxVSOut { + float4 position [[position]]; + float3 texCoord; +}; + +vertex SkyboxVSOut vertex_main( + SkyboxVertexIn in [[stage_in]], + constant SkyboxUniforms& uniforms [[buffer(BufferKind_Uniforms)]] +) { + SkyboxVSOut out; + // Transform position but use it as direction for cube map sampling + out.position = uniforms.view_proj * float4(in.position, 1.0); + // Use the local position as the cube map direction + out.texCoord = in.position; + return out; +} + +fragment float4 fragment_main( + SkyboxVSOut in [[stage_in]], + texturecube cubeTexture [[texture(0)]] +) { + constexpr sampler cubeSampler(mag_filter::linear, min_filter::linear); + return cubeTexture.sample(cubeSampler, in.texCoord); +} diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..bc3e9ea --- /dev/null +++ b/src/world.rs @@ -0,0 +1,12 @@ +use crate::render::Mesh; + +pub const WINDOW_W: f64 = 800.0; +pub const WINDOW_H: f64 = 600.0; + +pub const GLTF_NAME: &str = "DamagedHelmet"; + +pub struct World { + pub meshes: Vec, + // lights + // transforms +}