diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 984da195..6da58972 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -4,8 +4,6 @@ on: branches: - main pull_request: - branches: - - main jobs: win32: runs-on: self-hosted diff --git a/Cargo.toml b/Cargo.toml index c0d68823..10d89ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,13 @@ ddsfile = "0.5.1" version = "0.9.0" features = ["docking"] +[target.'cfg(target_os = "macos")'.dependencies] +metal = "0.28.0" +winit = "0.29.1" +objc = "0.2.4" +cocoa = "0.25.0" +core-graphics-types = "0.1.3" + [target.'cfg(windows)'.dependencies.windows] version = "0.58.0" features = [ diff --git a/client/main.rs b/client/main.rs index 78df5763..4f8709ae 100644 --- a/client/main.rs +++ b/client/main.rs @@ -5,13 +5,13 @@ use hotline_rs::*; use hotline_rs::client::*; -fn main() -> Result<(), hotline_rs::Error> { - +fn main() -> Result<(), hotline_rs::Error> { + // create client let ctx : Client = Client::create(HotlineInfo { ..Default::default() })?; - + // run if let Err(e) = ctx.run() { println!("error: {}", e.msg); diff --git a/examples/bindful/main.rs b/examples/bindful/main.rs index 35fd6675..6a1133a5 100644 --- a/examples/bindful/main.rs +++ b/examples/bindful/main.rs @@ -1,7 +1,4 @@ -// currently windows only because here we need a concrete gfx and os implementation -#![cfg(target_os = "windows")] - -use hotline_rs::{*, prelude::*}; +use hotline_rs::{*, prelude::{Pipeline, Texture}}; use os::{App, Window}; use gfx::{CmdBuf, Device, SwapChain, RenderPass}; @@ -113,7 +110,7 @@ fn main() -> Result<(), hotline_rs::Error> { let mut pmfx : pmfx::Pmfx = pmfx::Pmfx::create(&mut dev, 0); pmfx.load(&hotline_rs::get_data_path("shaders/bindful"))?; pmfx.create_render_pipeline(&dev, "bindful", swap_chain.get_backbuffer_pass())?; - + let fmt = swap_chain.get_backbuffer_pass().get_format_hash(); let pso_pmfx = pmfx.get_render_pipeline_for_format("bindful", fmt)?; @@ -199,7 +196,7 @@ fn main() -> Result<(), hotline_rs::Error> { }; cmdbuffer.push_render_constants(c0.index, 2, 0, gfx::as_u8_slice(&pc)); } - + cmdbuffer.set_index_buffer(&index_buffer); cmdbuffer.set_vertex_buffer(&vertex_buffer, 0); cmdbuffer.draw_indexed_instanced(6, 1, 0, 0, 0); diff --git a/examples/bindless/main.rs b/examples/bindless/main.rs index 753cb7fe..a80b8bb0 100644 --- a/examples/bindless/main.rs +++ b/examples/bindless/main.rs @@ -1,7 +1,5 @@ -// currently windows only because here we need a concrete gfx and os implementation -#![cfg(target_os = "windows")] - -use hotline_rs::{*, prelude::*}; +use hotline_rs::*; +use hotline_rs::prelude::*; use os::{App, Window}; use gfx::{CmdBuf, Device, SwapChain, RenderPass}; @@ -116,7 +114,7 @@ fn main() -> Result<(), hotline_rs::Error> { pmfx.load(&hotline_rs::get_data_path("shaders/bindless"))?; pmfx.create_compute_pipeline(&dev, "compute_rw")?; pmfx.create_render_pipeline(&dev, "bindless", swap_chain.get_backbuffer_pass())?; - + let fmt = swap_chain.get_backbuffer_pass().get_format_hash(); let pso_pmfx = pmfx.get_render_pipeline_for_format("bindless", fmt)?; let pso_compute = pmfx.get_compute_pipeline("compute_rw")?; @@ -260,7 +258,7 @@ fn main() -> Result<(), hotline_rs::Error> { cmdbuffer.set_scissor_rect(&scissor); cmdbuffer.set_render_pipeline(pso_pmfx); - + cmdbuffer.set_heap(pso_pmfx, dev.get_shader_heap()); cmdbuffer.set_index_buffer(&index_buffer); diff --git a/examples/imgui_demo/main.rs b/examples/imgui_demo/main.rs index 8c3f327b..11741794 100644 --- a/examples/imgui_demo/main.rs +++ b/examples/imgui_demo/main.rs @@ -1,12 +1,8 @@ -// currently windows only because here we need a concrete gfx and os implementation -#![cfg(target_os = "windows")] - use hotline_rs::*; use os::{App, Window}; use gfx::{CmdBuf, Device, SwapChain}; - fn main() -> Result<(), hotline_rs::Error> { // app let mut app = os_platform::App::create(os::AppInfo { @@ -67,6 +63,7 @@ fn main() -> Result<(), hotline_rs::Error> { filepath: font_path, glyph_ranges: None }], + monitors: app.enumerate_display_monitors() }; let mut imgui = imgui::ImGui::create(&mut imgui_info).unwrap(); @@ -118,6 +115,6 @@ fn main() -> Result<(), hotline_rs::Error> { // resources now no longer in use they can be properly cleaned up dev.cleanup_dropped_resources(&swap_chain); - + Ok(()) } diff --git a/examples/play_video/main.rs b/examples/play_video/main.rs index 581970d8..7b18717f 100644 --- a/examples/play_video/main.rs +++ b/examples/play_video/main.rs @@ -1,7 +1,5 @@ -// currently windows only because here we need a concrete gfx and os implementation -#![cfg(target_os = "windows")] - use hotline_rs::prelude::*; +use hotline_rs::*; fn main() -> Result<(), hotline_rs::Error> { // app @@ -53,7 +51,7 @@ fn main() -> Result<(), hotline_rs::Error> { fonts: vec![ imgui::FontInfo{ filepath: hotline_rs::get_data_path("fonts/roboto_medium.ttf"), - glyph_ranges: None + glyph_ranges: None }, imgui::FontInfo{ filepath: hotline_rs::get_data_path("fonts/font_awesome.ttf"), @@ -62,6 +60,7 @@ fn main() -> Result<(), hotline_rs::Error> { ]) } ], + monitors: app.enumerate_display_monitors() }; let mut imgui = imgui::ImGui::create(&mut imgui_info).unwrap(); diff --git a/examples/raytraced_triangle/main.rs b/examples/raytraced_triangle/main.rs index 3eac7282..e2c1bd44 100644 --- a/examples/raytraced_triangle/main.rs +++ b/examples/raytraced_triangle/main.rs @@ -15,10 +15,6 @@ use gfx::SwapChain; use os::App; use os::Window; -#[cfg(target_os = "windows")] -use os::win32 as os_platform; -use gfx::d3d12 as gfx_platform; - /// Create an rw texture output for raytracing to write into fn create_raytracing_output(device: &mut gfx_platform::Device, window_rect: &os::Rect) -> gfx_platform::Texture { let rw_info = gfx::TextureInfo { @@ -96,7 +92,7 @@ fn main() -> Result<(), hotline_rs::Error> { -0.25, 0.25, 1.0, 0.25, 0.25, 1.0 ]; - + let vertex_buffer = device.create_buffer(&gfx::BufferInfo { usage: BufferUsage::UPLOAD, cpu_access: gfx::CpuAccessFlags::WRITE, @@ -127,8 +123,8 @@ fn main() -> Result<(), hotline_rs::Error> { let tlas = device.create_raytracing_tlas(&RaytracingTLASInfo { instances: &vec![RaytracingInstanceInfo { transform: [ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 ], instance_id: 0, @@ -163,7 +159,7 @@ fn main() -> Result<(), hotline_rs::Error> { let raytracing_pipeline = pmfx.get_raytracing_pipeline("raytracing")?; cmd.set_raytracing_pipeline(&raytracing_pipeline.pipeline); - + // bind rw tex on u0 let uav0 = raytracing_output.get_uav_index().expect("expect raytracing_output to have a uav"); if let Some(u0) = raytracing_pipeline.pipeline.get_pipeline_slot(0, 0, gfx::DescriptorType::UnorderedAccess) { @@ -175,14 +171,14 @@ fn main() -> Result<(), hotline_rs::Error> { let aspect = window_rect.width as f32 / window_rect.height as f32; cmd.push_compute_constants(0, 8, 0, gfx::as_u8_slice(&[ // viewport - -1.0 + border, + -1.0 + border, -1.0 + border * aspect, - 1.0 - border, + 1.0 - border, 1.0 - border * aspect, // scissor - -1.0 + border / aspect, + -1.0 + border / aspect, -1.0 + border, - 1.0 - border / aspect, + 1.0 - border / aspect, 1.0 - border ])); diff --git a/examples/resource_tests/main.rs b/examples/resource_tests/main.rs index 02527278..4f8a6018 100644 --- a/examples/resource_tests/main.rs +++ b/examples/resource_tests/main.rs @@ -1,6 +1,3 @@ -// currently windows only because here we need a concrete gfx and os implementation -#![cfg(target_os = "windows")] - use hotline_rs::{*, prelude::{Pipeline, Texture}}; use os::{App, Window}; @@ -111,11 +108,11 @@ fn main() -> Result<(), hotline_rs::Error> { // create resources that are defined in resource_tests.pmfx pmfx.create_texture(&mut dev, "bear_frame")?; pmfx.create_texture(&mut dev, "copy_dest")?; - + pmfx.create_texture(&mut dev, "compressed_bc1")?; pmfx.create_texture(&mut dev, "compressed_bc5")?; pmfx.create_texture(&mut dev, "compressed_bc3")?; - + let fmt = swap_chain.get_backbuffer_pass().get_format_hash(); let pso_pmfx = pmfx.get_render_pipeline_for_format("bindful", fmt)?; @@ -141,7 +138,7 @@ fn main() -> Result<(), hotline_rs::Error> { let viewport = gfx::Viewport::from(vp_rect); let scissor = gfx::ScissorRect::from(vp_rect); - // copy texture region + // copy texture region cmdbuffer.transition_barrier(&gfx::TransitionBarrier { texture: Some(textures[1]), buffer: None, @@ -161,8 +158,8 @@ fn main() -> Result<(), hotline_rs::Error> { 0, 0, 0, 0, textures[0], Some(gfx::Region{ - left: 380, - top: 380, + left: 380, + top: 380, front: 0, right: 380 + 512, bottom: 380 + 512, @@ -227,7 +224,7 @@ fn main() -> Result<(), hotline_rs::Error> { if let Some(t3) = pso_pmfx.get_pipeline_slot(3, 0, gfx::DescriptorType::ShaderResource) { cmdbuffer.set_binding(pso_pmfx, &pmfx.shader_heap, t3.index, srv3); } - + cmdbuffer.set_index_buffer(&index_buffer); cmdbuffer.set_vertex_buffer(&vertex_buffer, 0); cmdbuffer.draw_indexed_instanced(6, 1, 0, 0, 0); diff --git a/examples/swap_chain/main.rs b/examples/swap_chain/main.rs index 90ec536c..87b0e76a 100644 --- a/examples/swap_chain/main.rs +++ b/examples/swap_chain/main.rs @@ -11,18 +11,6 @@ use maths_rs::cos; use gfx::Device; -#[cfg(target_os = "windows")] -use os::win32 as os_platform; - -#[cfg(target_os = "windows")] -use gfx::d3d12 as gfx_platform; - -#[cfg(target_os = "macos")] -use os::macos as os_platform; - -#[cfg(target_os = "macos")] -use gfx::mtl as gfx_platform; - fn main() -> Result<(), hotline_rs::Error> { // create an app println!("create app!"); diff --git a/examples/triangle/main.rs b/examples/triangle/main.rs index 8e8a2268..88b57b41 100644 --- a/examples/triangle/main.rs +++ b/examples/triangle/main.rs @@ -9,10 +9,6 @@ use os::Window; use std::fs; -#[cfg(target_os = "windows")] -use os::win32 as os_platform; -use gfx::d3d12 as gfx_platform; - #[repr(C)] struct Vertex { position: [f32; 3], @@ -78,7 +74,7 @@ fn main() -> Result<(), hotline_rs::Error> { initial_state: gfx::ResourceState::VertexConstantBuffer }; - let vertex_buffer = device.create_buffer(&info, Some(gfx::as_u8_slice(&vertices)))?; + let vertex_buffer = device.create_buffer(&info, Some(gfx::as_u8_slice(&vertices)))?; let vsc_filepath = hotline_rs::get_data_path("shaders/triangle/vs_main.vsc"); let psc_filepath = hotline_rs::get_data_path("shaders/triangle/ps_main.psc"); @@ -91,7 +87,7 @@ fn main() -> Result<(), hotline_rs::Error> { compile_info: None }; let vs = device.create_shader(&vsc_info, &vsc_data)?; - + let psc_info = gfx::ShaderInfo { shader_type: gfx::ShaderType::Vertex, compile_info: None diff --git a/src/client.rs b/src/client.rs index cbf4077e..75c0fd50 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,5 @@ +#![cfg(target_os = "windows")] + use crate::gfx; use crate::gfx::ReadBackRequest; use crate::os; @@ -42,7 +44,7 @@ pub struct HotlineInfo { /// Number of buffers in the swap chain (2 for double buffered, 3 for tripple etc) pub num_buffers: u32, /// Size of the default device heap for shader resources (textures, buffers, etc) - pub shader_heap_size: usize, + pub shader_heap_size: usize, /// Size of the default device heap for render targets pub render_target_heap_size: usize, /// Size of the default device heap for depth stencil targets @@ -169,7 +171,7 @@ enum PluginState { Unload, } -/// Container data describing a plugin +/// Container data describing a plugin struct PluginCollection { name: String, reloader: reloader::Reloader, @@ -195,10 +197,10 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx plugins: None } }; - + // override by the supplied user config let user_config = info.user_config.unwrap_or(saved_user_config); - + // app let mut app = A::create(os::AppInfo { name: info.name.to_string(), @@ -209,7 +211,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx if let Some(console_rect) = user_config.console_window_rect { app.set_console_window_rect(console_rect); } - + // device let mut device = D::create(&gfx::DeviceInfo { adapter_name: info.adapter_name, @@ -217,7 +219,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx render_target_heap_size: info.render_target_heap_size, depth_stencil_heap_size: info.depth_stencil_heap_size, }); - + // main window let main_window = app.create_window(os::WindowInfo { title: info.name.to_string(), @@ -225,7 +227,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx style: os::WindowStyleFlags::NONE, parent_handle: None, }); - + // swap chain let swap_chain_info = gfx::SwapChainInfo { num_buffers: info.num_buffers, @@ -241,7 +243,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx }; let imdraw : imdraw::ImDraw = imdraw::ImDraw::create(&imdraw_info).unwrap(); - // imgui + // imgui let mut imgui_info = imgui::ImGuiInfo:: { device: &mut device, swap_chain: &mut swap_chain, @@ -257,7 +259,8 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx [font_awesome::MINIMUM_CODEPOINT as u32, font_awesome::MAXIMUM_CODEPOINT as u32] ]) } - ] + ], + monitors: app.enumerate_display_monitors() }; let imgui = imgui::ImGui::create(&mut imgui_info)?; @@ -303,7 +306,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx client.add_plugin_lib(name, &info.path) } } - + Ok(client) } @@ -315,7 +318,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx if let Ok(elapsed) = elapsed { // raw delta each frame self.time.raw_delta = elapsed.as_secs_f32(); - + // track delta if !self.time.delta_paused { self.time.delta = elapsed.as_secs_f32() * self.time.time_scale; @@ -328,7 +331,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx if self.delta_history.len() > SMOOTH_DELTA_FRAMES { self.delta_history.pop_back(); } - + // calculate smooth delta let sum : f32 = self.delta_history.iter().sum(); self.time.smooth_delta = sum / self.delta_history.len() as f32; @@ -362,7 +365,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // check for focus on the dock let dock_input = self.imgui.main_dock_hovered(); self.app.set_input_enabled( - !self.imgui.want_capture_keyboard() || dock_input, + !self.imgui.want_capture_keyboard() || dock_input, !self.imgui.want_capture_mouse() || dock_input); let size = self.main_window.get_size(); @@ -395,12 +398,12 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx std::fs::File::create(&user_config_path).unwrap(); std::fs::write(&user_config_path, user_config_file_text).unwrap(); } - + /// internal function to manage tracking user config values and changes, writes to disk if change are detected fn update_user_config_windows(&mut self) { // track any changes and write once let mut invalidated = false; - + // main window pos / size let current = self.main_window.get_window_rect(); if current.x > 0 && current.y > 0 && self.user_config.main_window_rect != current { @@ -442,14 +445,14 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx state_before: gfx::ResourceState::Present, state_after: gfx::ResourceState::RenderTarget, }); - + // clear window self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_mut()); self.cmd_buf.end_render_pass(); // blit self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_no_clear()); - + // get srv index of the pmfx target to blit to the window, if the target exists if let Some(tex) = self.pmfx.get_texture(blit_view_name) { // blit to main window @@ -465,9 +468,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx self.cmd_buf.set_render_pipeline(pipeline); self.cmd_buf.push_render_constants(0, 2, 0, &[vp_rect.width as f32, vp_rect.height as f32]); - + self.cmd_buf.set_binding(pipeline, heap, 1, srv); - + self.cmd_buf.set_index_buffer(&self.unit_quad_mesh.ib); self.cmd_buf.set_vertex_buffer(&self.unit_quad_mesh.vb, 0); self.cmd_buf.draw_indexed_instanced(6, 1, 0, 0, 0); @@ -480,19 +483,19 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // render imgui self.cmd_buf.begin_event(0xff1fb6c4, "imgui"); - + self.imgui.render( - &mut self.app, - &mut self.main_window, - &mut self.device, + &mut self.app, + &mut self.main_window, + &mut self.device, &mut self.cmd_buf, &image_heaps ); - + self.cmd_buf.end_event(); self.cmd_buf.end_render_pass(); - + // transition to present self.cmd_buf.transition_barrier(&gfx::TransitionBarrier { texture: Some(self.swap_chain.get_backbuffer_texture()), @@ -522,7 +525,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx .join("target") .join(crate::get_config_name()) .to_str().unwrap().to_string(); - + let src_path = PathBuf::from(abs_path.to_string()) .join(name) .join("src") @@ -548,7 +551,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx unsafe { // create instance if it is a Plugin trait let create = lib.get_symbol:: *mut core::ffi::c_void>("create".as_bytes()); - + let instance = if let Ok(create) = create { // create function returns pointer to instance create() @@ -557,11 +560,11 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // allow null instances, in plugins which only export function calls and not plugin traits std::ptr::null_mut() }; - + // keep hold of everything for updating self.plugins.push( PluginCollection { name: name.to_string(), - instance, + instance, reloader: Reloader::create(Box::new(plugin)), state: PluginState::Setup }); @@ -588,9 +591,9 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx /// Intenral core-ui function, it displays the main menu bar in the main window and /// A plugin menu which allows users to reload or unload live plugins. fn core_ui(&mut self) { - // main menu bar + // main menu bar if self.imgui.begin_main_menu_bar() { - + if self.imgui.begin_menu("File") { // allow us to add plugins from files (libs) if self.imgui.menu_item("Open") { @@ -688,7 +691,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx // time scaling self.imgui.input_float("Time Scale", &mut self.time.time_scale); - + self.imgui.end_menu(); } @@ -701,7 +704,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx let cpu_ms = self.time.raw_delta * 1000.0; let gpu_ms = self.pmfx.get_total_stats().gpu_time_ms; self.imgui.text( - &format!("fps: {} | cpu: {:.2}(ms) | gpu: {:.2}(ms)", + &format!("fps: {} | cpu: {:.2}(ms) | gpu: {:.2}(ms)", fps, cpu_ms, gpu_ms )); self.imgui.same_line(); @@ -768,7 +771,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx if reload { for plugin in &mut plugins { if plugin.state == PluginState::None { - plugin.state = PluginState::Setup + plugin.state = PluginState::Setup } } } @@ -804,10 +807,10 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx break; } } - + // reload, actual reloading the lib of any libs which had changes for plugin in &mut plugins { - if plugin.state == PluginState::Reload { + if plugin.state == PluginState::Reload { // wait for lib reloader itself let lib = self.libs.get_mut(&plugin.name).expect("hotline::client: lib missing for plugin"); let start = SystemTime::now(); @@ -849,7 +852,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx } } } - + // update if !self.time.paused { for plugin in &mut plugins { @@ -919,11 +922,11 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx T::default() } } - + /// Very simple run loop which can take control of your application, you could roll your own pub fn run(mut self) -> Result<(), super::Error> { while self.app.run() { - + self.new_frame()?; self.core_ui(); @@ -968,19 +971,19 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx pub fn run_once(mut self) -> Result<(), super::Error> { for i in 0..3 { self.new_frame()?; - + self.core_ui(); self.pmfx.show_ui(&mut self.imgui, true); - + self = self.update_plugins(); - + if let Some(tex) = self.pmfx.get_texture("main_colour") { self.imgui.image_window("main_dock", tex); } - + // execute pmfx command buffers first self.pmfx.execute(&mut self.device); - + // main pass self.cmd_buf.transition_barrier(&gfx::TransitionBarrier { texture: Some(self.swap_chain.get_backbuffer_texture()), @@ -988,33 +991,33 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx state_before: gfx::ResourceState::Present, state_after: gfx::ResourceState::RenderTarget, }); - + // clear window self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_mut()); self.cmd_buf.end_render_pass(); - + // blit self.cmd_buf.begin_render_pass(self.swap_chain.get_backbuffer_pass_no_clear()); - + // render imgui self.cmd_buf.begin_event(0xff1fb6c4, "imgui"); let image_heaps = vec![ &self.pmfx.shader_heap ]; - + self.imgui.render( - &mut self.app, - &mut self.main_window, - &mut self.device, + &mut self.app, + &mut self.main_window, + &mut self.device, &mut self.cmd_buf, &image_heaps ); - + self.cmd_buf.end_event(); - + self.cmd_buf.end_render_pass(); - + // transition to present self.cmd_buf.transition_barrier(&gfx::TransitionBarrier { texture: Some(self.swap_chain.get_backbuffer_texture()), @@ -1022,11 +1025,11 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx state_before: gfx::ResourceState::RenderTarget, state_after: gfx::ResourceState::Present, }); - + let readback_request = self.cmd_buf.read_back_backbuffer(&self.swap_chain)?; - + self.cmd_buf.close().unwrap(); - + // execute the main window command buffer + swap self.device.execute(&self.cmd_buf); self.swap_chain.swap(&self.device); @@ -1040,15 +1043,15 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx read_start: 0, read_end: usize::MAX })?; - + let output_dir = "target/test_output"; if !std::path::PathBuf::from(output_dir.to_string()).exists() { std::fs::create_dir(output_dir)?; } - + let output_filepath = format!("{}/{}.png", output_dir, &self.instance_name); image::write_to_file_from_gpu(&output_filepath, &data)?; - + readback_request.unmap(); } @@ -1058,7 +1061,7 @@ impl Client where D: gfx::Device, A: os::App, D::RenderPipeline: gfx } self.swap_chain.wait_for_last_frame(); - + // unloads plugins, dropping all gpu resources self.unload(); diff --git a/src/gfx/mtl.rs b/src/gfx/mtl.rs new file mode 100644 index 00000000..c3ab3da5 --- /dev/null +++ b/src/gfx/mtl.rs @@ -0,0 +1,1576 @@ +#![cfg(target_os = "macos")] + +use crate::os_platform; +use crate::os::Window; + +use bevy_ecs::system::lifetimeless::Read; +use cocoa::foundation::NSUInteger; +use metal::MTLScissorRect; +use metal::MTLStepFunction; +use metal::MTLTextureUsage; +use metal::MTLVertexFormat; +use metal::MTLViewport; +use metal::TextureDescriptor; +use metal::VertexAttributeDescriptorArray; + +use super::*; +use super::Device as SuperDevice; +use super::ReadBackRequest as SuperReadBackRequest; +use super::Heap as SuperHeap; +use super::Pipeline as SuperPipleline; + +use std::alloc::Layout; +use std::collections::HashMap; +use std::result; + +use cocoa::{appkit::NSView, base::id as cocoa_id}; +use core_graphics_types::geometry::CGSize; + +use std::path::Path; + +const MEGA_BYTE : usize = 1024 * 1024 * 1024; + +const fn to_mtl_vertex_format(format: super::Format) -> MTLVertexFormat { + match format { + super::Format::Unknown => MTLVertexFormat::Invalid, + super::Format::R16n => MTLVertexFormat::ShortNormalized, + super::Format::R16u => MTLVertexFormat::UShort, + super::Format::R16i => MTLVertexFormat::Short, + super::Format::R16f => MTLVertexFormat::Half, + super::Format::R32u => MTLVertexFormat::UInt, + super::Format::R32i => MTLVertexFormat::Int, + super::Format::R32f => MTLVertexFormat::Float, + super::Format::RG16u => MTLVertexFormat::UShort2, + super::Format::RG16i => MTLVertexFormat::Short2, + super::Format::RG16f => MTLVertexFormat::Half2, + super::Format::RG32u => MTLVertexFormat::UInt2, + super::Format::RG32i => MTLVertexFormat::Int2, + super::Format::RG32f => MTLVertexFormat::Float2, + super::Format::RGB32u => MTLVertexFormat::UInt3, + super::Format::RGB32i => MTLVertexFormat::Int3, + super::Format::RGB32f => MTLVertexFormat::Float3, + super::Format::RGBA8n => MTLVertexFormat::Char4Normalized, + super::Format::RGBA8u => MTLVertexFormat::UChar4, + super::Format::RGBA8i => MTLVertexFormat::Char4, + super::Format::RGBA16u => MTLVertexFormat::UShort4, + super::Format::RGBA16i => MTLVertexFormat::Short4, + super::Format::RGBA16f => MTLVertexFormat::Half4, + super::Format::RGBA32u => MTLVertexFormat::UInt4, + super::Format::RGBA32i => MTLVertexFormat::Int4, + super::Format::RGBA32f => MTLVertexFormat::Float4, + _ => panic!("hotline_rs::gfx::mtl unsupported vertex format") + } +} + +fn to_mtl_texture_usage(usage: TextureUsage) -> MTLTextureUsage { + let mut mtl_usage : MTLTextureUsage = MTLTextureUsage::Unknown; + if usage.contains(super::TextureUsage::SHADER_RESOURCE) { + mtl_usage.insert(MTLTextureUsage::ShaderRead); + } + if usage.contains(super::TextureUsage::UNORDERED_ACCESS) { + mtl_usage.insert(MTLTextureUsage::ShaderWrite); + } + if usage.contains(super::TextureUsage::RENDER_TARGET) { + mtl_usage.insert(MTLTextureUsage::RenderTarget); + } + if usage.contains(super::TextureUsage::RENDER_TARGET) || + usage.contains(super::TextureUsage::DEPTH_STENCIL) { + mtl_usage.insert(MTLTextureUsage::RenderTarget); + } + mtl_usage +} + +#[derive(Clone)] +pub struct Device { + metal_device: metal::Device, + command_queue: metal::CommandQueue, + shader_heap: Heap, + adapter_info: AdapterInfo +} + +#[derive(Clone)] +pub struct SwapChain { + layer: metal::MetalLayer, + drawable: metal::MetalDrawable, + view: *mut objc::runtime::Object, + backbuffer_clear: Option, + backbuffer_texture: Texture, + backbuffer_pass: RenderPass, + backbuffer_pass_no_clear: RenderPass, +} + +impl super::SwapChain for SwapChain { + fn new_frame(&mut self) { + } + + fn wait_for_last_frame(&self) { + } + + fn get_num_buffers(&self) -> u32 { + 3 + } + + fn get_frame_fence_value(&self) -> u64 { + 0 + } + + fn update(&mut self, device: &mut Device, window: &A::Window, cmd: &mut CmdBuf) -> bool { + objc::rc::autoreleasepool(|| { + let draw_size = window.get_size(); + self.layer.set_drawable_size(CGSize::new(draw_size.x as f64, draw_size.y as f64)); + + let drawable = self.layer.next_drawable() + .expect("hotline_rs::gfx::mtl failed to get next drawable to create swap chain!"); + + self.drawable = drawable.to_owned(); + + self.backbuffer_texture = Texture { + metal_texture: drawable.texture().to_owned(), + srv_index: None, + heap_id: None + }; + + self.backbuffer_pass = device.create_render_pass_for_swap_chain(&self.backbuffer_texture, self.backbuffer_clear); + self.backbuffer_pass_no_clear = device.create_render_pass_for_swap_chain(&self.backbuffer_texture, None); + }); + + // TODO: check usage + true + } + + fn get_backbuffer_index(&self) -> u32 { + 0 + } + + fn get_backbuffer_texture(&self) -> &Texture { + &self.backbuffer_texture + } + + fn get_backbuffer_pass(&self) -> &RenderPass { + &self.backbuffer_pass + } + + fn get_backbuffer_pass_mut(&mut self) -> &mut RenderPass { + &mut self.backbuffer_pass + } + + fn get_backbuffer_pass_no_clear(&self) -> &RenderPass { + &self.backbuffer_pass_no_clear + } + + fn get_backbuffer_pass_no_clear_mut(&mut self) -> &mut RenderPass { + &mut self.backbuffer_pass_no_clear + } + + fn swap(&mut self, device: &Device) { + objc::rc::autoreleasepool(|| { + let cmd = device.command_queue.new_command_buffer(); + cmd.present_drawable(&self.drawable); + cmd.commit(); + }); + } +} + +#[derive(Clone)] +pub struct CmdBuf { + cmd_queue: metal::CommandQueue, + cmd: Option, + render_encoder: Option, + compute_encoder: Option, + bound_index_buffer: Option, + bound_index_stride: usize +} + +impl super::CmdBuf for CmdBuf { + fn reset(&mut self, swap_chain: &SwapChain) { + objc::rc::autoreleasepool(|| { + self.cmd = Some(self.cmd_queue.new_command_buffer().to_owned()); + }); + } + + fn close(&mut self) -> result::Result<(), super::Error> { + objc::rc::autoreleasepool(|| { + self.cmd.as_ref().expect("hotline_rs::gfx::mtl expected call to CmdBuf::reset before close").commit(); + self.cmd = None; + Ok(()) + }) + } + + fn get_backbuffer_index(&self) -> u32 { + 0 + } + + fn begin_render_pass(&mut self, render_pass: &RenderPass) { + objc::rc::autoreleasepool(|| { + // catch double begin + assert!(self.render_encoder.is_none(), + "hotline_rs::gfx::mtl begin_render_pass called without matching CmdBuf::end_render_pass"); + + // catch mismatched close/reset + let render_encoder = self.cmd.as_ref() + .expect("hotline_rs::gfx::mtl expected call to CmdBuf::reset after close") + .new_render_command_encoder(&render_pass.desc).to_owned(); + + // new encoder + self.render_encoder = Some(render_encoder); + }); + } + + fn end_render_pass(&mut self) { + objc::rc::autoreleasepool(|| { + self.render_encoder.as_ref() + .expect("hotline_rs::gfx::mtl end_render_pass called without matching begin") + .end_encoding(); + self.render_encoder = None; + }); + } + + fn begin_event(&mut self, colour: u32, name: &str) { + } + + fn end_event(&mut self) { + } + + fn timestamp_query(&mut self, heap: &mut QueryHeap, resolve_buffer: &mut Buffer) { + } + + fn begin_query(&mut self, heap: &mut QueryHeap, query_type: QueryType) -> usize { + 0 + } + + fn end_query(&mut self, heap: &mut QueryHeap, query_type: QueryType, index: usize, resolve_buffer: &mut Buffer) { + } + + fn transition_barrier(&mut self, barrier: &TransitionBarrier) { + } + + fn transition_barrier_subresource(&mut self, barrier: &TransitionBarrier, subresource: Subresource) { + } + + fn uav_barrier(&mut self, resource: UavResource) { + unimplemented!() + } + + fn set_viewport(&self, viewport: &super::Viewport) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .set_viewport(MTLViewport { + originX: viewport.x as f64, + originY: viewport.y as f64, + width: viewport.width as f64, + height: viewport.height as f64, + znear: viewport.min_depth as f64, + zfar: viewport.max_depth as f64, + }); + }); + } + + fn set_scissor_rect(&self, scissor_rect: &super::ScissorRect) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .set_scissor_rect(MTLScissorRect { + x: scissor_rect.left as u64, + y: scissor_rect.top as u64, + width: (scissor_rect.right - scissor_rect.left) as u64, + height: (scissor_rect.bottom - scissor_rect.top) as u64, + }); + }); + } + + fn set_vertex_buffer(&self, buffer: &Buffer, slot: u32) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .set_vertex_buffer(slot as NSUInteger, Some(&buffer.metal_buffer), 0); + }); + } + + fn set_index_buffer(&mut self, buffer: &Buffer) { + self.bound_index_buffer = Some(buffer.metal_buffer.clone()); + self.bound_index_stride = buffer.element_stride; + } + + fn set_render_pipeline(&self, pipeline: &RenderPipeline) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .set_render_pipeline_state(&pipeline.pipeline_state); + + // bind static samplers + for sampler in &pipeline.static_samplers { + self.render_encoder.as_ref().unwrap().set_fragment_sampler_state( + sampler.slot as u64, Some(&sampler.sampler)) + } + }); + } + + fn set_compute_pipeline(&self, pipeline: &ComputePipeline) { + + } + + fn set_raytracing_pipeline(&self, pipeline: &RaytracingPipeline) { + unimplemented!() + } + + fn set_heap(&self, pipeline: &T, heap: &Heap) { + + } + + fn set_heap_render(&self, pipeline: &RenderPipeline, heap: &Heap) { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .use_heap_at(&heap.mtl_heap, metal::MTLRenderStages::Fragment); + + + pipeline.fragment_descriptor_slots.iter().enumerate().for_each(|(slot_index, slot)| { + if let Some(slot) = slot { + slot.argument_encoder.set_argument_buffer(&slot.argument_buffer, 0); + + // TODO: need to know data types (Texture, Buffer) + // assign textures to slots + heap.texture_slots.iter().enumerate().for_each(|(index, texture)| { + if let Some(texture) = texture { + slot.argument_encoder.set_texture(index as u64, texture); + } + }); + + // TODO: need to know what stages to bind on + self.render_encoder.as_ref().unwrap().set_fragment_buffer(slot_index as u64, Some(&slot.argument_buffer), 0); + } + }); + + /* + pipeline.vertex_descriptor_slots.iter().enumerate().for_each(|(slot_index, slot)| { + if let Some(slot) = slot { + slot.argument_encoder.set_argument_buffer(&slot.argument_buffer, 0); + + // TODO: need to know data types (Texture, Buffer) + // assign textures to slots + heap.texture_slots.iter().enumerate().for_each(|(index, texture)| { + slot.argument_encoder.set_texture(index as u64, texture); + }); + + // TODO: need to know what stages to bind on + self.render_encoder.as_ref().unwrap().set_vertex_buffer(slot_index as u64, Some(&slot.argument_buffer), 0); + } + }); + */ + } + + // TODO: needs stage + fn set_binding(&self, pipeline: &T, heap: &Heap, slot: u32, offset: usize) { + let rp : &RenderPipeline = unsafe { std::mem::transmute(pipeline) }; + if rp.fragment_descriptor_slots.len() > 0 { + if let Some(d) = rp.fragment_descriptor_slots[0].as_ref() { + d.argument_encoder.set_argument_buffer(&d.argument_buffer, 0); + if let Some(texture) = heap.texture_slots[offset].as_ref() { + d.argument_encoder.set_texture(slot as u64, &texture); + } + } + } + } + + fn set_texture(&mut self, texture: &Texture, slot: u32) { + self.render_encoder.as_ref().unwrap().set_fragment_texture(slot as u64, Some(&texture.metal_texture)); + } + + fn set_marker(&mut self, colour: u32, name: &str) { + } + + fn push_render_constants(&self, slot: u32, num_values: u32, dest_offset: u32, data: &[T]) { + // TODO: need to know the stages and the buffer offset + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + // .set_fragment_bytes(slot as u64 + 2, num_values as u64 * 4, data.as_ptr() as _); + // .set_vertex_bytes(slot as u64 + 2, num_values as u64 * 4, data.as_ptr() as _); + .set_vertex_bytes(1, num_values as u64 * 4, data.as_ptr() as _); + } + + fn push_compute_constants(&self, slot: u32, num_values: u32, dest_offset: u32, data: &[T]) { + } + + fn draw_instanced( + &self, + vertex_count: u32, + instance_count: u32, + start_vertex: u32, + start_instance: u32, + ) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .draw_primitives_instanced_base_instance( + metal::MTLPrimitiveType::TriangleStrip, + start_vertex as u64, + vertex_count as u64, + instance_count as u64, + start_instance as u64 + ); + }); + } + + fn draw_indexed_instanced( + &self, + index_count: u32, + instance_count: u32, + start_index: u32, + base_vertex: i32, + start_instance: u32, + ) { + objc::rc::autoreleasepool(|| { + self.render_encoder + .as_ref() + .expect("hotline_rs::gfx::metal expected a call to begin render pass before using render commands") + .draw_indexed_primitives_instanced_base_instance( + metal::MTLPrimitiveType::TriangleStrip, + index_count as u64, + metal::MTLIndexType::UInt16, + &self.bound_index_buffer.as_ref().unwrap(), + start_index as u64 * self.bound_index_stride as u64, + instance_count as u64, + base_vertex as i64, + start_instance as u64 + ); + }) + } + + fn dispatch(&self, group_count: Size3, _numthreads: Size3) { + } + + fn execute_indirect( + &self, + command: &CommandSignature, + max_command_count: u32, + argument_buffer: &Buffer, + argument_buffer_offset: usize, + counter_buffer: Option<&Buffer>, + counter_buffer_offset: usize + ) { + } + + fn read_back_backbuffer(&mut self, swap_chain: &SwapChain) -> result::Result { + Ok(ReadBackRequest { + + }) + } + + fn resolve_texture_subresource(&self, texture: &Texture, subresource: u32) -> result::Result<(), super::Error> { + Ok(()) + } + + fn generate_mip_maps(&mut self, texture: &Texture, device: &Device, heap: &Heap) -> result::Result<(), super::Error> { + Ok(()) + } + + fn copy_buffer_region( + &mut self, + dst_buffer: &Buffer, + dst_offset: usize, + src_buffer: &Buffer, + src_offset: usize, + num_bytes: usize + ) { + } + + fn copy_texture_region( + &mut self, + dst_texture: &Texture, + subresource_index: u32, + dst_x: u32, + dst_y: u32, + dst_z: u32, + src_texture: &Texture, + src_region: Option + ) { + } + + fn dispatch_rays(&self, sbt: &RaytracingShaderBindingTable, numthreads: Size3) { + unimplemented!() + } + + fn update_raytracing_tlas(&mut self, tlas: &RaytracingTLAS, instance_buffer: &Buffer, instance_count: usize, mode: AccelerationStructureRebuildMode) { + unimplemented!() + } +} + +pub struct Buffer { + metal_buffer: metal::Buffer, + element_stride: usize +} + +impl super::Buffer for Buffer { + fn update(&mut self, offset: usize, data: &[T]) -> result::Result<(), super::Error> { + unsafe { + let data_ptr = self.metal_buffer.contents(); + std::ptr::copy_nonoverlapping(data.as_ptr() as *mut u8, data_ptr as *mut u8, data.len()); + } + Ok(()) + } + + fn write(&mut self, offset: usize, data: &[T]) -> result::Result<(), super::Error> { + Ok(()) + } + + fn get_cbv_index(&self) -> Option { + None + } + + fn get_srv_index(&self) -> Option { + None + } + + fn get_uav_index(&self) -> Option { + None + } + + fn get_vbv(&self) -> Option { + None + } + + fn get_ibv(&self) -> Option { + None + } + + fn get_counter_offset(&self) -> Option { + None + } + + fn map(&mut self, info: &MapInfo) -> *mut u8 { + std::ptr::null_mut() + } + + fn unmap(&mut self, info: &UnmapInfo) { + } +} + +pub struct Shader { + lib: metal::Library, + data: *const u8, + data_size: usize +} + +impl super::Shader for Shader {} + +struct MetalSamplerBinding { + slot: u32, + sampler: metal::SamplerState +} + +#[derive(Clone)] +pub struct DescriptorMember { + offset: u32, + num: u32, + info: PipelineSlotInfo +} +type DescriptorMemberArray = Vec>; + +#[derive(Clone)] +pub struct DescriptorSlot { + argument_buffer: metal::Buffer, + argument_encoder: metal::ArgumentEncoder, + members: Vec>, +} +type DescriptorSlotArray = Vec>; + +pub struct PushConstantSlot { + buffer: metal::Buffer, + slot: u32, + visibility: ShaderVisibility +} +pub struct RenderPipeline { + pipeline_state: metal::RenderPipelineState, + static_samplers: Vec, + slots: Vec, + vertex_descriptor_slots: DescriptorSlotArray, + fragment_descriptor_slots: DescriptorSlotArray, + vertex_push_constant_slots: Vec, + fragment_push_constant_slots: Vec +} + +impl super::RenderPipeline for RenderPipeline {} + +impl super::Pipeline for RenderPipeline { + fn get_pipeline_slot(&self, register: u32, space: u32, descriptor_type: DescriptorType) -> Option<&super::PipelineSlotInfo> { + if (space as usize) < self.fragment_descriptor_slots.len() { + if let Some(set) = self.fragment_descriptor_slots[space as usize].as_ref() { + if (register as usize) < set.members.len() { + if let Some(member) = set.members[(register as usize)].as_ref() { + Some(&member.info) + } + else { + None + } + } + else { + None + } + } + else { + None + } + } + else { + None + } + } + + fn get_pipeline_slots(&self) -> &Vec { + &self.slots + } + + fn get_pipeline_type() -> PipelineType { + super::PipelineType::Render + } +} + +#[derive(Clone)] +pub struct Texture { + metal_texture: metal::Texture, + srv_index: Option, + heap_id: Option +} + +impl super::Texture for Texture { + fn get_srv_index(&self) -> Option { + self.srv_index + } + + fn get_subresource_uav_index(&self, subresource: u32) -> Option { + None + } + + fn get_msaa_srv_index(&self) -> Option { + None + } + + fn get_uav_index(&self) -> Option { + None + } + + fn clone_inner(&self) -> Texture { + Texture { + metal_texture: self.metal_texture.clone(), + srv_index: self.srv_index, + heap_id: self.heap_id + } + } + + fn is_resolvable(&self) -> bool { + false + } + + fn get_shader_heap_id(&self) -> Option { + self.heap_id + } +} + +#[derive(Clone)] +pub struct Sampler { + mtl_sampler: metal::SamplerState +} + +pub struct ReadBackRequest { + +} + +impl super::ReadBackRequest for ReadBackRequest { + fn is_complete(&self, swap_chain: &SwapChain) -> bool { + false + } + + fn map(&self, info: &MapInfo) -> result::Result { + Err(super::Error { + msg: format!( + "not implemented", + ), + }) + } + + fn unmap(&self) { + } +} + +#[derive(Clone)] +pub struct RenderPass { + desc: metal::RenderPassDescriptor +} + +impl super::RenderPass for RenderPass { + fn get_format_hash(&self) -> u64 { + 0 + } +} + +pub struct ComputePipeline { + slots: Vec +} + +impl super::Pipeline for ComputePipeline { + fn get_pipeline_slot(&self, register: u32, space: u32, descriptor_type: DescriptorType) -> Option<&super::PipelineSlotInfo> { + None + } + + fn get_pipeline_slots(&self) -> &Vec { + &self.slots + } + + fn get_pipeline_type() -> PipelineType { + super::PipelineType::Compute + } +} + +#[derive(Clone)] +enum HeapResourceType { + None, + Texture, + Buffer +} + +#[derive(Clone)] +pub struct Heap { + mtl_heap: metal::Heap, + texture_slots: Vec>, + buffer_slots: Vec>, + resource_type: Vec, + offset: usize, + id: u16 +} + +impl Heap { + fn allocate(&mut self) -> usize { + let srv = self.offset; + self.offset += 1; + unsafe { + self.texture_slots.resize(self.offset, None); + self.buffer_slots.resize(self.offset, None); + } + self.resource_type.resize(self.offset, HeapResourceType::None); + srv + } +} + +impl super::Heap for Heap { + fn deallocate(&mut self, index: usize) { + + } + + fn cleanup_dropped_resources(&mut self, swap_chain: &SwapChain) { + } + + fn get_heap_id(&self) -> u16 { + 0 + } +} + +pub struct QueryHeap { + +} + +impl super::QueryHeap for QueryHeap { + fn reset(&mut self) { + } +} + +pub struct CommandSignature { + +} + +pub struct RaytracingPipeline { + +} + +pub struct RaytracingShaderBindingTable { + +} + +pub struct RaytracingBLAS { + +} + +pub struct RaytracingTLAS { + +} + +impl Device { + fn create_render_pass_for_swap_chain( + &self, + texture: &Texture, + clear_col: Option + ) -> RenderPass { + objc::rc::autoreleasepool(|| { + self.create_render_pass(&RenderPassInfo { + render_targets: vec![texture], + rt_clear: clear_col, + depth_stencil: None, + ds_clear: None, + resolve: false, + discard: false, + array_slice: 0 + }).unwrap() + }) + } + + fn create_heap_mtl(mtl_device: &metal::Device, info: &HeapInfo, id: u16) -> Heap { + // hmm? + let texture_descriptor = TextureDescriptor::new(); + texture_descriptor.set_width(512); + texture_descriptor.set_height(512); + texture_descriptor.set_depth(1); + texture_descriptor.set_texture_type(metal::MTLTextureType::D2); + texture_descriptor.set_pixel_format(metal::MTLPixelFormat::RGBA8Unorm); + texture_descriptor.set_storage_mode(metal::MTLStorageMode::Shared); + + // Determine the size required for the heap for the given descriptor + let size_and_align = mtl_device.heap_texture_size_and_align(&texture_descriptor); + let texture_size = align_pow2(size_and_align.size, size_and_align.align); + + let heap_size = texture_size * info.num_descriptors.max(1) as u64; + + let heap_descriptor = metal::HeapDescriptor::new(); + heap_descriptor.set_storage_mode(metal::MTLStorageMode::Shared); + heap_descriptor.set_size(heap_size); + + let heap = mtl_device.new_heap(&heap_descriptor); + + Heap { + mtl_heap: heap, + texture_slots: Vec::new(), + buffer_slots: Vec::new(), + resource_type: Vec::new(), + offset: 0, + id + } + } + + fn to_mtl_descriptor_slot(&self, visibility: super::ShaderVisibility, pipeline_bindings: &Option>) -> DescriptorSlotArray { + // argument buffer to descriptor slot style + let mut descriptor_slots : DescriptorSlotArray = Vec::new(); + + // register spaces, and shader registers may not be ordered and may not be sequential or have gaps + if let Some(bindings) = pipeline_bindings.as_ref() { + // make space for enough shader register spaces + let mut space_count = 0; + for binding in bindings.iter().filter(|b| b.visibility == visibility || b.visibility == ShaderVisibility::All) { + space_count = binding.register_space.max(space_count); + } + descriptor_slots.resize((space_count + 1) as usize, None); + + // iterate over descriptor slots and find members + descriptor_slots.iter_mut().enumerate().for_each(|(space, descriptor_slot)| { + let mut members : DescriptorMemberArray = Vec::new(); + for binding in bindings.iter().filter(|b| b.visibility == visibility || b.visibility == ShaderVisibility::All) { + if binding.register_space == space as u32 { + if members.len() < (binding.shader_register + 1) as usize { + members.resize((binding.shader_register + 1) as usize, None); + } + + // get num + let num = if let Some(num) = binding.num_descriptors { + num + } + else { + 1 + }; + + // assign member info + members[binding.shader_register as usize] = Some( + DescriptorMember { + offset: 0, + num: num, + info: PipelineSlotInfo { + index: binding.shader_register, + count: binding.num_descriptors + } + } + ); + } + } + + // now work out the offsets of the members within the space + let mut offset = 0; + for member in &mut members { + if let Some(member) = member { + member.offset = offset; + offset += member.num; + } + } + + // finally if we have members and not an empty space + // create an argument buffer + if members.len() > 0 { + let mut member_descriptors = Vec::new(); + + let mut total_num = 0; + for member in &members { + if let Some(member) = member { + let descriptor = metal::ArgumentDescriptor::new(); + descriptor.set_index(member.offset as u64); + descriptor.set_array_length(member.num as u64); + + // TODO: types / access + descriptor.set_data_type(metal::MTLDataType::Texture); + descriptor.set_access(metal::MTLArgumentAccess::ReadOnly); + + // push metal argument descriptor + member_descriptors.push(descriptor.to_owned()); + + total_num += member.num; + } + } + + // create encoder and argument buffer + let argument_encoder = self.metal_device.new_argument_encoder(metal::Array::from_owned_slice(member_descriptors.as_slice())); + let argument_buffer_size = argument_encoder.encoded_length() * total_num as u64; + let argument_buffer = self.metal_device.new_buffer(argument_buffer_size, metal::MTLResourceOptions::empty()); + + *descriptor_slot = Some( + DescriptorSlot { + argument_encoder, + argument_buffer, + members + } + ) + } + }); + } + + descriptor_slots + } + + fn to_mtl_push_constant_slot(&self, visibility: super::ShaderVisibility, pipeline_push_constants: &Option>, binding_offset: u32) -> Vec { + let mut push_constant_slots : Vec = Vec::new(); + if let Some(push_constants) = pipeline_push_constants.as_ref() { + push_constants.iter().filter(|b| b.visibility == visibility || b.visibility == ShaderVisibility::All).enumerate().for_each(|(index, push_constant)| { + push_constant_slots.push(PushConstantSlot{ + buffer: self.metal_device.new_buffer(push_constant.num_values as u64 * 4, metal::MTLResourceOptions::StorageModeShared), + slot: binding_offset + index as u32, + visibility: ShaderVisibility::All + }) + }); + } + push_constant_slots + } +} + +impl super::Device for Device { + type SwapChain = SwapChain; + type CmdBuf = CmdBuf; + type Buffer = Buffer; + type Shader = Shader; + type RenderPipeline = RenderPipeline; + type Texture = Texture; + type ReadBackRequest = ReadBackRequest; + type RenderPass = RenderPass; + type ComputePipeline = ComputePipeline; + type Heap = Heap; + type QueryHeap = QueryHeap; + type CommandSignature = CommandSignature; + type RaytracingPipeline = RaytracingPipeline; + type RaytracingShaderBindingTable = RaytracingShaderBindingTable; + type RaytracingBLAS = RaytracingBLAS; + type RaytracingTLAS = RaytracingTLAS; + + fn create(info: &super::DeviceInfo) -> Device { + objc::rc::autoreleasepool(|| { + let device = metal::Device::system_default() + .expect("hotline_rs::gfx::mtl: failed to create metal device"); + let command_queue = device.new_command_queue(); + + // adapter info + let adapter_info = AdapterInfo { + name: device.name().to_string(), + description: "Metal".to_string(), + dedicated_video_memory: device.recommended_max_working_set_size() as usize, + dedicated_system_memory: 0, + shared_system_memory: 0, + available: vec![device.name().to_string()] + }; + + // feature info + let tier = device.argument_buffers_support(); + assert_eq!(metal::MTLArgumentBuffersTier::Tier2, tier); //TODO: message + + Device { + command_queue: command_queue, + shader_heap: Self::create_heap_mtl(&device, &HeapInfo{ + heap_type: HeapType::Shader, + num_descriptors: info.shader_heap_size, + debug_name: Some("mtl device: shader heap".to_string()) + }, 1), + adapter_info: adapter_info, + metal_device: device + } + }) + } + + fn get_feature_flags(&self) -> &DeviceFeatureFlags { + unimplemented!() + } + + fn create_heap(&mut self, info: &HeapInfo) -> Heap { + Self::create_heap_mtl(&self.metal_device, &info, 2) + } + + fn create_query_heap(&self, info: &QueryHeapInfo) -> QueryHeap { + QueryHeap { + + } + } + + fn create_swap_chain( + &mut self, + info: &super::SwapChainInfo, + win: &A::Window, + ) -> result::Result { + unsafe { + objc::rc::autoreleasepool(|| { + // layer + let layer = metal::MetalLayer::new(); + layer.set_device(&self.metal_device); + layer.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm); + layer.set_presents_with_transaction(false); + + // view + let macos_win = std::mem::transmute::<&A::Window, &os::macos::Window>(win); + let view = os::macos::nsview_from_window(macos_win); + view.setWantsLayer(objc::runtime::YES); + view.setLayer(std::mem::transmute(layer.as_ref())); + + let draw_size = win.get_size(); + layer.set_drawable_size(CGSize::new(draw_size.x as f64, draw_size.y as f64)); + + let drawable = layer.next_drawable() + .expect("hotline_rs::gfx::mtl failed to get next drawable to create swap chain!"); + + let backbuffer_texture = Texture { + metal_texture: drawable.texture().to_owned(), + srv_index: None, + heap_id: None + }; + let render_pass = self.create_render_pass_for_swap_chain(&backbuffer_texture, info.clear_colour); + let render_pass_no_clear = self.create_render_pass_for_swap_chain(&backbuffer_texture, None); + + // create swap chain object + Ok(SwapChain { + layer: layer.clone(), + view: view, + drawable: drawable.to_owned(), + backbuffer_clear: info.clear_colour, + backbuffer_texture: backbuffer_texture, + backbuffer_pass: render_pass, + backbuffer_pass_no_clear: render_pass_no_clear, + }) + }) + } + } + + fn create_cmd_buf(&self, num_buffers: u32) -> CmdBuf { + objc::rc::autoreleasepool(|| { + CmdBuf { + cmd_queue: self.command_queue.clone(), + cmd: None, + render_encoder: None, + compute_encoder: None, + bound_index_buffer: None, + bound_index_stride: 0 + } + }) + } + + fn create_render_pipeline( + &self, + info: &super::RenderPipelineInfo, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let pipeline_state_descriptor = metal::RenderPipelineDescriptor::new(); + + if let Some(vs) = info.vs { + unsafe { + let lib = self.metal_device.new_library_with_data(std::slice::from_raw_parts(vs.data, vs.data_size))?; + let vvs = lib.get_function("vs_main", None).unwrap(); + pipeline_state_descriptor.set_vertex_function(Some(&vvs)); + } + }; + if let Some(fs) = info.fs { + unsafe { + let lib = self.metal_device.new_library_with_data(std::slice::from_raw_parts(fs.data, fs.data_size))?; + let pps = lib.get_function("ps_main", None).unwrap(); + pipeline_state_descriptor.set_fragment_function(Some(&pps)); + } + }; + + // vertex attribs + let vertex_desc = metal::VertexDescriptor::new(); + let mut attrib_index = 0; + + // make spaces for slots to calculate the stride from offsets + size + let mut slot_strides = Vec::new(); + for element in &info.input_layout { + if slot_strides.len() < (element.input_slot + 1) as usize { + slot_strides.resize((element.input_slot + 1) as usize, 0); + } + } + + // make the idividual attributes and track the stride of each slot + for element in &info.input_layout { + let attribute = metal::VertexAttributeDescriptor::new(); + attribute.set_format(to_mtl_vertex_format(element.format)); + attribute.set_buffer_index(element.input_slot as NSUInteger); + attribute.set_offset(element.aligned_byte_offset as NSUInteger); + vertex_desc.attributes().set_object_at(attrib_index, Some(&attribute)); + attrib_index += 1; + + let stride = element.aligned_byte_offset + block_size_for_format(element.format); + slot_strides[element.input_slot as usize] = max(slot_strides[element.input_slot as usize], stride); + } + + // vertex layouts; TODO: work out MTLVertexStepFunction + let layout_desc = metal::VertexBufferLayoutDescriptor::new(); + layout_desc.set_step_function(metal::MTLVertexStepFunction::PerVertex); + layout_desc.set_stride(slot_strides[0] as NSUInteger); + vertex_desc.layouts().set_object_at(0, Some(&layout_desc)); + + pipeline_state_descriptor.set_vertex_descriptor(Some(&vertex_desc)); + + // TODO: attachments + let attachment = pipeline_state_descriptor + .color_attachments() + .object_at(0) + .unwrap(); + attachment.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm); + attachment.set_blending_enabled(false); + attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add); + attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add); + attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha); + attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::SourceAlpha); + attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); + attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); + + // TODO: depth stencil + + // TODO: raster + + // TODO: samplers? + let mut pipeline_static_samplers = Vec::new(); + if let Some(static_samplers) = &info.pipeline_layout.static_samplers { + for sampler in static_samplers { + let desc = metal::SamplerDescriptor::new(); + desc.set_address_mode_r(metal::MTLSamplerAddressMode::Repeat); + desc.set_address_mode_s(metal::MTLSamplerAddressMode::Repeat); + desc.set_address_mode_t(metal::MTLSamplerAddressMode::Repeat); + desc.set_min_filter(metal::MTLSamplerMinMagFilter::Linear); + desc.set_mag_filter(metal::MTLSamplerMinMagFilter::Linear); + desc.set_mip_filter(metal::MTLSamplerMipFilter::Linear); + desc.set_support_argument_buffers(true); + + pipeline_static_samplers.push(MetalSamplerBinding { + slot: sampler.shader_register, + sampler: self.metal_device.new_sampler(&desc) + }) + } + } + + let vertex_descriptor_slots = self.to_mtl_descriptor_slot(ShaderVisibility::Vertex, &info.pipeline_layout.bindings); + let fragment_descriptor_slots = self.to_mtl_descriptor_slot(ShaderVisibility::Fragment, &info.pipeline_layout.bindings); + let vertex_push_constant_slots = self.to_mtl_push_constant_slot(ShaderVisibility::Vertex, &info.pipeline_layout.push_constants, vertex_descriptor_slots.len() as u32); + let fragment_push_constant_slots = self.to_mtl_push_constant_slot(ShaderVisibility::Fragment, &info.pipeline_layout.push_constants, fragment_descriptor_slots.len() as u32); + + let pipeline_state = self.metal_device.new_render_pipeline_state(&pipeline_state_descriptor)?; + + Ok(RenderPipeline { + pipeline_state, + slots: Vec::new(), + static_samplers: pipeline_static_samplers, + fragment_descriptor_slots, + vertex_descriptor_slots, + vertex_push_constant_slots, + fragment_push_constant_slots + }) + }) + } + + fn create_shader( + &self, + info: &super::ShaderInfo, + src: &[T], + ) -> std::result::Result { + objc::rc::autoreleasepool(|| { + + let (data, data_size) = unsafe { + let src = slice_as_u8_slice(src); + let data = std::alloc::alloc(Layout::from_size_align(src.len() + 1, 8)?); + std::ptr::write_bytes(data, 0x0, src.len() + 1); + std::ptr::copy_nonoverlapping(src.as_ptr(), data, src.len()); + (data, src.len()) + }; + + let lib = if let Some(compile_info) = info.compile_info.as_ref() { + + let u8slice = slice_as_u8_slice(src); + println!("{:?}", u8slice); + + let src = std::str::from_utf8(u8slice)?; + println!("{:?}", src); + + self.metal_device.new_library_with_file(std::path::Path::new(src))? + + /* + let src = std::str::from_utf8(slice_as_u8_slice(src))?; + let opt = metal::CompileOptions::new(); + opt.set_fast_math_enabled(true); + self.metal_device.new_library_with_source(src, &opt)? + */ + } + else { + unsafe { + self.metal_device.new_library_with_data(std::slice::from_raw_parts(data, data_size))? + } + }; + + let names = lib.function_names(); + if names.len() == 1 { + Ok(Shader{ + lib: lib.to_owned(), + data: data as *const u8, + data_size: data_size + }) + } + else { + Err(super::Error { + msg: format!( + "hotline_rs::gfx::mtl expected a shader with single entry point but shader has {} functions", names.len() + ), + }) + } + }) + } + + fn create_buffer_with_heap( + &mut self, + info: &BufferInfo, + data: Option<&[T]>, + heap: &mut Heap + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let opt = metal::MTLResourceOptions::CPUCacheModeDefaultCache | + metal::MTLResourceOptions::StorageModeManaged; + + let byte_len = (info.stride * info.num_elements) as NSUInteger; + + let buf = if let Some(data) = data { + let bytes = data.as_ptr() as *const std::ffi::c_void; + self.metal_device.new_buffer_with_data(bytes, byte_len, opt) + } + else { + self.metal_device.new_buffer(byte_len, opt) + }; + + Ok(Buffer{ + metal_buffer: buf, + element_stride: info.stride + }) + }) + } + + fn create_buffer( + &mut self, + info: &super::BufferInfo, + data: Option<&[T]>, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let opt = metal::MTLResourceOptions::CPUCacheModeDefaultCache | + metal::MTLResourceOptions::StorageModeManaged; + + let byte_len = (info.stride * info.num_elements) as NSUInteger; + + let buf = if let Some(data) = data { + let bytes = data.as_ptr() as *const std::ffi::c_void; + self.metal_device.new_buffer_with_data(bytes, byte_len, opt) + } + else { + self.metal_device.new_buffer(byte_len, opt) + }; + + Ok(Buffer{ + metal_buffer: buf, + element_stride: info.stride + }) + }) + } + + fn create_read_back_buffer( + &mut self, + size: usize, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let opt = metal::MTLResourceOptions::CPUCacheModeDefaultCache | + metal::MTLResourceOptions::StorageModeManaged; + + let byte_len = size as NSUInteger; + let buf = self.metal_device.new_buffer(byte_len, opt); + + Ok(Buffer{ + metal_buffer: buf, + element_stride: size + }) + }) + } + + fn create_texture( + &mut self, + info: &super::TextureInfo, + data: Option<&[T]>, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let desc = TextureDescriptor::new(); + + // TODO: + // tex_type + // format + // initial_state + + // desc + desc.set_pixel_format(metal::MTLPixelFormat::RGBA8Unorm); // TODO: format + + desc.set_width(info.width as NSUInteger); + desc.set_height(info.height as NSUInteger); + desc.set_depth(info.depth as NSUInteger); + desc.set_array_length(info.array_layers as NSUInteger); + desc.set_mipmap_level_count(info.mip_levels as NSUInteger); + desc.set_sample_count(info.samples as NSUInteger); + desc.set_usage(to_mtl_texture_usage(info.usage)); + desc.set_storage_mode(metal::MTLStorageMode::Shared); + desc.set_texture_type(metal::MTLTextureType::D2); + + // heap bindless + let tex = self.shader_heap.mtl_heap.new_texture(&desc) + .expect("hotline_rs::gfx::mtl failed to allocate texture in heap!"); + + // data + if let Some(data) = data { + tex.replace_region( + metal::MTLRegion { + origin: metal::MTLOrigin { x: 0, y: 0, z: 0 }, + size: metal::MTLSize { + width: info.width, + height: info.height, + depth: info.depth as u64, + }, + }, + 0, + data.as_ptr() as _, + info.width * 4, // TODO size from format + ); + } + + // srv + let srv_index = self.shader_heap.allocate(); + self.shader_heap.texture_slots[srv_index] = Some(tex.to_owned()); + + Ok(Texture{ + metal_texture: tex, + srv_index: Some(srv_index), + heap_id: Some(self.shader_heap.id) + }) + }) + } + + fn create_texture_with_heaps( + &mut self, + info: &TextureInfo, + heaps: TextureHeapInfo, + data: Option<&[T]>, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + let desc = TextureDescriptor::new(); + let tex = self.metal_device.new_texture(&desc); + Ok(Texture{ + metal_texture: tex, + srv_index: None, + heap_id: Some(self.shader_heap.id) + }) + }) + } + + fn create_render_pass( + &self, + info: &super::RenderPassInfo, + ) -> result::Result { + objc::rc::autoreleasepool(|| { + // new desc + let descriptor = metal::RenderPassDescriptor::new(); + + // colour attachments + for rt in &info.render_targets { + let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); + color_attachment.set_texture(Some(&rt.metal_texture)); + + if let Some(cc) = info.rt_clear { + color_attachment.set_load_action(metal::MTLLoadAction::Clear); + color_attachment.set_clear_color(metal::MTLClearColor::new(cc.r as f64, cc.g as f64, cc.b as f64, 1.0)); + color_attachment.set_store_action(metal::MTLStoreAction::Store); + } + else { + color_attachment.set_load_action(metal::MTLLoadAction::Load); + color_attachment.set_store_action(metal::MTLStoreAction::Store); + } + } + + Ok(RenderPass{ + desc: descriptor.to_owned() + }) + }) + } + + fn create_raytracing_pipeline( + &self, + info: &super::RaytracingPipelineInfo, + ) -> result::Result { + unimplemented!() + } + + fn create_raytracing_blas( + &mut self, + info: &RaytracingBLASInfo + ) -> result::Result { + unimplemented!() + } + + fn create_raytracing_shader_binding_table( + &self, + info: &super::RaytracingShaderBindingTableInfo + ) -> result::Result { + unimplemented!() + } + + fn create_raytracing_tlas( + &mut self, + info: &RaytracingTLASInfo + ) -> result::Result { + unimplemented!() + } + + fn create_compute_pipeline( + &self, + info: &super::ComputePipelineInfo, + ) -> result::Result { + Ok(ComputePipeline{ + slots: Vec::new() + }) + } + + fn create_indirect_render_command(&mut self, + arguments: Vec, + pipeline: Option<&RenderPipeline>) -> result::Result { + Ok(CommandSignature{ + + }) + } + + fn execute(&self, cmd: &CmdBuf) { + + } + + fn report_live_objects(&self) -> result::Result<(), super::Error> { + Ok(()) + } + + fn get_info_queue_messages(&self) -> result::Result, super::Error> { + Ok(vec![]) + } + + fn get_shader_heap(&self) -> &Self::Heap { + &self.shader_heap + } + + fn get_shader_heap_mut(&mut self) -> &mut Self::Heap { + &mut self.shader_heap + } + + fn cleanup_dropped_resources(&mut self, swap_chain: &Self::SwapChain) { + + } + + fn get_adapter_info(&self) -> &AdapterInfo { + &self.adapter_info + } + + fn read_buffer(&self, swap_chain: &SwapChain, buffer: &Buffer, size: usize, frame_written_fence: u64) -> Option { + None + } + + fn read_timestamps(&self, swap_chain: &SwapChain, buffer: &Self::Buffer, size_bytes: usize, frame_written_fence: u64) -> Vec { + vec![] + } + + fn read_pipeline_statistics(&self, swap_chain: &SwapChain, buffer: &Self::Buffer, frame_written_fence: u64) -> Option { + None + } + + fn get_timestamp_size_bytes() -> usize { + 0 + } + + fn get_pipeline_statistics_size_bytes() -> usize { + 0 + } + + fn get_indirect_command_size(argument_type: IndirectArgumentType) -> usize { + 0 + } + + fn get_counter_alignment() -> usize { + 0 + } +} + +unsafe impl Send for Device {} +unsafe impl Sync for Device {} +unsafe impl Send for SwapChain {} +unsafe impl Sync for SwapChain {} +unsafe impl Send for RenderPass {} +unsafe impl Sync for RenderPass {} +unsafe impl Send for RenderPipeline {} +unsafe impl Sync for RenderPipeline {} +unsafe impl Send for ComputePipeline {} +unsafe impl Sync for ComputePipeline {} +unsafe impl Send for Shader {} +unsafe impl Sync for Shader {} +unsafe impl Send for CmdBuf {} +unsafe impl Sync for CmdBuf {} +unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} +unsafe impl Send for Texture {} +unsafe impl Sync for Texture {} +unsafe impl Send for Heap {} +unsafe impl Sync for Heap {} +unsafe impl Send for QueryHeap {} +unsafe impl Sync for QueryHeap {} +unsafe impl Send for CommandSignature {} +unsafe impl Sync for CommandSignature {} + +impl super::ComputePipeline for ComputePipeline {} +impl super::CommandSignature for CommandSignature {} + +impl super::RaytracingPipeline for RaytracingPipeline {} +impl super::RaytracingShaderBindingTable for RaytracingShaderBindingTable {} +impl super::RaytracingBLAS for RaytracingBLAS {} + +impl super::RaytracingTLAS for RaytracingTLAS { + fn get_srv_index(&self) -> Option { + unimplemented!() + } + + fn get_shader_heap_id(&self) -> u16 { + unimplemented!() + } +} diff --git a/src/gfx/null.rs b/src/gfx/null.rs index 38e538af..91a67772 100644 --- a/src/gfx/null.rs +++ b/src/gfx/null.rs @@ -42,6 +42,9 @@ use super::AccelerationStructureRebuildMode; use super::Resource; use super::RaytracingInstanceInfo; use super::ResourceViewInfo; +use super::DescriptorType; +use super::PipelineType; + use crate::os::Window; use crate::os::App; @@ -245,16 +248,16 @@ impl super::CmdBuf for CmdBuf { fn execute_indirect( &mut self, - command: &CommandSignature, - max_command_count: u32, - argument_buffer: &Buffer, + command: &CommandSignature, + max_command_count: u32, + argument_buffer: &Buffer, argument_buffer_offset: usize, counter_buffer: Option<&Buffer>, counter_buffer_offset: usize ) { unimplemented!() } - + fn dispatch_rays(&mut self, sbt: &RaytracingShaderBindingTable, numthreads: Size3) { unimplemented!() } @@ -276,10 +279,10 @@ impl super::CmdBuf for CmdBuf { } fn copy_buffer_region( - &mut self, - dst_buffer: &Buffer, - dst_offset: usize, - src_buffer: &Buffer, + &mut self, + dst_buffer: &Buffer, + dst_offset: usize, + src_buffer: &Buffer, src_offset: usize, num_bytes: usize ) { @@ -465,7 +468,7 @@ impl super::Device for Device { } fn create_indirect_render_command( - &mut self, + &mut self, arguments: Vec, pipeline: Option<&Self::RenderPipeline> ) -> Result { @@ -655,6 +658,47 @@ impl super::RaytracingTLAS for RaytracingTLAS { } } +impl super::Pipeline for RenderPipeline { + fn get_pipeline_slot(&self, register: u32, space: u32, descriptor_type: DescriptorType) -> Option<&super::PipelineSlotInfo> { + unimplemented!() + } + + fn get_pipeline_slots(&self) -> &Vec { + unimplemented!() + } + + fn get_pipeline_type() -> PipelineType { + unimplemented!() + } +} + +impl super::Pipeline for ComputePipeline { + fn get_pipeline_slot(&self, register: u32, space: u32, descriptor_type: DescriptorType) -> Option<&super::PipelineSlotInfo> { + unimplemented!() + } + + fn get_pipeline_slots(&self) -> &Vec { + unimplemented!() + } + + fn get_pipeline_type() -> PipelineType { + unimplemented!() + } +} + +impl super::Pipeline for RaytracingPipeline { + fn get_pipeline_slot(&self, register: u32, space: u32, descriptor_type: DescriptorType) -> Option<&super::PipelineSlotInfo> { + unimplemented!() + } + + fn get_pipeline_slots(&self) -> &Vec { + unimplemented!() + } + + fn get_pipeline_type() -> PipelineType { + unimplemented!() + } +} impl super::Shader for Shader {} impl super::RenderPipeline for RenderPipeline {} diff --git a/src/imgui.rs b/src/imgui.rs index 32e1945b..c7e04ecb 100644 --- a/src/imgui.rs +++ b/src/imgui.rs @@ -5,6 +5,7 @@ use crate::os; use crate::os::App; use crate::os::Window; use crate::os::NativeHandle; +use crate::os::MonitorInfo; use crate::gfx; use crate::gfx::Buffer; @@ -59,6 +60,7 @@ pub struct ImGuiInfo<'stack, D: Device, A: App> { pub swap_chain: &'stack mut D::SwapChain, pub main_window: &'stack A::Window, pub fonts: Vec, + pub monitors: Vec } /// The concrete `ImGui` instance itself @@ -250,14 +252,14 @@ fn create_render_pipeline(info: &ImGuiInfo) -> Result(info: &ImGuiInfo) -> Result( let imgui_cmd = &cmd_data; if imgui_cmd.UserCallback.is_some() { // TODO: - } + } else { let clip_min_x = imgui_cmd.ClipRect.x - clip_off.x; let clip_min_y = imgui_cmd.ClipRect.y - clip_off.y; @@ -640,7 +642,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline io.IniFilename = i.as_ptr() as _; } }; - + Self::style_colours_hotline(); let style = &mut *igGetStyle(); @@ -661,10 +663,10 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline let config = ImFontConfig_ImFontConfig(); (*config).MergeMode = merge; - // copy over the font ranges + // copy over the font ranges let null_term_ranges = new_ranges(); let mut itr = 0; - + if let Some(ranges) = &font.glyph_ranges { // we alloc a fixed sized array on the heap for imgui glyph ranges // if you have more ranges that size will need increasing @@ -676,7 +678,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline } } font_ranges.push(null_term_ranges); - + // pass ranges or null let p_ranges = if font.glyph_ranges.is_some() { null_term_ranges as *mut u32 @@ -762,7 +764,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline let mut monitors: Vec = Vec::new(); let platform_io = &mut *igGetPlatformIO(); - let os_monitors = A::enumerate_display_monitors(); + let os_monitors = &info.monitors; for monitor in os_monitors { let ig_mon = ImGuiPlatformMonitor { MainPos: ImVec2 { @@ -1005,15 +1007,15 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline } /// Add an optional status bar which appears at the bottom of the main window at fixed size and position - /// you push items into the status bar by using `imgui.begin("status_bar")` + /// you push items into the status bar by using `imgui.begin("status_bar")` /// when passing `height` the true size may actually differ, the actual size is returned from this function pub fn add_status_bar(&mut self, height: f32) -> f32 { unsafe { - let status_bar_flags = ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoResize | + let status_bar_flags = ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings; let vp = &*igGetMainViewport(); @@ -1229,7 +1231,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline /// End imgui window must be called after a call to `begin` regardless of if `begin` returns true or false pub fn end(&mut self) { - unsafe { + unsafe { igEnd(); }; } @@ -1264,7 +1266,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline igPopStyleColor(1); } } - + /// Pop a style colour using ImGuiCol_ flags pub fn pop_style_colour_count(&mut self, count: i32) { unsafe { @@ -1307,7 +1309,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline let null_term_label = CString::new(label).unwrap(); let null_term_preview_item = CString::new(preview_item).unwrap(); igBeginCombo( - null_term_label.as_ptr() as *const i8, + null_term_label.as_ptr() as *const i8, null_term_preview_item.as_ptr() as *const i8, flags ) @@ -1335,7 +1337,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline if self.begin_combo(label, selected, ImGuiComboFlags_None as i32) { for item in items { if self.selectable(item, item == selected, ImGuiSelectableFlags_None as i32) { - result = item.to_string(); + result = item.to_string(); } } self.end_combo(); @@ -1351,9 +1353,9 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline let null_term_label = CString::new(label).unwrap(); unsafe { igMenuItemBool( - null_term_label.as_ptr() as *const i8, - std::ptr::null(), - false, + null_term_label.as_ptr() as *const i8, + std::ptr::null(), + false, true) } } @@ -1374,7 +1376,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline /// The next width will appear on the same line (horizontally) as the previous pub fn same_line(&mut self) { - unsafe { + unsafe { igSameLine(0.0, -1.0); }; } @@ -1398,7 +1400,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline /// Checkbox can be used on bools yes/no will display a tick or check when v is true and appear /// empty otherwise pub fn checkbox(&mut self, label: &str, v: &mut bool) -> bool { - unsafe { + unsafe { let null_label = CString::new(label).unwrap(); igCheckbox(null_label.as_ptr() as *const i8, v) } @@ -1459,7 +1461,7 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline unsafe { let id = to_imgui_texture_id::(tex); igImage( - id, + id, ImVec2 {x: w, y: h}, ImVec2 {x: 0.0, y: 0.0}, ImVec2 {x: 1.0, y: 1.0}, @@ -1498,9 +1500,9 @@ impl ImGui where D: Device, A: App, D::RenderPipeline: gfx::Pipeline let id = to_imgui_texture_id::(tex); igBegin(null_label.as_ptr() as *const i8, std::ptr::null_mut(), 0); - + igImage( - id, + id, ImVec2 {x: w, y: h}, ImVec2 {x: 0.0, y: 0.0}, ImVec2 {x: 1.0, y: 1.0}, @@ -1730,7 +1732,7 @@ unsafe extern "C" fn platform_destroy_window(vp: *mut ImGuiVi if !io.UserData.is_null() { get_user_data::().app.destroy_window(&vd.window[0]); } - + if !vd.swap_chain.is_empty() { vd.swap_chain.clear(); } @@ -1742,7 +1744,7 @@ unsafe extern "C" fn platform_destroy_window(vp: *mut ImGuiVi if !vd.buffers.is_empty() { vd.buffers.clear(); } - + if !vd.window.is_empty() { vd.window.clear(); } diff --git a/src/lib.rs b/src/lib.rs index 495379fc..e3a551e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ fn get_files_recursive(dir: &str, mut files: Vec) -> Vec { } else { files.push(path.to_str().unwrap().to_string()); - } + } } files } @@ -197,7 +197,7 @@ pub mod prelude { imgui, image, - // platform specific + // platform specific gfx_platform, os_platform, av_platform, @@ -230,26 +230,35 @@ pub mod prelude { }; } -#[cfg(not(target_os = "windows"))] +#[cfg(target_os = "macos")] pub mod prelude { #[doc(hidden)] pub use crate::{ // modules gfx, os, - client, - plugin, pmfx, imgui, // traits - gfx::{Device, SwapChain, CmdBuf, Texture, RenderPass}, + gfx::{Device, SwapChain, CmdBuf, Texture, RenderPass, Pipeline, Buffer}, os::{App, Window}, pmfx::Pmfx, imgui::ImGui, imdraw::ImDraw, - client::{Client, HotlineInfo, PluginInfo}, - plugin::{Plugin}, av::{VideoPlayer}, }; } + +/// This is a hardcoded compile time selection of os backend for macos as winnit +#[cfg(target_os = "macos")] +pub use os::macos as os_platform; + +/// This is a hardcoded compile time selection of os backend for macos as null +#[cfg(target_os = "macos")] +pub use gfx::null as gfx_platform; + +/// This is a hardcoded compile time selection of os backend for macos as null +#[cfg(target_os = "macos")] +pub use av::null as av_platform; + diff --git a/src/os.rs b/src/os.rs index 6c68a47f..39f1d104 100644 --- a/src/os.rs +++ b/src/os.rs @@ -5,6 +5,10 @@ pub mod null; #[cfg(target_os = "windows")] pub mod win32; +/// Implements this interface for windows macos platfrom +#[cfg(target_os = "macos")] +pub mod macos; + use std::any::Any; use serde::{Deserialize, Serialize}; @@ -144,7 +148,7 @@ bitflags! { /// Flags to control the open file dialog window pub struct OpenFileDialogFlags : u32 { - /// Open dialog to look for files + /// Open dialog to look for files const FILES = 1<<0; /// Open dialog to look for folders const FOLDERS = 1<<1; @@ -217,7 +221,7 @@ pub trait App: 'static + Any + Sized + Send + Sync + Clone { /// Get value for whether (keyboard, mouse) input is enabled fn get_input_enabled(&self) -> (bool, bool); /// Enumerate all display monitors - fn enumerate_display_monitors() -> Vec; + fn enumerate_display_monitors(&self) -> Vec; /// Sets the mouse cursor fn set_cursor(&self, cursor: &Cursor); /// Opens a native open file dialog window, exts are provided to filer selections. ie vec![".txt", ".png"] diff --git a/src/os/macos.rs b/src/os/macos.rs new file mode 100644 index 00000000..7f9c2a0d --- /dev/null +++ b/src/os/macos.rs @@ -0,0 +1,441 @@ +#![cfg(target_os = "macos")] + +extern crate objc; + +use core::panic; +use std::time::Duration; +use std::sync::Arc; +use std::sync::RwLock; + +use winit::{ + dpi::{LogicalPosition, LogicalSize}, event::{Event, WindowEvent}, event_loop::{self, ControlFlow}, platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, raw_window_handle::{HasWindowHandle, RawWindowHandle} +}; + +use cocoa::{appkit::{NSEvent, NSLeftArrowFunctionKey, NSNextFunctionKey, NSView}, base::id as cocoa_id }; + +use crate::os::Rect; + +#[derive(Clone)] +pub struct App { + event_loop: Arc>> +} + +unsafe impl Send for App {} +unsafe impl Sync for App {} + +#[derive(Clone)] +pub struct Window { + winit_window: Arc +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +#[derive(Clone, Copy)] +pub struct NativeHandle { + handle: isize +} + +pub fn nsview_from_window(window: &Window) -> *mut objc::runtime::Object { + if let Ok(RawWindowHandle::AppKit(rw)) = window.winit_window.window_handle().map(|wh| wh.as_raw()) { + let view = rw.ns_view.as_ptr() as cocoa_id; + view + } + else { + std::ptr::null_mut() + } +} + +pub fn isize_id_from_window(window: &Window) -> isize { + if let Ok(RawWindowHandle::AppKit(rw)) = window.winit_window.window_handle().map(|wh| wh.as_raw()) { + let view = rw.ns_view.as_ptr() as isize; + view + } + else { + 0 + } +} + +impl super::App for App { + type Window = Window; + type NativeHandle = NativeHandle; + + /// Create an application instance + fn create(info: super::AppInfo) -> Self { + App { + event_loop: Arc::new(RwLock::new(winit::event_loop::EventLoop::new().unwrap())) + } + } + + /// Create a new operating system window + fn create_window(&mut self, info: super::WindowInfo) -> Self::Window { + let window = winit::window::WindowBuilder::new() + .with_inner_size(winit::dpi::LogicalSize::new(info.rect.width, info.rect.height)) + .with_position(winit::dpi::LogicalPosition::new(info.rect.x, info.rect.y)) + .with_title(info.title) + .build(&*self.event_loop.read().unwrap()) + .unwrap(); + Window { + winit_window: Arc::new(window) + } + } + + /// Destroy window, unregistering app tracking + fn destroy_window(&mut self, window: &Self::Window) { + panic!(); + } + + /// Call to update windows and os state each frame, when false is returned the app has been requested to close + fn run(&mut self) -> bool { + objc::rc::autoreleasepool(|| { + let mut resume = true; + let _ = self.event_loop.write().and_then(|mut event_loop| { + let status = event_loop.pump_events(Some(Duration::ZERO), |event, elwt| { + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => { + resume = false; + } + WindowEvent::RedrawRequested => { + } + _ => { + + } + } + _ => { + + } + } + }); + + Ok(()) + }); + resume + }) + } + + /// Request to exit the application + fn exit(&mut self, exit_code: i32) { + panic!(); + } + + /// Retuns the mouse in screen coordinates + fn get_mouse_pos(&self) -> super::Point { + // TODO: + super::Point { + x: 0, + y: 0 + } + } + + /// Retuns the mouse vertical wheel position + fn get_mouse_wheel(&self) -> f32 { + //panic!(); + // TODO: + 0.0 + } + + /// Retuns the mouse horizontal wheel positions + fn get_mouse_hwheel(&self) -> f32 { + // panic!(); + 0.0 + } + + /// Retuns the mouse button states, up or down + fn get_mouse_buttons(&self) -> [bool; super::MouseButton::Count as usize] { + // panic!(); + [false; 5] + } + + /// Returns the distance the mouse has moved since the last frame + fn get_mouse_pos_delta(&self) -> super::Size { + // panic!(); + super::Size { + x: 0, + y: 0 + } + } + + /// Returns a vector of utf-16 characters that have been input since the last frame + fn get_utf16_input(&self) -> Vec { + // panic!(); + vec![] + } + + /// Returns an array of bools containing 0-256 keys down (true) or up (false) + fn get_keys_down(&self) -> [bool; 256] { + // panic!(); + [false; 256] + } + + /// Returns an array of bools containing 0-256 of keys pressed, will trigger only once and then require debouce + fn get_keys_pressed(&self) -> [bool; 256] { + // panic!(); + [false; 256] + } + + /// Returns true if the sys key is down and false if the key is up + fn is_sys_key_down(&self, key: super::SysKey) -> bool { + // panic!(); + false + } + + /// Returns true if the sys key is pressed this frame and + /// requires debounce until it is pressed again + fn is_sys_key_pressed(&self, key: super::SysKey) -> bool { + // panic!(); + false + } + + /// Get os system virtual key code from Key + fn get_key_code(key: super::Key) -> i32 { + match key { + super::Key::Tab => winit::keyboard::KeyCode::Tab as i32, + super::Key::Left => winit::keyboard::KeyCode::ArrowLeft as i32, + super::Key::Right => winit::keyboard::KeyCode::ArrowRight as i32, + super::Key::Up => winit::keyboard::KeyCode::ArrowUp as i32, + super::Key::Down => winit::keyboard::KeyCode::ArrowDown as i32, + super::Key::PageUp => winit::keyboard::KeyCode::PageUp as i32, + super::Key::PageDown => winit::keyboard::KeyCode::PageDown as i32, + super::Key::Home => winit::keyboard::KeyCode::Home as i32, + super::Key::End => winit::keyboard::KeyCode::End as i32, + super::Key::Insert => winit::keyboard::KeyCode::Insert as i32, + super::Key::Delete => winit::keyboard::KeyCode::Delete as i32, + super::Key::Backspace => winit::keyboard::KeyCode::Backspace as i32, + super::Key::Space => winit::keyboard::KeyCode::Space as i32, + super::Key::Enter => winit::keyboard::KeyCode::Enter as i32, + super::Key::Escape => winit::keyboard::KeyCode::Escape as i32, + super::Key::KeyPadEnter => winit::keyboard::KeyCode::NumpadEnter as i32, + } + } + + /// Set's whethere input from keybpard or mouse is available or not + fn set_input_enabled(&mut self, keyboard: bool, mouse: bool) { + panic!(); + } + + /// Get value for whether (keyboard, mouse) input is enabled + fn get_input_enabled(&self) -> (bool, bool) { + panic!(); + (false, false) + } + + fn enumerate_display_monitors(&self) -> Vec { + let event_loop = &*self.event_loop.read().unwrap(); + let primary_monitor = event_loop.primary_monitor(); + event_loop.available_monitors().map(|monitor| { + let winit::dpi::PhysicalSize { width, height } = monitor.size(); + let winit::dpi::PhysicalPosition { x, y } = monitor.position(); + super::MonitorInfo { + rect: Rect { + x, y, + width: width as i32, + height: height as i32 + }, + client_rect: Rect { + x, y, + width: width as i32, + height: height as i32 + }, + dpi_scale: monitor.scale_factor() as f32, + primary: primary_monitor.as_ref() == Some(&monitor) + } + }).collect() + } + + /// Sets the mouse cursor + fn set_cursor(&self, cursor: &super::Cursor) { + panic!(); + } + + /// Opens a native open file dialog window, exts are provided to filer selections. ie vec![".txt", ".png"] + fn open_file_dialog(flags: super::OpenFileDialogFlags, exts: Vec<&str>) -> Result, super::Error> { + panic!(); + Ok(vec![]) + } + + /// Returns the wndow rectangle for the console window associated with the current app + fn get_console_window_rect(&self) -> super::Rect { + super::Rect { + x: 0, + y: 0, + width: 0, + height: 0 + } + } + + /// Sets the console window rect that belongs to this app + fn set_console_window_rect(&self, rect: super::Rect) { + panic!(); + } +} + +impl super::Window for Window { + /// Bring window to front and draw ontop of all others + fn bring_to_front(&self) { + // TODO: maybe? + self.winit_window.focus_window(); + } + + /// Show window, specify true to show window or false to hide + fn show(&self, show: bool, activate: bool) { + self.winit_window.set_visible(show); + } + + /// Must be called each frame to handle resizes, parent App my have events to forward + fn update(&mut self, app: &mut App) { + // stub + // panic!(); + } + + /// Close the window + fn close(&mut self) { + // stub + //panic!(); + } + + /// Change the windows style + fn update_style(&mut self, flags: super::WindowStyleFlags, rect: super::Rect) { + // stub? + //panic!(); + } + + /// Returns true if the window is focused + fn is_focused(&self) -> bool { + self.winit_window.has_focus() + } + + /// Returns true if the window is minimised + fn is_minimised(&self) -> bool { + if let Some(min) = self.winit_window.is_minimized() { + min + } + else { + false + } + } + + /// Sets focus to this window + fn set_focused(&self) { + self.winit_window.focus_window(); + } + + /// Returns true if the mouse if hovering this window + fn is_mouse_hovered(&self) -> bool { + // TODO: + false + } + + /// Set the window display title that appears on the title bar + fn set_title(&self, title: String) { + self.winit_window.set_title(&title.as_str()); + } + + /// Set window position in screen space + fn set_pos(&self, pos: super::Point) { + self.winit_window.set_outer_position(LogicalPosition { + x: pos.x, + y: pos.y + }); + } + + /// Set window size in screen coordinates + fn set_size(&self, size: super::Size) { + self.winit_window.request_inner_size(LogicalSize::new(size.x, size.y)); + } + + /// Returns the screen position for the top-left corner of the window + fn get_pos(&self) -> super::Point { + let pos = self.winit_window.outer_position().unwrap(); + super::Point { + x: pos.x, + y: pos.y + } + } + + /// Returns a gfx friendly full window rect to use as `gfx::Viewport` or `gfx::Scissor` + fn get_viewport_rect(&self) -> super::Rect { + let size = self.winit_window.inner_size(); + super::Rect { + x: 0, + y: 0, + width: size.width as i32, + height: size.height as i32 + } + } + + /// Returns the screen position for the top-left corner of the window + fn get_size(&self) -> super::Size { + let size = self.winit_window.inner_size(); + super::Size { + x: size.width as i32, + y: size.height as i32 + } + } + + /// Returns the screen rect of the window screen pos x, y , size x, y. + fn get_window_rect(&self) -> super::Rect { + let pos = self.winit_window.outer_position().unwrap(); + let size = self.winit_window.inner_size(); + super::Rect { + x: pos.x, + y: pos.y, + width: size.width as i32, + height: size.height as i32 + } + } + + /// Return mouse position in relative coordinates from the top left corner of the window + fn get_mouse_client_pos(&self, mouse_pos: super::Point) -> super::Point { + // panic!(); + super::Point { + x: 0, + y: 0 + } + } + + /// Return the dpi scale for the current monitor the window is on + fn get_dpi_scale(&self) -> f32 { + self.winit_window.scale_factor() as f32 + } + + /// Gets the internal native handle + fn get_native_handle(&self) -> NativeHandle { + NativeHandle { + handle: isize_id_from_window(self) + } + } + + /// Gets window events tracked from os update, to handle events inside external systems + fn get_events(&self) -> super::WindowEventFlags { + panic!(); + super::WindowEventFlags { + bits: 0 + } + } + /// Clears events after they have been responded to + fn clear_events(&mut self) { + panic!(); + } + + /// Const pointer + fn as_ptr(&self) -> *const Self { + unimplemented!() + } + + /// Mut pointer + fn as_mut_ptr(&mut self) -> *mut Self { + unimplemented!() + } +} + +impl super::NativeHandle for NativeHandle { + fn get_isize(&self) -> isize { + self.handle + } + fn copy(&self) -> NativeHandle { + NativeHandle { + handle: self.handle + } + } +} \ No newline at end of file diff --git a/src/os/null.rs b/src/os/null.rs index 4f65e638..fbde865f 100644 --- a/src/os/null.rs +++ b/src/os/null.rs @@ -180,7 +180,7 @@ impl super::App for App { unimplemented!() } - fn enumerate_display_monitors() -> Vec { + fn enumerate_display_monitors(&self) -> Vec { unimplemented!() } diff --git a/src/os/win32.rs b/src/os/win32.rs index ffe062ff..67ed2a6d 100644 --- a/src/os/win32.rs +++ b/src/os/win32.rs @@ -726,7 +726,7 @@ impl super::App for App { (self.keyboard_input_enabled, self.mouse_input_enabled) } - fn enumerate_display_monitors() -> Vec { + fn enumerate_display_monitors(&self) -> Vec { unsafe { static_ref_mut!(MONITOR_ENUM).clear(); let _ = EnumDisplayMonitors(HDC::default(), None, Some(enum_func), LPARAM(0)); diff --git a/src/plugin.rs b/src/plugin.rs index 214f9e3e..514d0287 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,9 +1,11 @@ +#![cfg(target_os = "windows")] + use crate::client::*; use crate::gfx; use crate::os; use crate::reloader; -use std::process::ExitStatus; +use std::process::ExitStatus; use std::process::Command; use std::io::{self, Write}; @@ -98,7 +100,7 @@ impl reloader::ReloadResponder for PluginReloadResponder { .output() .expect("hotline::hot_lib:: hot lib failed to build!") } - else { + else { Command::new("cargo") .current_dir(&self.path) .arg("build") @@ -125,12 +127,12 @@ impl reloader::ReloadResponder for PluginReloadResponder { /// Macro to instantiate a new hotline plugin, simply defined a concrete plugin type: /// struct EmptyPlugin; -/// +/// /// You can implement the `Plugin` trait for `EmptyPlugin` /// impl Plugin for EmptyPlugin { /// .. /// } -/// +/// /// Then use this macro to make the plugin loadable from a dll /// hotline_plugin![EmptyPlugin]; #[macro_export] @@ -144,31 +146,31 @@ macro_rules! hotline_plugin { let ptr = Box::into_raw(Box::new(plugin)); ptr.cast() } - + // c-abi wrapper for `Plugin::update` #[no_mangle] pub fn update(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { - unsafe { + unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); plugin.update(client) } } - + // c-abi wrapper for `Plugin::setup` #[no_mangle] pub fn setup(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { - unsafe { + unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); plugin.setup(client) } } - + // c-abi wrapper for `Plugin::reload` #[no_mangle] pub fn unload(mut client: client::Client, ptr: *mut core::ffi::c_void) -> client::Client { - unsafe { + unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); plugin.unload(client) @@ -178,7 +180,7 @@ macro_rules! hotline_plugin { // c-abi wrapper for `Plugin::reload` #[no_mangle] pub fn ui(mut client: client::Client, ptr: *mut core::ffi::c_void, imgui_ctx: *mut core::ffi::c_void) -> client::Client { - unsafe { + unsafe { let plugin = ptr.cast::<$input>(); let plugin = plugin.as_mut().unwrap(); client.imgui.set_current_context(imgui_ctx);