diff --git a/client/Cargo.toml b/client/Cargo.toml index db598046..8e25f61e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -14,6 +14,8 @@ server = { path = "../server" } tracing = "0.1.10" ash = { version = "0.38.0", default-features = false, features = ["loaded", "debug", "std"] } lahar = { git = "https://github.com/Ralith/lahar", rev = "7963ae5750ea61fa0a894dbb73d3be0ac77255d2" } +yakui = { git = "https://github.com/SecondHalfGames/yakui", rev = "273a4a1020803066b84ac69a7646893419c31385" } +yakui-vulkan = { git = "https://github.com/SecondHalfGames/yakui", rev = "273a4a1020803066b84ac69a7646893419c31385" } winit = "0.29.10" ash-window = "0.13" raw-window-handle = "0.6" diff --git a/client/shaders/fog.frag b/client/shaders/fog.frag index 37ef0048..e8cf9da2 100644 --- a/client/shaders/fog.frag +++ b/client/shaders/fog.frag @@ -16,12 +16,6 @@ void main() { float view_length = length(view_pos); // Convert to true hyperbolic distance, taking care to respect atanh's domain float dist = view_length >= 1.0 ? INFINITY : atanh(view_length); - if (dot(scaled_view_pos.xy, scaled_view_pos.xy) < 0.0001) { - // Temporary code to add a cursor in the center of the window for placing/breaking blocks - // TODO: Replace with a UI element when UI exists - fog = vec4(0.0, 0.0, 0.0, 0.0); - } else { - // Exponential^k fog - fog = vec4(0.5, 0.65, 0.9, exp(-pow(dist * fog_density, 5))); - } + // Exponential^k fog + fog = vec4(0.5, 0.65, 0.9, exp(-pow(dist * fog_density, 5))); } diff --git a/client/src/graphics/base.rs b/client/src/graphics/base.rs index e3aa6fb2..31641ba8 100644 --- a/client/src/graphics/base.rs +++ b/client/src/graphics/base.rs @@ -139,7 +139,11 @@ impl Base { .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() .queue_family_index(queue_family_index) .queue_priorities(&[1.0])]) - .enabled_extension_names(&device_exts), + .enabled_extension_names(&device_exts) + .push_next( + &mut vk::PhysicalDeviceVulkan12Features::default() + .descriptor_binding_partially_bound(true), + ), None, ) .unwrap(), diff --git a/client/src/graphics/core.rs b/client/src/graphics/core.rs index 377a35ed..5341b166 100644 --- a/client/src/graphics/core.rs +++ b/client/src/graphics/core.rs @@ -68,7 +68,7 @@ impl Core { .application_version(0) .engine_name(name) .engine_version(0) - .api_version(vk::make_api_version(0, 1, 1, 0)); + .api_version(vk::make_api_version(0, 1, 2, 0)); let mut instance_info = vk::InstanceCreateInfo::default() .application_info(&app_info) .enabled_extension_names(&exts); diff --git a/client/src/graphics/draw.rs b/client/src/graphics/draw.rs index eb7139eb..3dd45155 100644 --- a/client/src/graphics/draw.rs +++ b/client/src/graphics/draw.rs @@ -48,6 +48,9 @@ pub struct Draw { /// Reusable storage for barriers that prevent races between buffer upload and read buffer_barriers: Vec>, + /// Yakui Vulkan context + yakui_vulkan: yakui_vulkan::YakuiVulkan, + /// Miscellany character_model: Asset, } @@ -181,6 +184,17 @@ impl Draw { gfx.save_pipeline_cache(); + let mut yakui_vulkan_options = yakui_vulkan::Options::default(); + yakui_vulkan_options.render_pass = gfx.render_pass; + yakui_vulkan_options.subpass = 1; + let mut yakui_vulkan = yakui_vulkan::YakuiVulkan::new( + &yakui_vulkan::VulkanContext::new(device, gfx.queue, gfx.memory_properties), + yakui_vulkan_options, + ); + for _ in 0..PIPELINE_DEPTH { + yakui_vulkan.transfers_submitted(); + } + let character_model = loader.load( "character model", super::GlbFile { @@ -208,6 +222,8 @@ impl Draw { buffer_barriers: Vec::new(), image_barriers: Vec::new(), + yakui_vulkan, + character_model, } } @@ -235,6 +251,12 @@ impl Draw { let device = &*self.gfx.device; let state = &mut self.states[self.next_state]; device.wait_for_fences(&[state.fence], true, !0).unwrap(); + self.yakui_vulkan + .transfers_finished(&yakui_vulkan::VulkanContext::new( + device, + self.gfx.queue, + self.gfx.memory_properties, + )); state.in_flight = false; } @@ -253,9 +275,11 @@ impl Draw { /// /// Submits commands that wait on `image_acquired` before writing to `framebuffer`'s color /// attachment. + #[allow(clippy::too_many_arguments)] // Every argument is of a different type, making this less of a problem. pub unsafe fn draw( &mut self, mut sim: Option<&mut Sim>, + yakui_paint_dom: &yakui::paint::PaintDom, framebuffer: vk::Framebuffer, depth_view: vk::ImageView, extent: vk::Extent2D, @@ -273,6 +297,9 @@ impl Draw { let state = &mut self.states[self.next_state]; let cmd = state.cmd; + let yakui_vulkan_context = + yakui_vulkan::VulkanContext::new(device, self.gfx.queue, self.gfx.memory_properties); + // We're using this state again, so put the fence back in the unsignaled state and compute // the next frame to use device.reset_fences(&[state.fence]).unwrap(); @@ -340,6 +367,9 @@ impl Draw { ); timestamp_index += 1; + self.yakui_vulkan + .transfer(yakui_paint_dom, &yakui_vulkan_context, cmd); + // Schedule transfer of uniform data. Note that we defer actually preparing the data to just // before submitting the command buffer so time-sensitive values can be set with minimum // latency. @@ -470,6 +500,9 @@ impl Draw { self.fog.draw(device, state.common_ds, cmd); + self.yakui_vulkan + .paint(yakui_paint_dom, &yakui_vulkan_context, cmd, extent); + // Finish up device.cmd_end_render_pass(cmd); device.cmd_write_timestamp( @@ -512,6 +545,7 @@ impl Draw { state.fence, ) .unwrap(); + self.yakui_vulkan.transfers_submitted(); state.used = true; state.in_flight = true; histogram!("frame.cpu").record(draw_started.elapsed()); @@ -546,6 +580,7 @@ impl Drop for Draw { voxels.destroy(device); } } + self.yakui_vulkan.cleanup(&self.gfx.device); device.destroy_command_pool(self.cmd_pool, None); device.destroy_query_pool(self.timestamp_pool, None); device.destroy_descriptor_pool(self.common_descriptor_pool, None); diff --git a/client/src/graphics/gui.rs b/client/src/graphics/gui.rs new file mode 100644 index 00000000..14353cc6 --- /dev/null +++ b/client/src/graphics/gui.rs @@ -0,0 +1,40 @@ +use yakui::{ + align, colored_box, colored_box_container, label, pad, widgets::Pad, Alignment, Color, +}; + +use crate::Sim; + +pub struct GuiState { + show_gui: bool, +} + +impl GuiState { + pub fn new() -> Self { + GuiState { show_gui: true } + } + + /// Toggles whether the GUI is shown + pub fn toggle_gui(&mut self) { + self.show_gui = !self.show_gui; + } + + /// Prepare the GUI for rendering. This should be called between + /// Yakui::start and Yakui::finish. + pub fn run(&self, sim: &Sim) { + if !self.show_gui { + return; + } + + align(Alignment::CENTER, || { + colored_box(Color::BLACK.with_alpha(0.9), [5.0, 5.0]); + }); + + align(Alignment::TOP_LEFT, || { + pad(Pad::all(8.0), || { + colored_box_container(Color::BLACK.with_alpha(0.7), || { + label(format!("Selected material: {:?}", sim.selected_material())); + }); + }); + }); + } +} diff --git a/client/src/graphics/mod.rs b/client/src/graphics/mod.rs index 83622d0c..d39cd9ae 100644 --- a/client/src/graphics/mod.rs +++ b/client/src/graphics/mod.rs @@ -6,6 +6,7 @@ mod draw; mod fog; mod frustum; mod gltf_mesh; +mod gui; mod meshes; mod png_array; pub mod voxels; diff --git a/client/src/graphics/window.rs b/client/src/graphics/window.rs index 866726c2..e4302df5 100644 --- a/client/src/graphics/window.rs +++ b/client/src/graphics/window.rs @@ -15,6 +15,7 @@ use winit::{ window::{CursorGrabMode, Window as WinitWindow, WindowBuilder}, }; +use super::gui::GuiState; use super::{Base, Core, Draw, Frustum}; use crate::Net; use crate::{net, Config, Sim}; @@ -57,6 +58,8 @@ pub struct Window { swapchain_needs_update: bool, draw: Option, sim: Option, + gui_state: GuiState, + yak: yakui::Yakui, net: Net, } @@ -93,6 +96,8 @@ impl Window { swapchain_needs_update: false, draw: None, sim: None, + gui_state: GuiState::new(), + yak: yakui::Yakui::new(), net, } } @@ -261,6 +266,9 @@ impl Window { sim.toggle_no_clip(); } } + KeyCode::F1 if state == ElementState::Pressed => { + self.gui_state.toggle_gui(); + } KeyCode::Escape => { let _ = self.window.set_cursor_grab(CursorGrabMode::None); self.window.set_cursor_visible(true); @@ -345,16 +353,30 @@ impl Window { } } }; - let aspect_ratio = - swapchain.state.extent.width as f32 / swapchain.state.extent.height as f32; + let extent = swapchain.state.extent; + let aspect_ratio = extent.width as f32 / extent.height as f32; let frame = &swapchain.state.frames[frame_id as usize]; let frustum = Frustum::from_vfov(f32::consts::FRAC_PI_4 * 1.2, aspect_ratio); + // Render the GUI + self.yak + .set_surface_size([extent.width as f32, extent.height as f32].into()); + self.yak + .set_unscaled_viewport(yakui::geometry::Rect::from_pos_size( + Default::default(), + [extent.width as f32, extent.height as f32].into(), + )); + self.yak.start(); + if let Some(sim) = self.sim.as_ref() { + self.gui_state.run(sim); + } + self.yak.finish(); // Render the frame draw.draw( self.sim.as_mut(), + self.yak.paint(), frame.buffer, frame.depth_view, - swapchain.state.extent, + extent, frame.present, &frustum, ); diff --git a/client/src/sim.rs b/client/src/sim.rs index 8abfe08c..9f59582f 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -161,6 +161,10 @@ impl Sim { self.selected_material = *MATERIAL_PALETTE.get(idx).unwrap_or(&MATERIAL_PALETTE[0]); } + pub fn selected_material(&self) -> Material { + self.selected_material + } + pub fn set_break_block_pressed_true(&mut self) { self.break_block_pressed = true; }