diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 8c490aae..4577f8a3 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -165,6 +165,28 @@ impl FramebufferRect { pub fn is_covering(&self) -> bool { self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0 } + + pub fn to_rect(&self, width: f32, height: f32) -> Rect { + let mut x1 = self.x1; + let mut x2 = self.x2; + let mut y1 = self.y1; + let mut y2 = self.y2; + (x1, y1, x2, y2) = match self.output_transform { + Transform::None => (x1, y1, x2, y2), + Transform::Rotate90 => (y1, -x2, y2, -x1), + Transform::Rotate180 => (-x2, -y2, -x1, -y1), + Transform::Rotate270 => (-y2, x1, -y1, x2), + Transform::Flip => (-x2, y1, -x1, y2), + Transform::FlipRotate90 => (y1, x1, y2, x2), + Transform::FlipRotate180 => (x1, -y2, x2, -y1), + Transform::FlipRotate270 => (-y2, -x2, -y1, -x1), + }; + let x1 = ((x1 + 1.0) / 2.0 * width).round() as i32; + let x2 = ((x2 + 1.0) / 2.0 * width).round() as i32; + let y1 = ((y1 + 1.0) / 2.0 * height).round() as i32; + let y2 = ((y2 + 1.0) / 2.0 * height).round() as i32; + Rect::new(x1, y1, x2, y2).unwrap_or_default() + } } #[derive(Debug)] @@ -192,7 +214,6 @@ pub struct CopyTexture { pub acquire_sync: AcquireSync, pub release_sync: ReleaseSync, pub alpha: Option, - #[expect(dead_code)] pub opaque: bool, } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index d5cf958c..3a8b5725 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -5,7 +5,7 @@ use { format::XRGB8888, gfx_api::{ AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat, - GfxFramebuffer, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, + GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, }, gfx_apis::vulkan::{ VulkanError, @@ -17,7 +17,6 @@ use { }, device::VulkanDevice, fence::VulkanFence, - format::BLEND_FORMAT, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, pipeline::{PipelineCreateInfo, VulkanPipeline}, sampler::VulkanSampler, @@ -28,7 +27,7 @@ use { }, }, io_uring::IoUring, - rect::Region, + rect::{Rect, Region}, theme::Color, utils::{ copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once, @@ -53,8 +52,10 @@ use { }, }, isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, + jay_algorithms::rect::Tag, linearize::{Linearize, StaticMap, static_map}, std::{ + borrow::Cow, cell::{Cell, RefCell}, collections::hash_map::Entry, fmt::{Debug, Formatter}, @@ -68,8 +69,7 @@ use { pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, - pub(super) pipelines: CopyHashMap>, - pub(super) pipelines_with_blend_buffer: CopyHashMap>, + pub(super) pipelines: StaticMap>>, pub(super) out_pipelines: RefCell>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, @@ -149,16 +149,22 @@ pub(super) struct Memory { release_fence: Option>, release_sync_file: Option, descriptor_buffers: ArrayVec, - paint_regions: Vec, - clear_rects: Vec, + paint_regions: StaticMap>, + clear_rects: StaticMap>, image_copy_regions: Vec>, - is_full_clear: bool, sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter, resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter, + regions_1: Vec, + regions_2: Vec>, +} + +#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)] +pub(super) enum RenderPass { + BlendBuffer, + FrameBuffer, } struct PaintRegion { - rect: Rect2D, x1: f32, y1: f32, x2: f32, @@ -179,7 +185,7 @@ pub(super) struct PendingFrame { } pub(super) struct VulkanFormatPipelines { - pub(super) fill: Rc, + pub(super) fill: StaticMap>, pub(super) tex: StaticMap>>, } @@ -248,7 +254,6 @@ impl VulkanDevice { formats: Rc::new(formats), device: self.clone(), pipelines: Default::default(), - pipelines_with_blend_buffer: Default::default(), out_pipelines: Default::default(), gfx_command_buffers, transfer_command_buffers, @@ -277,7 +282,7 @@ impl VulkanDevice { resource_descriptor_buffer_cache, blend_buffers: Default::default(), }); - render.get_or_create_pipelines(XRGB8888.vk_format, None)?; + render.get_or_create_pipelines(XRGB8888.vk_format, RenderPass::FrameBuffer)?; Ok(render) } } @@ -286,28 +291,28 @@ impl VulkanRenderer { fn get_or_create_pipelines( &self, format: vk::Format, - bb: Option<&VulkanImage>, + pass: RenderPass, ) -> Result, VulkanError> { - let with_linear_output = bb.is_some(); - let pipelines = match with_linear_output { - false => &self.pipelines, - true => &self.pipelines_with_blend_buffer, - }; + let with_linear_output = pass == RenderPass::BlendBuffer; + let pipelines = &self.pipelines[pass]; if let Some(pl) = pipelines.get(&format) { return Ok(pl); } - let fill = self - .device - .create_pipeline::(PipelineCreateInfo { - format, - vert: self.fill_vert_shader.clone(), - frag: self.fill_frag_shader.clone(), - blend: true, - src_has_alpha: true, - has_alpha_mult: false, - with_linear_output, - frag_descriptor_set_layout: None, - })?; + let create_fill_pipeline = |src_has_alpha| { + self.device + .create_pipeline::(PipelineCreateInfo { + format, + vert: self.fill_vert_shader.clone(), + frag: self.fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + with_linear_output, + frag_descriptor_set_layout: None, + }) + }; + let fill_opaque = create_fill_pipeline(false)?; + let fill_alpha = create_fill_pipeline(true)?; let create_tex_pipeline = |src_has_alpha, has_alpha_mult| { self.device .create_pipeline::(PipelineCreateInfo { @@ -326,7 +331,10 @@ impl VulkanRenderer { let tex_mult_opaque = create_tex_pipeline(false, true)?; let tex_mult_alpha = create_tex_pipeline(true, true)?; let format_pipelines = Rc::new(VulkanFormatPipelines { - fill, + fill: static_map! { + TexSourceType::HasAlpha => fill_alpha.clone(), + TexSourceType::Opaque => fill_opaque.clone(), + }, tex: static_map! { TexCopyType::Identity => static_map! { TexSourceType::HasAlpha => tex_alpha.clone(), @@ -452,31 +460,11 @@ impl VulkanRenderer { } } - fn initial_barriers( - &self, - buf: CommandBuffer, - fb: &VulkanImage, - bb: Option<&VulkanImage>, - ) -> Result<(), VulkanError> { + fn initial_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) -> Result<(), VulkanError> { zone!("initial_barriers"); let mut memory = self.memory.borrow_mut(); let memory = &mut *memory; memory.image_barriers.clear(); - if let Some(bb) = bb { - let barrier = image_barrier() - .image(bb.image) - .old_layout(if bb.is_undefined.get() { - ImageLayout::UNDEFINED - } else { - ImageLayout::SHADER_READ_ONLY_OPTIMAL - }) - .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) - .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .src_access_mask(AccessFlags2::SHADER_READ) - .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE); - memory.image_barriers.push(barrier); - } let mut need_fb_barrier = true; if let VulkanImageMemory::Internal(..) = &fb.ty { need_fb_barrier = fb.is_undefined.get() @@ -569,62 +557,58 @@ impl VulkanRenderer { fn begin_rendering( &self, buf: CommandBuffer, - fb: &VulkanImage, + target: &VulkanImage, clear: Option<&Color>, - bb: Option<&VulkanImage>, + pass: RenderPass, ) { zone!("begin_rendering"); let memory = &mut *self.memory.borrow_mut(); let mut load_clear = None; let mut manual_clear = None; - memory.is_full_clear = false; + let clear_rects = &memory.clear_rects[pass]; if let Some(clear) = clear { - let clear_value = ClearValue { - color: ClearColorValue { - float32: if bb.is_some() { - clear.to_array_linear(None) - } else { - clear.to_array_srgb(None) + if clear_rects.is_not_empty() { + let clear_value = ClearValue { + color: ClearColorValue { + float32: match pass { + RenderPass::BlendBuffer => clear.to_array_linear(None), + RenderPass::FrameBuffer => clear.to_array_srgb(None), + }, }, - }, - }; - let use_load_clear = memory.paint_regions.len() == 1 && { - let rect = &memory.paint_regions[0].rect; - rect.offset.x == 0 - && rect.offset.y == 0 - && rect.extent.width == fb.width - && rect.extent.height == fb.height - }; - if use_load_clear { - load_clear = Some(clear_value); - } else { - manual_clear = Some(clear_value); + }; + let use_load_clear = clear_rects.len() == 1 && { + let rect = &clear_rects[0].rect; + rect.offset.x == 0 + && rect.offset.y == 0 + && rect.extent.width == target.width + && rect.extent.height == target.height + }; + if use_load_clear { + load_clear = Some(clear_value); + } else { + manual_clear = Some(clear_value); + } } - memory.is_full_clear = use_load_clear; } let mut rendering_attachment_info = RenderingAttachmentInfo::default() .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .image_view(target.render_view.unwrap_or(target.texture_view)) .store_op(AttachmentStoreOp::STORE); - rendering_attachment_info = if let Some(bb) = bb { - rendering_attachment_info - .image_view(bb.render_view.unwrap_or(bb.texture_view)) - .load_op(AttachmentLoadOp::DONT_CARE) + let load_op = if let Some(clear) = load_clear { + rendering_attachment_info = rendering_attachment_info.clear_value(clear); + AttachmentLoadOp::CLEAR + } else if pass == RenderPass::BlendBuffer { + AttachmentLoadOp::DONT_CARE } else { - rendering_attachment_info - .image_view(fb.render_view.unwrap_or(fb.texture_view)) - .load_op(AttachmentLoadOp::LOAD) + AttachmentLoadOp::LOAD }; - if let Some(clear) = load_clear { - rendering_attachment_info = rendering_attachment_info - .clear_value(clear) - .load_op(AttachmentLoadOp::CLEAR); - } + rendering_attachment_info = rendering_attachment_info.load_op(load_op); let rendering_info = RenderingInfo::default() .render_area(Rect2D { offset: Default::default(), extent: Extent2D { - width: fb.width, - height: fb.height, + width: target.width, + height: target.height, }, }) .layer_count(1) @@ -632,26 +616,16 @@ impl VulkanRenderer { unsafe { self.device.device.cmd_begin_rendering(buf, &rendering_info); } - if memory.paint_regions.is_not_empty() { + if clear_rects.is_not_empty() { if let Some(clear) = manual_clear { let clear_attachment = ClearAttachment::default() .color_attachment(0) .clear_value(clear) .aspect_mask(ImageAspectFlags::COLOR); - memory.clear_rects.clear(); - for region in &memory.paint_regions { - memory.clear_rects.push(ClearRect { - rect: region.rect, - base_array_layer: 0, - layer_count: 1, - }); - } unsafe { - self.device.device.cmd_clear_attachments( - buf, - &[clear_attachment], - &memory.clear_rects, - ); + self.device + .device + .cmd_clear_attachments(buf, &[clear_attachment], clear_rects); } } } @@ -687,17 +661,14 @@ impl VulkanRenderer { fn record_draws( &self, buf: CommandBuffer, - fb: &VulkanImage, + target: &VulkanImage, opts: &[GfxApiOpt], - bb: Option<&VulkanImage>, + pass: RenderPass, ) -> Result<(), VulkanError> { zone!("record_draws"); let memory = &*self.memory.borrow(); - let format = match bb.is_some() { - false => fb.format.vk_format, - true => BLEND_FORMAT.vk_format, - }; - let pipelines = self.get_or_create_pipelines(format, bb)?; + let paint_regions = &memory.paint_regions[pass]; + let pipelines = self.get_or_create_pipelines(target.format.vk_format, pass)?; let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -714,23 +685,27 @@ impl VulkanRenderer { GfxApiOpt::FillRect(r) => { let push = FillPushConstants { pos: r.rect.to_points(), - color: if bb.is_some() { - r.color.to_array_linear(r.alpha) - } else { - r.color.to_array_srgb(r.alpha) + color: match pass { + RenderPass::BlendBuffer => r.color.to_array_linear(r.alpha), + RenderPass::FrameBuffer => r.color.to_array_srgb(r.alpha), }, }; - for region in &memory.paint_regions { + let source_type = match push.color[3] < 1.0 { + true => TexSourceType::HasAlpha, + false => TexSourceType::Opaque, + }; + let pipeline = &pipelines.fill[source_type]; + for region in paint_regions { let mut push = push; let draw = region.constrain(&mut push.pos, None); if !draw { continue; } - bind(&pipelines.fill); + bind(pipeline); unsafe { dev.cmd_push_constants( buf, - pipelines.fill.pipeline_layout, + pipeline.pipeline_layout, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, 0, uapi::as_bytes(&push), @@ -754,7 +729,7 @@ impl VulkanRenderer { true => TexCopyType::Multiply, false => TexCopyType::Identity, }; - let source_type = match tex.format.has_alpha { + let source_type = match tex.format.has_alpha && !c.opaque { true => TexSourceType::HasAlpha, false => TexSourceType::Opaque, }; @@ -768,7 +743,7 @@ impl VulkanRenderer { .image_view(tex.texture_view) .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); let init = Once::default(); - for region in &memory.paint_regions { + for region in paint_regions { let mut push = push; let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos)); if !draw { @@ -815,18 +790,37 @@ impl VulkanRenderer { Ok(()) } - fn copy_blend_buffer( + fn blend_buffer_initial_barrier(&self, buf: CommandBuffer, bb: &VulkanImage) { + zone!("blend_buffer_initial_barrier"); + let memory = &mut *self.memory.borrow_mut(); + memory.image_barriers.clear(); + let barrier = image_barrier() + .image(bb.image) + .old_layout(if bb.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::SHADER_READ_ONLY_OPTIMAL + }) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .src_access_mask(AccessFlags2::SHADER_READ) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE); + memory.image_barriers.push(barrier); + let dep_info = DependencyInfoKHR::default().image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn blend_buffer_copy( &self, buf: CommandBuffer, fb: &VulkanImage, - bb: Option<&VulkanImage>, + bb: &VulkanImage, ) -> Result<(), VulkanError> { - let Some(bb) = bb else { - return Ok(()); - }; - zone!("copy_blend_buffer"); + zone!("blend_buffer_copy"); let memory = &*self.memory.borrow(); - let dev = &self.device.device; let db = self.device.descriptor_buffer.as_ref().unwrap(); let pipeline = match self.out_pipelines.borrow_mut().entry(fb.format.vk_format) { Entry::Occupied(pipeline) => pipeline.get().clone(), @@ -848,6 +842,43 @@ impl VulkanRenderer { out } }; + let dev = &self.device.device; + unsafe { + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + db.cmd_set_descriptor_buffer_offsets( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &[1], + &[bb.descriptor_buffer_offset.get()], + ); + } + for region in &memory.paint_regions[RenderPass::BlendBuffer] { + let push = OutPushConstants { + pos: [ + [region.x2, region.y1], + [region.x1, region.y1], + [region.x2, region.y2], + [region.x1, region.y2], + ], + }; + unsafe { + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + Ok(()) + } + + fn blend_buffer_final_barrier(&self, buf: CommandBuffer, bb: &VulkanImage) { + zone!("blend_buffer_final_barrier"); let image_barrier = image_barrier() .image(bb.image) .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) @@ -858,58 +889,11 @@ impl VulkanRenderer { .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER); let dependency_info = DependencyInfoKHR::default().image_memory_barriers(slice::from_ref(&image_barrier)); - let rendering_attachment_info = RenderingAttachmentInfo::default() - .image_view(fb.render_view.unwrap_or(fb.texture_view)) - .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .load_op(if memory.is_full_clear { - AttachmentLoadOp::DONT_CARE - } else { - AttachmentLoadOp::LOAD - }) - .store_op(AttachmentStoreOp::STORE); - let rendering_info = RenderingInfo::default() - .render_area(Rect2D { - offset: Default::default(), - extent: Extent2D { - width: fb.width, - height: fb.height, - }, - }) - .layer_count(1) - .color_attachments(slice::from_ref(&rendering_attachment_info)); unsafe { - dev.cmd_pipeline_barrier2(buf, &dependency_info); - dev.cmd_begin_rendering(buf, &rendering_info); - dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); - db.cmd_set_descriptor_buffer_offsets( - buf, - PipelineBindPoint::GRAPHICS, - pipeline.pipeline_layout, - 0, - &[1], - &[bb.descriptor_buffer_offset.get()], - ); - for region in &memory.paint_regions { - let push = OutPushConstants { - pos: [ - [region.x2, region.y1], - [region.x1, region.y1], - [region.x2, region.y2], - [region.x1, region.y2], - ], - }; - dev.cmd_push_constants( - buf, - pipeline.pipeline_layout, - ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, - 0, - uapi::as_bytes(&push), - ); - dev.cmd_draw(buf, 4, 1, 0, 0); - } - dev.cmd_end_rendering(buf); + self.device + .device + .cmd_pipeline_barrier2(buf, &dependency_info); } - Ok(()) } fn end_rendering(&self, buf: CommandBuffer) { @@ -919,7 +903,7 @@ impl VulkanRenderer { } } - fn copy_bridge_to_dmabuf(&self, buf: CommandBuffer, fb: &VulkanImage) { + fn copy_bridge_to_dmabuf(&self, buf: CommandBuffer, fb: &VulkanImage, region: &Region) { zone!("copy_bridge_to_dmabuf"); let Some(bridge) = &fb.bridge else { return; @@ -961,15 +945,14 @@ impl VulkanRenderer { .base_array_layer(0) .mip_level(0); memory.image_copy_regions.clear(); - for region in &memory.paint_regions { - let offset = Offset3D { - x: region.rect.offset.x, - y: region.rect.offset.y, - z: 0, + for rect in region.rects() { + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; }; + let offset = Offset3D { x: x1, y: y1, z: 0 }; let extent = Extent3D { - width: region.rect.extent.width, - height: region.rect.extent.height, + width: (x2 - x1) as _, + height: (y2 - y1) as _, depth: 1, }; memory.image_copy_regions.push( @@ -981,14 +964,16 @@ impl VulkanRenderer { .extent(extent), ); } - let copy_image_info = CopyImageInfo2::default() - .src_image(fb.image) - .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .dst_image(bridge.dmabuf_image) - .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) - .regions(&memory.image_copy_regions); - unsafe { - self.device.device.cmd_copy_image2(buf, ©_image_info); + if memory.image_copy_regions.is_not_empty() { + let copy_image_info = CopyImageInfo2::default() + .src_image(fb.image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(bridge.dmabuf_image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .regions(&memory.image_copy_regions); + unsafe { + self.device.device.cmd_copy_image2(buf, ©_image_info); + } } } @@ -1287,43 +1272,124 @@ impl VulkanRenderer { Ok(semaphore) } - fn create_paint_regions(&self, fb: &VulkanImage, region: &Region) { - let mut region = region; - let region_owned; - if fb.contents_are_undefined.get() { - region_owned = fb.full_region(); - region = ®ion_owned; - } + fn create_regions( + &self, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + region: &Region, + bb: Option<&VulkanImage>, + ) { + zone!("create_paint_regions"); let memory = &mut *self.memory.borrow_mut(); - memory.paint_regions.clear(); - for rect in region.rects() { - let x1 = rect.x1().max(0); - let y1 = rect.y1().max(0); - let x2 = rect.x2(); - let y2 = rect.y2(); - if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { - continue; + memory.regions_1.clear(); + memory.regions_2.clear(); + let width = fb.width as f32; + let height = fb.height as f32; + let mut tag = 0; + for opt in opts.iter().rev() { + let (opaque, fb_rect) = match opt { + GfxApiOpt::Sync => continue, + GfxApiOpt::FillRect(f) => (f.effective_color().a >= 1.0, f.rect), + GfxApiOpt::CopyTexture(c) => { + let opaque = 'opaque: { + if let Some(a) = c.alpha { + if a < 1.0 { + break 'opaque false; + } + } + if !c.opaque { + let tex = c.tex.as_vk(&self.device.device); + if tex.format.has_alpha { + break 'opaque false; + } + } + true + }; + (opaque, c.target) + } + }; + if opaque || bb.is_none() { + tag |= 1; + } else { + tag += tag & 1; } - let x2 = x2.min(fb.width as i32); - let y2 = y2.min(fb.height as i32); - let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; - memory.paint_regions.push(PaintRegion { - rect: Rect2D { - offset: Offset2D { - x: x1 as _, - y: y1 as _, - }, - extent: Extent2D { - width: (x2 - x1) as u32, - height: (y2 - y1) as u32, - }, - }, + let rect = fb_rect.to_rect(width, height); + if opaque && clear.is_some() { + memory.regions_1.push(rect); + } + memory.regions_2.push(rect.with_tag(tag)); + } + let clear_region = if clear.is_some() { + let opaque_region = Region::from_rects2(&memory.regions_1); + region.subtract_cow(&opaque_region) + } else { + Cow::Owned(Region::default()) + }; + let tagged_region = Region::from_rects_tagged(&memory.regions_2).intersect_tagged(region); + memory.regions_1.clear(); + memory.paint_regions[RenderPass::BlendBuffer].clear(); + memory.paint_regions[RenderPass::FrameBuffer].clear(); + let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; + for rect in tagged_region.rects() { + if rect.tag() == 0 && clear.is_some() { + memory.regions_1.push(rect.untag()); + } + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; + }; + let region = match rect.tag() { + 0 => &mut memory.paint_regions[RenderPass::BlendBuffer], + _ => &mut memory.paint_regions[RenderPass::FrameBuffer], + }; + region.push(PaintRegion { x1: to_fb(x1, fb.width), x2: to_fb(x2, fb.width), y1: to_fb(y1, fb.height), y2: to_fb(y2, fb.height), }); } + let blend_clear = clear_region.intersect(&Region::from_rects2(&memory.regions_1)); + let opaque_clear = clear_region.subtract_cow(&blend_clear); + // if bb.is_none() { + // log::info!("blend_clear = {:?}", blend_clear); + // log::info!("opaque_clear = {:?}", opaque_clear); + // } + for (pass, clear_region) in [ + (RenderPass::BlendBuffer, &blend_clear), + (RenderPass::FrameBuffer, &opaque_clear), + ] { + memory.clear_rects[pass].clear(); + for rect in clear_region.rects() { + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; + }; + memory.clear_rects[pass].push(ClearRect { + rect: Rect2D { + offset: Offset2D { + x: x1 as _, + y: y1 as _, + }, + extent: Extent2D { + width: (x2 - x1) as u32, + height: (y2 - y1) as u32, + }, + }, + base_array_layer: 0, + layer_count: 1, + }); + } + } + } + + fn elide_blend_buffer(&self, blend_buffer: &mut Option>) { + if blend_buffer.is_none() { + return; + } + let memory = &*self.memory.borrow(); + if memory.paint_regions[RenderPass::BlendBuffer].is_empty() { + *blend_buffer = None; + } } fn try_execute( @@ -1334,22 +1400,36 @@ impl VulkanRenderer { opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, - blend_buffer: Option>, + mut blend_buffer: Option>, ) -> Result<(), VulkanError> { - let bb = blend_buffer.as_deref(); self.check_defunct()?; - self.create_paint_regions(fb, region); + self.create_regions(fb, opts, clear, region, blend_buffer.as_deref()); + self.elide_blend_buffer(&mut blend_buffer); + let bb = blend_buffer.as_deref(); let buf = self.gfx_command_buffers.allocate()?; self.collect_memory(opts); self.begin_command_buffer(buf.buffer)?; self.create_descriptor_buffers(buf.buffer, opts, bb)?; - self.initial_barriers(buf.buffer, fb, bb)?; - self.begin_rendering(buf.buffer, fb, clear, bb); + self.initial_barriers(buf.buffer, fb)?; self.set_viewport(buf.buffer, fb); - self.record_draws(buf.buffer, fb, opts, bb)?; - self.end_rendering(buf.buffer); - self.copy_blend_buffer(buf.buffer, fb, bb)?; - self.copy_bridge_to_dmabuf(buf.buffer, fb); + if let Some(bb) = bb { + zone!("blend buffer pass"); + self.blend_buffer_initial_barrier(buf.buffer, bb); + self.begin_rendering(buf.buffer, bb, clear, RenderPass::BlendBuffer); + self.record_draws(buf.buffer, bb, opts, RenderPass::BlendBuffer)?; + self.end_rendering(buf.buffer); + self.blend_buffer_final_barrier(buf.buffer, bb); + } + { + zone!("frame buffer pass"); + self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer); + self.record_draws(buf.buffer, fb, opts, RenderPass::FrameBuffer)?; + if let Some(bb) = bb { + self.blend_buffer_copy(buf.buffer, fb, bb)?; + } + self.end_rendering(buf.buffer); + } + self.copy_bridge_to_dmabuf(buf.buffer, fb, region); self.final_barriers(buf.buffer, fb); self.end_command_buffer(buf.buffer)?; self.create_wait_semaphores(fb, &fb_acquire_sync)?; @@ -1524,3 +1604,19 @@ impl PaintRegion { true } } + +fn constrain_to_fb(fb: &VulkanImage, rect: &Rect) -> Option<[i32; 4]> +where + T: Tag, +{ + let x1 = rect.x1().max(0); + let y1 = rect.y1().max(0); + let x2 = rect.x2(); + let y2 = rect.y2(); + if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { + return None; + } + let x2 = x2.min(fb.width as i32); + let y2 = y2.min(fb.height as i32); + Some([x1, y1, x2, y2]) +} diff --git a/src/rect.rs b/src/rect.rs index 62103392..77b0974a 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -163,7 +163,6 @@ impl Rect { Self::new_sized(self.raw.x1, self.raw.y1, width, height) } - #[expect(dead_code)] pub fn with_tag(&self, tag: u32) -> Rect { Rect { raw: RectRaw { @@ -337,7 +336,6 @@ where ) } - #[expect(dead_code)] pub fn tag(&self) -> T { self.raw.tag } diff --git a/src/rect/region.rs b/src/rect/region.rs index 0814cb17..9679bc8a 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -15,6 +15,7 @@ use { }, smallvec::SmallVec, std::{ + borrow::Cow, cell::UnsafeCell, fmt::{Debug, Formatter}, mem, @@ -86,7 +87,19 @@ impl Region { }) } - #[cfg_attr(not(test), expect(dead_code))] + pub fn subtract_cow<'a>(&'a self, other: &Self) -> Cow<'a, Self> { + if self.extents.is_empty() || other.extents.is_empty() { + return Cow::Borrowed(self); + } + let rects = subtract(&self.rects, &other.rects); + Cow::Owned(Self { + extents: Rect { + raw: extents(&rects), + }, + rects, + }) + } + pub fn intersect(&self, other: &Region) -> Self { if self.is_empty() || other.is_empty() { return Self::default(); @@ -102,7 +115,6 @@ impl Region { } impl Region { - #[cfg_attr(not(test), expect(dead_code))] pub fn from_rects_tagged(rects: &[Rect]) -> Self { if rects.is_empty() { return Self::default(); @@ -123,7 +135,6 @@ impl Region { } } - #[cfg_attr(not(test), expect(dead_code))] pub fn intersect_tagged(&self, other: &Region) -> Self { if self.is_empty() || other.is_empty() { return Self::default();