scissor attempt

This commit is contained in:
Aleksander
2025-09-23 17:43:19 +02:00
parent 5e6852e5d0
commit 8d41d8bbd1
8 changed files with 143 additions and 70 deletions

View File

@@ -23,7 +23,7 @@ use wgui::{
EventListenerCollection, MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, EventListenerCollection, MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent,
MouseWheelEvent, MouseWheelEvent,
}, },
gfx::WGfx, gfx::{WGfx, cmd::WGfxClearMode},
renderer_vk::{self}, renderer_vk::{self},
}; };
use winit::{ use winit::{
@@ -32,12 +32,16 @@ use winit::{
keyboard::{KeyCode, PhysicalKey}, keyboard::{KeyCode, PhysicalKey},
}; };
use crate::testbed::{ use crate::{
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, rate_limiter::RateLimiter,
testbed::{
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric,
},
}; };
mod assets; mod assets;
mod profiler; mod profiler;
mod rate_limiter;
mod testbed; mod testbed;
mod timestep; mod timestep;
mod vulkan; mod vulkan;
@@ -117,6 +121,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut timestep = Timestep::new(); let mut timestep = Timestep::new();
timestep.set_tps(60.0); timestep.set_tps(60.0);
let mut limiter = RateLimiter::new();
#[allow(deprecated)] #[allow(deprecated)]
event_loop.run(move |event, elwt| { event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll); elwt.set_control_flow(ControlFlow::Poll);
@@ -291,6 +297,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
log::trace!("drawing frame {frame_index}"); log::trace!("drawing frame {frame_index}");
frame_index += 1; frame_index += 1;
limiter.start(120); // max 120 fps
profiler.start(); profiler.start();
{ {
@@ -301,7 +308,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
recreate = true; recreate = true;
return; 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(); let tgt = images[image_index as usize].clone();
@@ -309,7 +319,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd_buf = gfx let mut cmd_buf = gfx
.create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit) .create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)
.unwrap(); .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(); let primitives = wgui::drawing::draw(&testbed.layout().borrow_mut()).unwrap();
render_context render_context
@@ -334,6 +346,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
profiler.end(); profiler.end();
limiter.end();
} }
Event::AboutToWait => { Event::AboutToWait => {
// should be limited to vsync // should be limited to vsync

View File

@@ -1,10 +1,4 @@
use std::{sync::LazyLock, time::Instant}; use crate::timestep::get_micros;
static TIME_START: LazyLock<Instant> = LazyLock::new(Instant::now);
pub fn get_micros() -> u64 {
TIME_START.elapsed().as_micros() as u64
}
pub struct Profiler { pub struct Profiler {
interval_us: u64, interval_us: u64,

37
uidev/src/rate_limiter.rs Normal file
View File

@@ -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));
}
}
}

View File

@@ -111,6 +111,14 @@ pub struct Rectangle {
pub round_units: u8, 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 struct RenderPrimitive {
pub(super) boundary: Boundary, pub(super) boundary: Boundary,
pub(super) transform: Mat4, pub(super) transform: Mat4,
@@ -122,6 +130,7 @@ pub enum PrimitivePayload {
Rectangle(Rectangle), Rectangle(Rectangle),
Text(Rc<RefCell<Buffer>>), Text(Rc<RefCell<Buffer>>),
Sprite(Option<CustomGlyph>), //option because we want as_slice Sprite(Option<CustomGlyph>), //option because we want as_slice
Scissor(Scissor),
} }
fn draw_widget( fn draw_widget(

View File

@@ -8,7 +8,7 @@ use vulkano::{
PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents,
}, },
device::Queue, device::Queue,
format::Format, format::{ClearValue, Format},
image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView}, image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
render_pass::{AttachmentLoadOp, AttachmentStoreOp}, render_pass::{AttachmentLoadOp, AttachmentStoreOp},
@@ -44,14 +44,26 @@ impl<T> WCommandBuffer<T> {
} }
} }
#[derive(Clone, Copy)]
pub enum WGfxClearMode {
Keep,
Clear([f32; 4]),
}
impl WCommandBuffer<CmdBufGfx> { impl WCommandBuffer<CmdBufGfx> {
pub fn begin_rendering(&mut self, render_target: Arc<ImageView>) -> anyhow::Result<()> { pub fn begin_rendering(&mut self, render_target: Arc<ImageView>, clear_mode: WGfxClearMode) -> anyhow::Result<()> {
self.command_buffer.begin_rendering(RenderingInfo { self.command_buffer.begin_rendering(RenderingInfo {
contents: SubpassContents::SecondaryCommandBuffers, contents: SubpassContents::SecondaryCommandBuffers,
color_attachments: vec![Some(RenderingAttachmentInfo { 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, 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) ..RenderingAttachmentInfo::image_view(render_target)
})], })],
..Default::default() ..Default::default()

View File

@@ -3,9 +3,10 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
use cosmic_text::Buffer; use cosmic_text::Buffer;
use glam::{Mat4, Vec2, Vec3}; use glam::{Mat4, Vec2, Vec3};
use slotmap::{SlotMap, new_key_type}; use slotmap::{SlotMap, new_key_type};
use vulkano::{buffer::view, pipeline::graphics::viewport};
use crate::{ use crate::{
drawing, drawing::{self, Scissor},
gfx::{WGfx, cmd::GfxCommandBuffer}, gfx::{WGfx, cmd::GfxCommandBuffer},
}; };
@@ -24,10 +25,11 @@ struct RendererPass<'a> {
text_areas: Vec<TextArea<'a>>, text_areas: Vec<TextArea<'a>>,
text_renderer: TextRenderer, text_renderer: TextRenderer,
rect_renderer: RectRenderer, rect_renderer: RectRenderer,
scissor: Option<Scissor>,
} }
impl RendererPass<'_> { impl RendererPass<'_> {
fn new(text_atlas: &mut TextAtlas, rect_pipeline: RectPipeline) -> anyhow::Result<Self> { fn new(text_atlas: &mut TextAtlas, rect_pipeline: RectPipeline, scissor: Option<Scissor>) -> anyhow::Result<Self> {
let text_renderer = TextRenderer::new(text_atlas)?; let text_renderer = TextRenderer::new(text_atlas)?;
let rect_renderer = RectRenderer::new(rect_pipeline)?; let rect_renderer = RectRenderer::new(rect_pipeline)?;
@@ -36,6 +38,7 @@ impl RendererPass<'_> {
text_renderer, text_renderer,
rect_renderer, rect_renderer,
text_areas: Vec::new(), text_areas: Vec::new(),
scissor,
}) })
} }
@@ -49,6 +52,19 @@ impl RendererPass<'_> {
if self.submitted { if self.submitted {
return Ok(()); 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.submitted = true;
self.rect_renderer.render(gfx, viewport, cmd_buf)?; self.rect_renderer.render(gfx, viewport, cmd_buf)?;
@@ -152,10 +168,7 @@ impl Context {
self.dirty = true; self.dirty = true;
} }
let size = Vec2::new( let size = Vec2::new(resolution[0] as f32 / pixel_scale, resolution[1] as f32 / pixel_scale);
resolution[0] as f32 / pixel_scale,
resolution[1] as f32 / pixel_scale,
);
let fov = 0.4; let fov = 0.4;
let aspect_ratio = size.x / size.y; 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 atlas = shared.atlas_map.get_mut(self.shared_ctx_key).unwrap();
let mut passes = vec![RendererPass::new( let mut passes = Vec::<RendererPass>::new();
&mut atlas.text_atlas, let mut needs_new_pass = true;
shared.rect_pipeline.clone(), let mut next_scissor: Option<Scissor> = None;
)?];
for primitive in primitives { 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 let pass = passes.last_mut().unwrap(); // always safe
match &primitive.payload { match &primitive.payload {
drawing::PrimitivePayload::Rectangle(rectangle) => { drawing::PrimitivePayload::Rectangle(rectangle) => {
pass.rect_renderer.add_rect( pass
primitive.boundary, .rect_renderer
*rectangle, .add_rect(primitive.boundary, *rectangle, &primitive.transform, primitive.depth);
&primitive.transform,
primitive.depth,
);
} }
drawing::PrimitivePayload::Text(text) => { drawing::PrimitivePayload::Text(text) => {
pass.text_areas.push(TextArea { pass.text_areas.push(TextArea {
@@ -230,16 +249,16 @@ impl Context {
transform: primitive.transform, transform: primitive.transform,
}); });
} }
drawing::PrimitivePayload::Scissor(scissor) => {
next_scissor = Some(*scissor);
needs_new_pass = true;
}
} }
} }
let pass = passes.last_mut().unwrap(); for mut pass in passes {
pass.submit( pass.submit(&shared.gfx, &mut self.viewport, cmd_buf, &mut atlas.text_atlas)?
&shared.gfx, }
&mut self.viewport,
cmd_buf,
&mut atlas.text_atlas,
)?;
Ok(()) Ok(())
} }

View File

@@ -43,8 +43,7 @@ impl ModelBuffer {
}) })
} }
pub fn clear(&mut self) { pub const fn begin(&mut self) {
self.models.clear(); // note: capacity is being preserved here
self.idx = 0; self.idx = 0;
} }
@@ -83,9 +82,7 @@ impl ModelBuffer {
}*/ }*/
if self.idx == self.models.len() as u32 { if self.idx == self.models.len() as u32 {
self self.models.resize((self.models.len() * 2).max(1), Default::default());
.models
.resize(self.models.len() * 2, Default::default());
//log::info!("ModelBuffer: resized to {}", self.models.len()); //log::info!("ModelBuffer: resized to {}", self.models.len());
} }
@@ -96,12 +93,7 @@ impl ModelBuffer {
ret ret
} }
pub fn register_pos_size( pub fn register_pos_size(&mut self, pos: &glam::Vec2, size: &glam::Vec2, transform: &Mat4) -> u32 {
&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)); let mut model = glam::Mat4::from_translation(Vec3::new(pos.x, pos.y, 0.0));
model *= *transform; model *= *transform;
model *= glam::Mat4::from_scale(Vec3::new(size.x, size.y, 1.0)); model *= glam::Mat4::from_scale(Vec3::new(size.x, size.y, 1.0));

View File

@@ -75,12 +75,11 @@ pub struct RectRenderer {
impl RectRenderer { impl RectRenderer {
pub fn new(pipeline: RectPipeline) -> anyhow::Result<Self> { pub fn new(pipeline: RectPipeline) -> anyhow::Result<Self> {
const BUFFER_SIZE: usize = 128; const BUFFER_SIZE: usize = 32;
let vert_buffer = pipeline.gfx.empty_buffer( let vert_buffer = pipeline
BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, .gfx
BUFFER_SIZE as _, .empty_buffer(BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, BUFFER_SIZE as _)?;
)?;
Ok(Self { Ok(Self {
model_buffer: ModelBuffer::new(&pipeline.gfx)?, model_buffer: ModelBuffer::new(&pipeline.gfx)?,
@@ -92,17 +91,15 @@ impl RectRenderer {
}) })
} }
pub fn add_rect( pub fn begin(&mut self) {
&mut self, self.rect_vertices.clear();
boundary: Boundary, self.model_buffer.begin();
rectangle: Rectangle, }
transform: &Mat4,
depth: f32, pub fn add_rect(&mut self, boundary: Boundary, rectangle: Rectangle, transform: &Mat4, depth: f32) {
) { let in_model_idx = self
let in_model_idx = .model_buffer
self .register_pos_size(&boundary.pos, &boundary.size, transform);
.model_buffer
.register_pos_size(&boundary.pos, &boundary.size, transform);
self.rect_vertices.push(RectVertex { self.rect_vertices.push(RectVertex {
in_model_idx, in_model_idx,
@@ -123,10 +120,10 @@ impl RectRenderer {
fn upload_verts(&mut self) -> anyhow::Result<()> { fn upload_verts(&mut self) -> anyhow::Result<()> {
if self.vert_buffer_size < self.rect_vertices.len() { if self.vert_buffer_size < self.rect_vertices.len() {
let new_size = self.vert_buffer_size * 2; let new_size = self.vert_buffer_size * 2;
self.vert_buffer = self.pipeline.gfx.empty_buffer( self.vert_buffer = self
BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, .pipeline
new_size as _, .gfx
)?; .empty_buffer(BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, new_size as _)?;
self.vert_buffer_size = new_size; self.vert_buffer_size = new_size;
} }