diff --git a/uidev/src/main.rs b/uidev/src/main.rs index ce6419b..4a689b0 100644 --- a/uidev/src/main.rs +++ b/uidev/src/main.rs @@ -23,7 +23,7 @@ use wgui::{ EventListenerCollection, MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent, }, - gfx::WGfx, + gfx::{WGfx, cmd::WGfxClearMode}, renderer_vk::{self}, }; use winit::{ @@ -32,12 +32,16 @@ use winit::{ keyboard::{KeyCode, PhysicalKey}, }; -use crate::testbed::{ - TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, +use crate::{ + rate_limiter::RateLimiter, + testbed::{ + TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, + }, }; mod assets; mod profiler; +mod rate_limiter; mod testbed; mod timestep; mod vulkan; @@ -117,6 +121,8 @@ fn main() -> Result<(), Box> { let mut timestep = Timestep::new(); timestep.set_tps(60.0); + let mut limiter = RateLimiter::new(); + #[allow(deprecated)] event_loop.run(move |event, elwt| { elwt.set_control_flow(ControlFlow::Poll); @@ -291,6 +297,7 @@ fn main() -> Result<(), Box> { log::trace!("drawing frame {frame_index}"); frame_index += 1; + limiter.start(120); // max 120 fps profiler.start(); { @@ -301,7 +308,10 @@ fn main() -> Result<(), Box> { recreate = true; return; } - Err(e) => panic!("failed to acquire next image: {e}"), + Err(e) => { + log::error!("failed to acquire next image: {e}"); + return; + } }; let tgt = images[image_index as usize].clone(); @@ -309,7 +319,9 @@ fn main() -> Result<(), Box> { let mut cmd_buf = gfx .create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit) .unwrap(); - cmd_buf.begin_rendering(tgt).unwrap(); + cmd_buf + .begin_rendering(tgt, WGfxClearMode::Clear([0.0, 0.0, 0.0, 0.1])) + .unwrap(); let primitives = wgui::drawing::draw(&testbed.layout().borrow_mut()).unwrap(); render_context @@ -334,6 +346,7 @@ fn main() -> Result<(), Box> { } profiler.end(); + limiter.end(); } Event::AboutToWait => { // should be limited to vsync diff --git a/uidev/src/profiler.rs b/uidev/src/profiler.rs index 67c498b..3d4d523 100644 --- a/uidev/src/profiler.rs +++ b/uidev/src/profiler.rs @@ -1,10 +1,4 @@ -use std::{sync::LazyLock, time::Instant}; - -static TIME_START: LazyLock = LazyLock::new(Instant::now); - -pub fn get_micros() -> u64 { - TIME_START.elapsed().as_micros() as u64 -} +use crate::timestep::get_micros; pub struct Profiler { interval_us: u64, diff --git a/uidev/src/rate_limiter.rs b/uidev/src/rate_limiter.rs new file mode 100644 index 0000000..4ad3186 --- /dev/null +++ b/uidev/src/rate_limiter.rs @@ -0,0 +1,37 @@ +use crate::timestep::get_micros; + +#[derive(Default)] +pub struct RateLimiter { + rate: u16, + start_us: u64, + end_us: u64, +} + +impl RateLimiter { + pub fn new() -> Self { + Self { + rate: 0, + end_us: 0, + start_us: 0, + } + } + + pub fn start(&mut self, rate: u16) { + self.rate = rate; + self.start_us = get_micros(); + } + + pub fn end(&mut self) { + if self.rate == 0 { + return; + } + + self.end_us = get_micros(); + let microseconds = self.end_us - self.start_us; + let frametime_microseconds = ((1000.0 / self.rate as f32) * 1000.0) as u64; + let delay = frametime_microseconds as i64 - microseconds as i64; + if delay > 0 { + std::thread::sleep(std::time::Duration::from_micros(delay as u64)); + } + } +} diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 5ac29a8..e1603b8 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -111,6 +111,14 @@ pub struct Rectangle { pub round_units: u8, } +#[derive(Clone, Copy)] +pub struct Scissor { + pub x: u32, + pub y: u32, + pub w: u32, + pub h: u32, +} + pub struct RenderPrimitive { pub(super) boundary: Boundary, pub(super) transform: Mat4, @@ -122,6 +130,7 @@ pub enum PrimitivePayload { Rectangle(Rectangle), Text(Rc>), Sprite(Option), //option because we want as_slice + Scissor(Scissor), } fn draw_widget( diff --git a/wgui/src/gfx/cmd.rs b/wgui/src/gfx/cmd.rs index bfe8f1a..6c8c343 100644 --- a/wgui/src/gfx/cmd.rs +++ b/wgui/src/gfx/cmd.rs @@ -8,7 +8,7 @@ use vulkano::{ PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents, }, device::Queue, - format::Format, + format::{ClearValue, Format}, image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, render_pass::{AttachmentLoadOp, AttachmentStoreOp}, @@ -44,14 +44,26 @@ impl WCommandBuffer { } } +#[derive(Clone, Copy)] +pub enum WGfxClearMode { + Keep, + Clear([f32; 4]), +} + impl WCommandBuffer { - pub fn begin_rendering(&mut self, render_target: Arc) -> anyhow::Result<()> { + pub fn begin_rendering(&mut self, render_target: Arc, clear_mode: WGfxClearMode) -> anyhow::Result<()> { self.command_buffer.begin_rendering(RenderingInfo { contents: SubpassContents::SecondaryCommandBuffers, color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, + load_op: match &clear_mode { + WGfxClearMode::Keep => AttachmentLoadOp::Load, + WGfxClearMode::Clear(_) => AttachmentLoadOp::Clear, + }, store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 0.0].into()), + clear_value: match &clear_mode { + WGfxClearMode::Keep => None, + WGfxClearMode::Clear(color) => Some(ClearValue::Float(*color)), + }, ..RenderingAttachmentInfo::image_view(render_target) })], ..Default::default() diff --git a/wgui/src/renderer_vk/context.rs b/wgui/src/renderer_vk/context.rs index 605b9ac..3fb43f5 100644 --- a/wgui/src/renderer_vk/context.rs +++ b/wgui/src/renderer_vk/context.rs @@ -3,9 +3,10 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use cosmic_text::Buffer; use glam::{Mat4, Vec2, Vec3}; use slotmap::{SlotMap, new_key_type}; +use vulkano::{buffer::view, pipeline::graphics::viewport}; use crate::{ - drawing, + drawing::{self, Scissor}, gfx::{WGfx, cmd::GfxCommandBuffer}, }; @@ -24,10 +25,11 @@ struct RendererPass<'a> { text_areas: Vec>, text_renderer: TextRenderer, rect_renderer: RectRenderer, + scissor: Option, } impl RendererPass<'_> { - fn new(text_atlas: &mut TextAtlas, rect_pipeline: RectPipeline) -> anyhow::Result { + fn new(text_atlas: &mut TextAtlas, rect_pipeline: RectPipeline, scissor: Option) -> anyhow::Result { let text_renderer = TextRenderer::new(text_atlas)?; let rect_renderer = RectRenderer::new(rect_pipeline)?; @@ -36,6 +38,7 @@ impl RendererPass<'_> { text_renderer, rect_renderer, text_areas: Vec::new(), + scissor, }) } @@ -49,6 +52,19 @@ impl RendererPass<'_> { if self.submitted { return Ok(()); } + + let vk_scissor = if let Some(scissor) = self.scissor { + viewport::Scissor { + offset: [scissor.x, scissor.y], + extent: [scissor.w, scissor.h], + } + } else { + viewport::Scissor::default() + }; + + // TODO? + // cmd_buf.command_buffer.set_scissor(0, smallvec::smallvec![vk_scissor])?; + self.submitted = true; self.rect_renderer.render(gfx, viewport, cmd_buf)?; @@ -152,10 +168,7 @@ impl Context { self.dirty = true; } - let size = Vec2::new( - resolution[0] as f32 / pixel_scale, - resolution[1] as f32 / pixel_scale, - ); + let size = Vec2::new(resolution[0] as f32 / pixel_scale, resolution[1] as f32 / pixel_scale); let fov = 0.4; let aspect_ratio = size.x / size.y; @@ -187,22 +200,28 @@ impl Context { let atlas = shared.atlas_map.get_mut(self.shared_ctx_key).unwrap(); - let mut passes = vec![RendererPass::new( - &mut atlas.text_atlas, - shared.rect_pipeline.clone(), - )?]; + let mut passes = Vec::::new(); + let mut needs_new_pass = true; + let mut next_scissor: Option = None; for primitive in primitives { + if needs_new_pass { + passes.push(RendererPass::new( + &mut atlas.text_atlas, + shared.rect_pipeline.clone(), + next_scissor, + )?); + next_scissor = None; + needs_new_pass = false; + } + let pass = passes.last_mut().unwrap(); // always safe match &primitive.payload { drawing::PrimitivePayload::Rectangle(rectangle) => { - pass.rect_renderer.add_rect( - primitive.boundary, - *rectangle, - &primitive.transform, - primitive.depth, - ); + pass + .rect_renderer + .add_rect(primitive.boundary, *rectangle, &primitive.transform, primitive.depth); } drawing::PrimitivePayload::Text(text) => { pass.text_areas.push(TextArea { @@ -230,16 +249,16 @@ impl Context { transform: primitive.transform, }); } + drawing::PrimitivePayload::Scissor(scissor) => { + next_scissor = Some(*scissor); + needs_new_pass = true; + } } } - let pass = passes.last_mut().unwrap(); - pass.submit( - &shared.gfx, - &mut self.viewport, - cmd_buf, - &mut atlas.text_atlas, - )?; + for mut pass in passes { + pass.submit(&shared.gfx, &mut self.viewport, cmd_buf, &mut atlas.text_atlas)? + } Ok(()) } diff --git a/wgui/src/renderer_vk/model_buffer.rs b/wgui/src/renderer_vk/model_buffer.rs index f834f3e..1567ab6 100644 --- a/wgui/src/renderer_vk/model_buffer.rs +++ b/wgui/src/renderer_vk/model_buffer.rs @@ -43,8 +43,7 @@ impl ModelBuffer { }) } - pub fn clear(&mut self) { - self.models.clear(); // note: capacity is being preserved here + pub const fn begin(&mut self) { self.idx = 0; } @@ -83,9 +82,7 @@ impl ModelBuffer { }*/ if self.idx == self.models.len() as u32 { - self - .models - .resize(self.models.len() * 2, Default::default()); + self.models.resize((self.models.len() * 2).max(1), Default::default()); //log::info!("ModelBuffer: resized to {}", self.models.len()); } @@ -96,12 +93,7 @@ impl ModelBuffer { ret } - pub fn register_pos_size( - &mut self, - pos: &glam::Vec2, - size: &glam::Vec2, - transform: &Mat4, - ) -> u32 { + pub fn register_pos_size(&mut self, pos: &glam::Vec2, size: &glam::Vec2, transform: &Mat4) -> u32 { let mut model = glam::Mat4::from_translation(Vec3::new(pos.x, pos.y, 0.0)); model *= *transform; model *= glam::Mat4::from_scale(Vec3::new(size.x, size.y, 1.0)); diff --git a/wgui/src/renderer_vk/rect.rs b/wgui/src/renderer_vk/rect.rs index 6e22d6b..2461e6d 100644 --- a/wgui/src/renderer_vk/rect.rs +++ b/wgui/src/renderer_vk/rect.rs @@ -75,12 +75,11 @@ pub struct RectRenderer { impl RectRenderer { pub fn new(pipeline: RectPipeline) -> anyhow::Result { - const BUFFER_SIZE: usize = 128; + const BUFFER_SIZE: usize = 32; - let vert_buffer = pipeline.gfx.empty_buffer( - BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, - BUFFER_SIZE as _, - )?; + let vert_buffer = pipeline + .gfx + .empty_buffer(BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, BUFFER_SIZE as _)?; Ok(Self { model_buffer: ModelBuffer::new(&pipeline.gfx)?, @@ -92,17 +91,15 @@ impl RectRenderer { }) } - pub fn add_rect( - &mut self, - boundary: Boundary, - rectangle: Rectangle, - transform: &Mat4, - depth: f32, - ) { - let in_model_idx = - self - .model_buffer - .register_pos_size(&boundary.pos, &boundary.size, transform); + pub fn begin(&mut self) { + self.rect_vertices.clear(); + self.model_buffer.begin(); + } + + pub fn add_rect(&mut self, boundary: Boundary, rectangle: Rectangle, transform: &Mat4, depth: f32) { + let in_model_idx = self + .model_buffer + .register_pos_size(&boundary.pos, &boundary.size, transform); self.rect_vertices.push(RectVertex { in_model_idx, @@ -123,10 +120,10 @@ impl RectRenderer { fn upload_verts(&mut self) -> anyhow::Result<()> { if self.vert_buffer_size < self.rect_vertices.len() { let new_size = self.vert_buffer_size * 2; - self.vert_buffer = self.pipeline.gfx.empty_buffer( - BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, - new_size as _, - )?; + self.vert_buffer = self + .pipeline + .gfx + .empty_buffer(BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, new_size as _)?; self.vert_buffer_size = new_size; }