diff --git a/build/vulkan/hash.rs b/build/vulkan/hash.rs index 1a1d5573..edd5f2e2 100644 --- a/build/vulkan/hash.rs +++ b/build/vulkan/hash.rs @@ -25,6 +25,9 @@ pub const TREES: &[Tree] = &[Tree { "rounded_tex.vert", "blur_composite.vert", "blur_composite.frag", + "blur.vert", + "blur_down.frag", + "blur_up.frag", "legacy/fill.frag", "legacy/fill.vert", "legacy/tex.vert", diff --git a/src/gfx_apis/vulkan/blur.rs b/src/gfx_apis/vulkan/blur.rs index b63cc924..af781cea 100644 --- a/src/gfx_apis/vulkan/blur.rs +++ b/src/gfx_apis/vulkan/blur.rs @@ -2,8 +2,9 @@ use { crate::gfx_apis::vulkan::{ VulkanError, image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory}, + pipeline::VulkanPipeline, renderer::VulkanRenderer, - shaders::BlurCompositePushConstants, + shaders::{BlurCompositePushConstants, BlurPushConstants}, }, ash::vk::{ AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer, @@ -16,13 +17,14 @@ use { }, gpu_alloc::UsageFlags, run_on_drop::on_drop, - std::{cell::Cell, collections::hash_map::Entry, rc::Rc, slice}, + std::{cell::Cell, rc::Rc, slice}, }; const BLUR_SCRATCH_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( ImageUsageFlags::TRANSFER_SRC.as_raw() | ImageUsageFlags::TRANSFER_DST.as_raw() - | ImageUsageFlags::SAMPLED.as_raw(), + | ImageUsageFlags::SAMPLED.as_raw() + | ImageUsageFlags::COLOR_ATTACHMENT.as_raw(), ); pub(super) struct BlurMaskRecord<'a> { @@ -42,12 +44,17 @@ impl VulkanRenderer { ) -> Result, VulkanError> { let key = (width, height, format); let cached = &mut *self.blur_scratch.borrow_mut(); - let entry = cached.entry(key); - if let Entry::Occupied(e) = &entry - && let Some(img) = e.get().upgrade() - { - return Ok(img); + + if let Some(weak) = cached.get(&key) { + if let Some(img) = weak.upgrade() { + if Rc::strong_count(&img) == 1 { + img.is_undefined.set(false); + img.contents_are_undefined.set(false); + return Ok(img); + } + } } + let create_info = ImageCreateInfo::default() .image_type(ImageType::TYPE_2D) .format(format) @@ -77,7 +84,6 @@ impl VulkanRenderer { .bind_image_memory(image, allocation.memory, allocation.offset) }; res.map_err(VulkanError::BindImageMemory)?; - // No view needed (we only blit), but VulkanImage requires one. let image_view_create_info = ImageViewCreateInfo::default() .image(image) .format(format) @@ -96,8 +102,6 @@ impl VulkanRenderer { }; let view = view.map_err(VulkanError::CreateImageView)?; destroy_image.forget(); - // Reuse the BLEND_FORMAT placeholder; the format field is informational - // here, blit ops use the actual VkFormat above. let img = Rc::new(VulkanImage { renderer: self.clone(), format: crate::gfx_apis::vulkan::format::BLEND_FORMAT, @@ -117,28 +121,30 @@ impl VulkanRenderer { sampled_image_descriptor: None, execution_version: Default::default(), }); - match entry { - Entry::Occupied(mut e) => { - e.insert(Rc::downgrade(&img)); - } - Entry::Vacant(e) => { - e.insert(Rc::downgrade(&img)); - } - } + cached.insert(key, Rc::downgrade(&img)); Ok(img) } - /// Records a backdrop blur of the given pixel rect on the target image. - /// Caller is responsible for ending the current dynamic render pass before - /// invoking, and for restarting it afterward (with LOAD). + /// Records a dual-Kawase backdrop blur of the given pixel rect on the target + /// image. Caller is responsible for ending the current dynamic render pass + /// before invoking, and for restarting it afterward (with LOAD). + /// + /// If `cached_blur` is Some, the cascade is skipped and that image is used + /// directly as the blurred input to the composite. The mask must also be Some + /// in that case, since cache+no-mask is just a no-op (blit-back of cached). + /// On a cache miss, the level-0 scratch image (holding the blurred result) + /// is returned via `out_blur_image` for the caller to store in the cache. pub(super) fn record_blur( self: &Rc, buf: CommandBuffer, target: &VulkanImage, rect: [i32; 4], passes: u8, + offset: f32, scratch_out: &mut Vec>, mask: Option<&BlurMaskRecord<'_>>, + cached_blur: Option<&Rc>, + out_blur_image: &mut Option>, ) -> Result<(), VulkanError> { let [x1, y1, x2, y2] = rect; let x1 = x1.max(0).min(target.width as i32); @@ -150,7 +156,26 @@ impl VulkanRenderer { if w < 4 || h < 4 { return Ok(()); } - let passes = passes.clamp(1, 6) as u32; + let passes = passes.clamp(1, 8) as u32; + let offset = offset.max(0.0); + + let dev = &self.device.device; + + // Cache hit fast path: skip cascade, just composite from cached image. + // The format check matters because BlurBarrier may run in either the + // BlendBuffer pass (linear format) or the FrameBuffer pass (gamma) + // depending on whether BB was elided. A cached image from one pass + // has the wrong format for the other. + if let (Some(cached), Some(mask)) = (cached_blur, mask) + && cached.width == w + && cached.height == h + && cached.format.vk_format == target.format.vk_format + { + self.record_blur_composite_only(buf, target, cached, mask, [x1, y1, x2, y2])?; + // Hold cached image alive for this frame's GPU execution. + scratch_out.push(cached.clone()); + return Ok(()); + } let format = target.format.vk_format; let mut levels: Vec> = Vec::with_capacity(passes as usize + 1); @@ -163,7 +188,8 @@ impl VulkanRenderer { levels.push(self.acquire_blur_scratch(cw, ch, format)?); } - let dev = &self.device.device; + // After cascade, levels[0] holds the blurred result. Stash it for caching. + *out_blur_image = Some(levels[0].clone()); let subres = ImageSubresourceLayers::default() .aspect_mask(ImageAspectFlags::COLOR) .layer_count(1) @@ -203,7 +229,7 @@ impl VulkanRenderer { }; // Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC. - // Step 1: levels[0] UNDEFINED -> TRANSFER_DST. + // levels[0] -> TRANSFER_DST (discard prior contents). do_barriers(&[ barrier( target.image, @@ -224,6 +250,7 @@ impl VulkanRenderer { AccessFlags2::TRANSFER_WRITE, ), ]); + levels[0].is_undefined.set(false); // Step 2: blit target rect -> levels[0] full. let blit = ImageBlit2::default() @@ -252,139 +279,153 @@ impl VulkanRenderer { dev.cmd_blit_image2(buf, &blit_info); } - // Down passes: levels[i-1] -> levels[i] with linear filter. - for i in 1..=passes as usize { - let (src, dst) = (&levels[i - 1], &levels[i]); - do_barriers(&[ - barrier( - src.image, - ImageLayout::TRANSFER_DST_OPTIMAL, - ImageLayout::TRANSFER_SRC_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_READ, - ), - barrier( - dst.image, - ImageLayout::UNDEFINED, - ImageLayout::TRANSFER_DST_OPTIMAL, - PipelineStageFlags2::TOP_OF_PIPE, - AccessFlags2::empty(), - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - ), - ]); - let blit = ImageBlit2::default() - .src_subresource(subres) - .dst_subresource(subres) - .src_offsets([ - Offset3D { x: 0, y: 0, z: 0 }, - Offset3D { - x: src.width as i32, - y: src.height as i32, - z: 1, - }, - ]) - .dst_offsets([ - Offset3D { x: 0, y: 0, z: 0 }, - Offset3D { - x: dst.width as i32, - y: dst.height as i32, - z: 1, - }, - ]); - let blit_info = BlitImageInfo2::default() - .src_image(src.image) - .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.image) - .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) - .filter(Filter::LINEAR) - .regions(slice::from_ref(&blit)); + // Step 3: levels[0] TRANSFER_DST -> SHADER_READ_ONLY for sampling in + // the down pass. + do_barriers(&[barrier( + levels[0].image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + )]); + + let blur_down_pipeline = self.get_or_create_blur_down_pipeline(format)?; + let blur_up_pipeline = self.get_or_create_blur_up_pipeline(format)?; + + // Helper to run one blur pass: sample `src`, draw into `dst`. Caller + // must have transitioned dst to COLOR_ATTACHMENT and src to + // SHADER_READ_ONLY before this. Layouts after: dst stays in + // COLOR_ATTACHMENT (caller transitions next). + let run_pass = |pipeline: &VulkanPipeline, + src: &VulkanImage, + dst: &VulkanImage| + -> Result<(), VulkanError> { + let color_attachment = RenderingAttachmentInfo::default() + .image_view(dst.texture_view) + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(AttachmentLoadOp::DONT_CARE) + .store_op(AttachmentStoreOp::STORE); + let render_area = Rect2D { + offset: Offset2D { x: 0, y: 0 }, + extent: Extent2D { + width: dst.width, + height: dst.height, + }, + }; + let rendering_info = RenderingInfo::default() + .render_area(render_area) + .layer_count(1) + .color_attachments(slice::from_ref(&color_attachment)); + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: dst.width as f32, + height: dst.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }; + let scissor = render_area; + let push = BlurPushConstants { + halfpixel: [0.5 / src.width as f32, 0.5 / src.height as f32], + offset, + }; + let src_image_info = DescriptorImageInfo::default() + .image_view(src.texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let writes = [WriteDescriptorSet::default() + .dst_binding(0) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&src_image_info))]; unsafe { - dev.cmd_blit_image2(buf, &blit_info); + dev.cmd_begin_rendering(buf, &rendering_info); + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + dev.cmd_set_scissor(buf, 0, slice::from_ref(&scissor)); + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &writes, + ); + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + dev.cmd_end_rendering(buf); } + Ok(()) + }; + + // Down passes: levels[i-1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT). + // Each iteration transitions the destination to COLOR_ATTACHMENT, + // draws, then to SHADER_READ_ONLY for the next iteration's read. + for i in 1..=passes as usize { + do_barriers(&[barrier( + levels[i].image, + ImageLayout::UNDEFINED, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::TOP_OF_PIPE, + AccessFlags2::empty(), + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE, + )]); + levels[i].is_undefined.set(false); + run_pass(&blur_down_pipeline, &levels[i - 1], &levels[i])?; + do_barriers(&[barrier( + levels[i].image, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + )]); } - // Up passes: levels[i+1] -> levels[i] with linear filter. + // Up passes: levels[i+1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT). for i in (0..passes as usize).rev() { - let (src, dst) = (&levels[i + 1], &levels[i]); - do_barriers(&[ - barrier( - src.image, - ImageLayout::TRANSFER_DST_OPTIMAL, - ImageLayout::TRANSFER_SRC_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_READ, - ), - barrier( - dst.image, - ImageLayout::TRANSFER_SRC_OPTIMAL, - ImageLayout::TRANSFER_DST_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_READ, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - ), - ]); - let blit = ImageBlit2::default() - .src_subresource(subres) - .dst_subresource(subres) - .src_offsets([ - Offset3D { x: 0, y: 0, z: 0 }, - Offset3D { - x: src.width as i32, - y: src.height as i32, - z: 1, - }, - ]) - .dst_offsets([ - Offset3D { x: 0, y: 0, z: 0 }, - Offset3D { - x: dst.width as i32, - y: dst.height as i32, - z: 1, - }, - ]); - let blit_info = BlitImageInfo2::default() - .src_image(src.image) - .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.image) - .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) - .filter(Filter::LINEAR) - .regions(slice::from_ref(&blit)); - unsafe { - dev.cmd_blit_image2(buf, &blit_info); - } + do_barriers(&[barrier( + levels[i].image, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE, + )]); + run_pass(&blur_up_pipeline, &levels[i + 1], &levels[i])?; + do_barriers(&[barrier( + levels[i].image, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + )]); } + // After cascade: levels[0] in SHADER_READ_ONLY, target in TRANSFER_SRC. + if let Some(mask) = mask { - // Masked composite path: - // levels[0] (TRANSFER_DST) -> SHADER_READ_ONLY_OPTIMAL - // target (TRANSFER_SRC) -> COLOR_ATTACHMENT_OPTIMAL - // draw composite shader sampling levels[0] + mask, blending onto fb - do_barriers(&[ - barrier( - levels[0].image, - ImageLayout::TRANSFER_DST_OPTIMAL, - ImageLayout::SHADER_READ_ONLY_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - PipelineStageFlags2::FRAGMENT_SHADER, - AccessFlags2::SHADER_SAMPLED_READ, - ), - barrier( - target.image, - ImageLayout::TRANSFER_SRC_OPTIMAL, - ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_READ, - PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, - AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, - ), - ]); + // Masked composite path: restore target to COLOR_ATTACHMENT and + // draw the composite shader sampling levels[0] + mask. + do_barriers(&[barrier( + target.image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + )]); let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?; @@ -470,14 +511,15 @@ impl VulkanRenderer { dev.cmd_end_rendering(buf); } } else { - // Final blit: levels[0] -> target rect. + // Unmasked: transition levels[0] back to TRANSFER_SRC, target stays + // in TRANSFER_SRC, retarget target to TRANSFER_DST, blit-back. do_barriers(&[ barrier( levels[0].image, - ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, ImageLayout::TRANSFER_SRC_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, PipelineStageFlags2::TRANSFER, AccessFlags2::TRANSFER_READ, ), @@ -516,21 +558,158 @@ impl VulkanRenderer { unsafe { dev.cmd_blit_image2(buf, &blit_info); } - - // Restore target to COLOR_ATTACHMENT_OPTIMAL. - do_barriers(&[barrier( - target.image, - ImageLayout::TRANSFER_DST_OPTIMAL, - ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - PipelineStageFlags2::TRANSFER, - AccessFlags2::TRANSFER_WRITE, - PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, - AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, - )]); + // Restore target to COLOR_ATTACHMENT for the resumed render pass. + // Also push levels[0] back to SHADER_READ_ONLY so its tracked layout + // matches what the cache-hit fast path expects on next frame. + do_barriers(&[ + barrier( + target.image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + ), + barrier( + levels[0].image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + ), + ]); } // Hold the scratch images until the frame is submitted. scratch_out.extend(levels); Ok(()) } + + /// Cache-hit fast path. Cached image is already in SHADER_READ_ONLY_OPTIMAL + /// (the layout we leave it in after the previous frame's composite). We just + /// re-bind the composite pipeline with the cached image as input and draw. + fn record_blur_composite_only( + self: &Rc, + buf: CommandBuffer, + target: &VulkanImage, + cached: &Rc, + mask: &BlurMaskRecord<'_>, + rect: [i32; 4], + ) -> Result<(), VulkanError> { + let [x1, y1, x2, y2] = rect; + let w = (x2 - x1) as u32; + let h = (y2 - y1) as u32; + let dev = &self.device.device; + // The caller (BlurBarrier handler) has already cmd_end_rendering'd the + // pass that produced the underlying scene. Without an explicit barrier + // here, the new render pass's LOAD reads can race with those prior + // COLOR_ATTACHMENT_WRITEs and pull stale memory — which manifests as + // per-frame flicker in the blurred region. + let target_load_barrier = ImageMemoryBarrier2::default() + .image(target.image) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask( + AccessFlags2::COLOR_ATTACHMENT_READ | AccessFlags2::COLOR_ATTACHMENT_WRITE, + ) + .src_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED); + let dep = DependencyInfoKHR::default() + .image_memory_barriers(slice::from_ref(&target_load_barrier)); + unsafe { + dev.cmd_pipeline_barrier2(buf, &dep); + } + let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?; + let target_render_view = target.render_view.unwrap_or(target.texture_view); + let color_attachment = RenderingAttachmentInfo::default() + .image_view(target_render_view) + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(AttachmentLoadOp::LOAD) + .store_op(AttachmentStoreOp::STORE); + let render_area = Rect2D { + offset: Offset2D { x: 0, y: 0 }, + extent: Extent2D { + width: target.width, + height: target.height, + }, + }; + let rendering_info = RenderingInfo::default() + .render_area(render_area) + .layer_count(1) + .color_attachments(slice::from_ref(&color_attachment)); + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: target.width as f32, + height: target.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }; + let scissor = Rect2D { + offset: Offset2D { x: x1, y: y1 }, + extent: Extent2D { + width: w, + height: h, + }, + }; + let blurred_tc: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]]; + let push = BlurCompositePushConstants { + pos: mask.target_points, + blurred_tex_pos: blurred_tc, + mask_tex_pos: mask.mask_source_points, + threshold: mask.threshold, + }; + let blurred_image_info = DescriptorImageInfo::default() + .image_view(cached.texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let mask_image_info = DescriptorImageInfo::default() + .image_view(mask.mask_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let writes = [ + WriteDescriptorSet::default() + .dst_binding(0) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&blurred_image_info)), + WriteDescriptorSet::default() + .dst_binding(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&mask_image_info)), + ]; + unsafe { + dev.cmd_begin_rendering(buf, &rendering_info); + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + dev.cmd_set_scissor(buf, 0, slice::from_ref(&scissor)); + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &writes, + ); + 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); + } + Ok(()) + } } diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index fa207f7a..145ce007 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -87,6 +87,31 @@ impl VulkanDevice { })) } + pub(super) fn create_blur_descriptor_set_layout( + self: &Rc, + sampler: &Rc, + ) -> Result, VulkanError> { + let immutable_sampler = [sampler.sampler]; + let binding = DescriptorSetLayoutBinding::default() + .binding(0) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .immutable_samplers(&immutable_sampler); + let create_info = DescriptorSetLayoutCreateInfo::default() + .bindings(slice::from_ref(&binding)) + .flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + Ok(Rc::new(VulkanDescriptorSetLayout { + device: self.clone(), + layout, + size: 0, + offsets: Default::default(), + _sampler: Some(sampler.clone()), + })) + } + pub(super) fn create_tex_sampler_descriptor_set_layout( self: &Rc, sampler: &Rc, diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index c1de1a03..bf94d6f2 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -26,7 +26,8 @@ use { sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ - BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BlurCompositePushConstants, + BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BLUR_DOWN_FRAG, BLUR_UP_FRAG, BLUR_VERT, + BlurCompositePushConstants, BlurPushConstants, ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants, InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG, LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT, @@ -123,6 +124,12 @@ pub struct VulkanRenderer { pub(super) blur_composite_frag_shader: Rc, pub(super) blur_composite_descriptor_set_layout: Rc, pub(super) blur_composite_pipelines: CopyHashMap>, + pub(super) blur_vert_shader: Rc, + pub(super) blur_down_frag_shader: Rc, + pub(super) blur_up_frag_shader: Rc, + pub(super) blur_descriptor_set_layout: Rc, + pub(super) blur_down_pipelines: CopyHashMap>, + pub(super) blur_up_pipelines: CopyHashMap>, pub(super) defunct: Cell, pub(super) pending_cpu_jobs: CopyHashMap, pub(super) shm_allocator: Rc, @@ -229,13 +236,20 @@ enum VulkanOp { struct VulkanBlurOp { rect: crate::gfx_api::FramebufferRect, passes: u8, + offset: f32, mask: Option, + cache: Option>>>, + cache_epoch: u64, + cache_pixel_rect: [i32; 4], } struct VulkanBlurMask { tex: Rc, source: crate::gfx_api::SampleRect, threshold: f32, + buffer_resv: Option>, + acquire_sync: Option, + release_sync: ReleaseSync, } struct VulkanTexOp { @@ -246,6 +260,7 @@ struct VulkanTexOp { acquire_sync: Option, release_sync: ReleaseSync, alpha: f32, + discard_alpha: f32, source_type: TexSourceType, copy_type: TexCopyType, alpha_mode: AlphaMode, @@ -286,6 +301,7 @@ struct VulkanRoundedTexOp { acquire_sync: Option, release_sync: ReleaseSync, alpha: f32, + discard_alpha: f32, source_type: TexSourceType, copy_type: TexCopyType, alpha_mode: AlphaMode, @@ -406,6 +422,10 @@ impl VulkanDevice { let blur_composite_frag_shader = self.create_shader(BLUR_COMPOSITE_FRAG)?; let blur_composite_descriptor_set_layout = self.create_blur_composite_descriptor_set_layout(&sampler)?; + let blur_vert_shader = self.create_shader(BLUR_VERT)?; + let blur_down_frag_shader = self.create_shader(BLUR_DOWN_FRAG)?; + let blur_up_frag_shader = self.create_shader(BLUR_UP_FRAG)?; + let blur_descriptor_set_layout = self.create_blur_descriptor_set_layout(&sampler)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; let transfer_command_buffers = self .distinct_transfer_queue_family_idx @@ -497,6 +517,12 @@ impl VulkanDevice { blur_composite_frag_shader, blur_composite_descriptor_set_layout, blur_composite_pipelines: Default::default(), + blur_vert_shader, + blur_down_frag_shader, + blur_up_frag_shader, + blur_descriptor_set_layout, + blur_down_pipelines: Default::default(), + blur_up_pipelines: Default::default(), defunct: Cell::new(false), pending_cpu_jobs: Default::default(), shm_allocator, @@ -886,6 +912,128 @@ impl VulkanRenderer { })) } + pub(super) fn get_or_create_blur_down_pipeline( + &self, + format: vk::Format, + ) -> Result, VulkanError> { + if let Some(pl) = self.blur_down_pipelines.get(&format) { + return Ok(pl); + } + let pl = self.create_blur_pass_pipeline(format, &self.blur_down_frag_shader)?; + self.blur_down_pipelines.set(format, pl.clone()); + Ok(pl) + } + + pub(super) fn get_or_create_blur_up_pipeline( + &self, + format: vk::Format, + ) -> Result, VulkanError> { + if let Some(pl) = self.blur_up_pipelines.get(&format) { + return Ok(pl); + } + let pl = self.create_blur_pass_pipeline(format, &self.blur_up_frag_shader)?; + self.blur_up_pipelines.set(format, pl.clone()); + Ok(pl) + } + + fn create_blur_pass_pipeline( + &self, + format: vk::Format, + frag: &Rc, + ) -> Result, VulkanError> { + use ash::vk::{ + ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, + GraphicsPipelineCreateInfo, PipelineCache, PipelineColorBlendAttachmentState, + PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, + PipelineInputAssemblyStateCreateInfo, PipelineLayoutCreateInfo, + PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, + PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, + PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, + PrimitiveTopology, PushConstantRange, SampleCountFlags, + }; + let dev = &self.device.device; + let push_range = PushConstantRange::default() + .stage_flags(ShaderStageFlags::FRAGMENT) + .offset(0) + .size(size_of::() as u32); + let set_layouts = [self.blur_descriptor_set_layout.layout]; + let layout_info = PipelineLayoutCreateInfo::default() + .push_constant_ranges(slice::from_ref(&push_range)) + .set_layouts(&set_layouts); + let pipeline_layout = unsafe { dev.create_pipeline_layout(&layout_info, None) }; + let pipeline_layout = pipeline_layout.map_err(VulkanError::CreatePipelineLayout)?; + let destroy_layout = + run_on_drop::on_drop(|| unsafe { dev.destroy_pipeline_layout(pipeline_layout, None) }); + let stages = [ + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::VERTEX) + .module(self.blur_vert_shader.module) + .name(c"main"), + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::FRAGMENT) + .module(frag.module) + .name(c"main"), + ]; + let input_assembly_state = PipelineInputAssemblyStateCreateInfo::default() + .topology(PrimitiveTopology::TRIANGLE_STRIP); + let vertex_input_state = PipelineVertexInputStateCreateInfo::default(); + let rasterization_state = PipelineRasterizationStateCreateInfo::default() + .polygon_mode(PolygonMode::FILL) + .cull_mode(CullModeFlags::NONE) + .line_width(1.0) + .front_face(FrontFace::COUNTER_CLOCKWISE); + let multisampling_state = PipelineMultisampleStateCreateInfo::default() + .sample_shading_enable(false) + .rasterization_samples(SampleCountFlags::TYPE_1); + let blending = PipelineColorBlendAttachmentState::default() + .color_write_mask(ColorComponentFlags::RGBA) + .blend_enable(false); + let color_blend_state = + PipelineColorBlendStateCreateInfo::default().attachments(slice::from_ref(&blending)); + let dynamic_states = [DynamicState::VIEWPORT, DynamicState::SCISSOR]; + let dynamic_state = + PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); + let viewport_state = PipelineViewportStateCreateInfo::default() + .viewport_count(1) + .scissor_count(1); + let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::default() + .color_attachment_formats(slice::from_ref(&format)); + let create_info = GraphicsPipelineCreateInfo::default() + .push_next(&mut pipeline_rendering_create_info) + .stages(&stages) + .input_assembly_state(&input_assembly_state) + .vertex_input_state(&vertex_input_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisampling_state) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) + .viewport_state(&viewport_state) + .layout(pipeline_layout); + let pipelines = unsafe { + dev.create_graphics_pipelines( + PipelineCache::null(), + slice::from_ref(&create_info), + None, + ) + }; + let mut pipelines = pipelines + .map_err(|e| e.1) + .map_err(VulkanError::CreatePipeline)?; + let pipeline = pipelines.pop().unwrap(); + destroy_layout.forget(); + Ok(Rc::new(VulkanPipeline { + vert: self.blur_vert_shader.clone(), + _frag: frag.clone(), + pipeline_layout, + pipeline, + _descriptor_set_layouts: { + let mut v = ArrayVec::new(); + v.push(self.blur_descriptor_set_layout.clone()); + v + }, + })) + } + pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } @@ -1263,6 +1411,7 @@ impl VulkanRenderer { acquire_sync: Some(ct.acquire_sync.clone()), release_sync: ct.release_sync, alpha: ct.alpha.unwrap_or_default(), + discard_alpha: ct.discard_alpha.unwrap_or(-1.0), source_type, copy_type, alpha_mode: ct.alpha_mode, @@ -1361,6 +1510,7 @@ impl VulkanRenderer { acquire_sync: Some(ct.acquire_sync.clone()), release_sync: ct.release_sync, alpha: ct.alpha.unwrap_or_default(), + discard_alpha: ct.discard_alpha.unwrap_or(-1.0), source_type, copy_type, alpha_mode: ct.alpha_mode, @@ -1376,9 +1526,6 @@ impl VulkanRenderer { } } GfxApiOpt::BlurBackdrop(b) => { - // Flush all pending ops in original order, then push a - // barrier op to FrameBuffer pass that will end + restart - // the render pass to do the blur work in between. sync(memory); let mask = if let Some(m) = &b.mask { let tex = m.texture.clone().into_vk(&self.device.device)?; @@ -1393,15 +1540,35 @@ impl VulkanRenderer { tex, source: m.source, threshold: m.threshold, + buffer_resv: m.buffer_resv.clone(), + acquire_sync: Some(m.acquire_sync.clone()), + release_sync: m.release_sync, }) } } else { None }; - memory.ops[RenderPass::FrameBuffer].push(VulkanOp::BlurBarrier(VulkanBlurOp { + // Route to whichever pass actually contains the scene. + // The BlendBuffer holds the linearly-composed scene + // (background + workspace + translucent layers) and is + // copied into the FrameBuffer only at the end of the FB + // pass. Reading FB before that copy would sample an + // empty target and produce a black blur. If BB has been + // elided (no blended content this frame), fall back to + // FB which then carries the full scene itself. + let target_pass = if !memory.paint_regions[RenderPass::BlendBuffer].is_empty() { + RenderPass::BlendBuffer + } else { + RenderPass::FrameBuffer + }; + memory.ops[target_pass].push(VulkanOp::BlurBarrier(VulkanBlurOp { rect: b.rect, passes: b.passes, + offset: b.offset, mask, + cache: b.cache.clone(), + cache_epoch: b.cache_epoch, + cache_pixel_rect: b.cache_pixel_rect, })); } } @@ -1586,7 +1753,7 @@ impl VulkanRenderer { release_sync, }); } else if let VulkanOp::BlurBarrier(b) = cmd - && let Some(m) = &b.mask + && let Some(m) = &mut b.mask { let tex = &m.tex; if tex.execution_version.replace(execution) != execution { @@ -1598,6 +1765,12 @@ impl VulkanRenderer { if let VulkanImageMemory::DmaBuf(_) = &tex.ty { memory.dmabuf_sample.push(tex.clone()) } + memory.textures.push(UsedTexture { + tex: tex.clone(), + resv: m.buffer_resv.take(), + acquire_sync: m.acquire_sync.take().unwrap(), + release_sync: m.release_sync, + }); } } } @@ -1929,6 +2102,7 @@ impl VulkanRenderer { let push = TexPushConstants { vertices: c.range_address, alpha: c.alpha, + discard_threshold: c.discard_alpha, }; unsafe { db.cmd_set_descriptor_buffer_offsets( @@ -1966,6 +2140,7 @@ impl VulkanRenderer { pos, tex_pos, alpha: c.alpha, + discard_threshold: c.discard_alpha, }; unsafe { dev.cmd_push_constants( @@ -2045,6 +2220,7 @@ impl VulkanRenderer { let push = RoundedTexPushConstants { vertices: c.range_address, alpha: c.alpha, + discard_threshold: c.discard_alpha, size_x: c.size[0], size_y: c.size[1], corner_radius_tl: c.corner_radius[0], @@ -2088,6 +2264,7 @@ impl VulkanRenderer { pos: c.target, tex_pos: c.source, alpha: c.alpha, + discard_threshold: c.discard_alpha, size_x: c.size[0], size_y: c.size[1], corner_radius_tl: c.corner_radius[0], @@ -2109,10 +2286,8 @@ impl VulkanRenderer { } } VulkanOp::BlurBarrier(blur) => { - // Blur is only meaningful in the FrameBuffer pass. - if pass != RenderPass::FrameBuffer { - continue; - } + // BlurBarrier is pushed to exactly one pass in convert_ops + // (BB if present, else FB), so no per-pass gating is needed. // End the current dynamic render pass, run the blur work // (image-blit cascade between scratch images), and resume // the render pass with LOAD so subsequent draws layer on @@ -2132,14 +2307,55 @@ impl VulkanRenderer { threshold: m.threshold, _phantom: std::marker::PhantomData, }); + + // Cache lookup: a hit lets us skip the entire blur cascade. + // Only masked blurs are cached. The masked path leaves the + // blurred scratch image in SHADER_READ_ONLY_OPTIMAL, which + // is the layout required by the cache-hit composite path. + let cached_blur: Option> = mask_record + .as_ref() + .and_then(|_| blur.cache.as_ref()) + .and_then(|slot| { + let slot_borrow = slot.borrow(); + slot_borrow.as_ref().and_then(|entry| { + if entry.epoch == blur.cache_epoch + && entry.passes == blur.passes + && entry.offset == blur.offset + && entry.pixel_rect == blur.cache_pixel_rect + { + entry.image.clone().into_vk(&self.device.device).ok() + } else { + None + } + }) + }); + + let mut produced_blur: Option> = None; self.record_blur( buf, target, rect_arr, blur.passes, + blur.offset, &mut local_blur_scratch, mask_record.as_ref(), + cached_blur.as_ref(), + &mut produced_blur, )?; + + // On a masked cache miss, store the freshly-blurred image + // for the next frame to reuse. + if let (Some(_), Some(slot), Some(image)) = + (mask_record.as_ref(), blur.cache.as_ref(), produced_blur) + { + *slot.borrow_mut() = Some(crate::gfx_api::BlurCacheEntry { + pixel_rect: blur.cache_pixel_rect, + passes: blur.passes, + offset: blur.offset, + epoch: blur.cache_epoch, + image, + }); + } self.begin_rendering_load(buf, target); // Pipeline state is invalidated across the render-pass // break — force re-bind on next draw. @@ -2679,6 +2895,7 @@ impl VulkanRenderer { let width = fb.width as f32; let height = fb.height as f32; let mut tag = 0; + let mut blur_rects: Vec = Vec::new(); for opt in opts.iter().rev() { let (opaque, fb_rect) = match opt { GfxApiOpt::Sync => continue, @@ -2706,7 +2923,10 @@ impl VulkanRenderer { (false, rf.rect) } GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target), - GfxApiOpt::BlurBackdrop(_) => continue, + GfxApiOpt::BlurBackdrop(b) => { + blur_rects.push(b.rect.to_rect(width, height)); + continue; + } }; if opaque || bb.is_none() { tag |= 1; @@ -2719,6 +2939,20 @@ impl VulkanRenderer { } memory.regions_2.push(rect.with_tag(tag)); } + // Force blur source rects into the effective damage region. The blur + // cascade reads its source from BB (or FB), and both buffers persist + // their contents across frames in undamaged regions. Without this, + // a cache-miss cascade can sample a stale composite — including the + // blur surface's own previously-drawn body — and re-blur it, + // producing visible double-shadow artifacts at the blur boundary. + let blur_region_owned = Region::from_rects2(&blur_rects); + let expanded_region; + let region: &Region = if blur_region_owned.is_empty() { + region + } else { + expanded_region = region.union_cow(&blur_region_owned).into_owned(); + &expanded_region + }; let clear_region = if clear.is_some() { let opaque_region = Region::from_rects2(&memory.regions_1); region.subtract_cow(&opaque_region) diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index 607fd49a..a1894335 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -21,6 +21,9 @@ pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv"); pub const BLUR_COMPOSITE_VERT: &[u8] = include_bytes!("shaders_bin/blur_composite.vert.spv"); pub const BLUR_COMPOSITE_FRAG: &[u8] = include_bytes!("shaders_bin/blur_composite.frag.spv"); +pub const BLUR_VERT: &[u8] = include_bytes!("shaders_bin/blur.vert.spv"); +pub const BLUR_DOWN_FRAG: &[u8] = include_bytes!("shaders_bin/blur_down.frag.spv"); +pub const BLUR_UP_FRAG: &[u8] = include_bytes!("shaders_bin/blur_up.frag.spv"); pub const LEGACY_ROUNDED_FILL_VERT: &[u8] = include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv"); pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] = @@ -69,6 +72,7 @@ unsafe impl Packed for TexVertex {} pub struct TexPushConstants { pub vertices: DeviceAddress, pub alpha: f32, + pub discard_threshold: f32, } unsafe impl Packed for TexPushConstants {} @@ -109,6 +113,7 @@ pub struct LegacyTexPushConstants { pub pos: [[f32; 2]; 4], pub tex_pos: [[f32; 2]; 4], pub alpha: f32, + pub discard_threshold: f32, } unsafe impl Packed for LegacyTexPushConstants {} @@ -148,6 +153,7 @@ unsafe impl Packed for LegacyRoundedFillPushConstants {} pub struct RoundedTexPushConstants { pub vertices: DeviceAddress, pub alpha: f32, + pub discard_threshold: f32, pub size_x: f32, pub size_y: f32, pub corner_radius_tl: f32, @@ -165,6 +171,7 @@ pub struct LegacyRoundedTexPushConstants { pub pos: [[f32; 2]; 4], pub tex_pos: [[f32; 2]; 4], pub alpha: f32, + pub discard_threshold: f32, pub size_x: f32, pub size_y: f32, pub corner_radius_tl: f32, @@ -195,6 +202,15 @@ pub struct BlurCompositePushConstants { unsafe impl Packed for BlurCompositePushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct BlurPushConstants { + pub halfpixel: [f32; 2], + pub offset: f32, +} + +unsafe impl Packed for BlurPushConstants {} + impl VulkanDevice { pub(super) fn create_shader( self: &Rc, diff --git a/src/gfx_apis/vulkan/shaders/blur.vert b/src/gfx_apis/vulkan/shaders/blur.vert new file mode 100644 index 00000000..7e0fde54 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/blur.vert @@ -0,0 +1,26 @@ +#version 450 + +layout(location = 0) out vec2 v_texcoord; + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: + pos = vec2( 1.0, -1.0); + v_texcoord = vec2(1.0, 0.0); + break; + case 1: + pos = vec2(-1.0, -1.0); + v_texcoord = vec2(0.0, 0.0); + break; + case 2: + pos = vec2( 1.0, 1.0); + v_texcoord = vec2(1.0, 1.0); + break; + case 3: + pos = vec2(-1.0, 1.0); + v_texcoord = vec2(0.0, 1.0); + break; + } + gl_Position = vec4(pos, 0.0, 1.0); +} diff --git a/src/gfx_apis/vulkan/shaders/blur_down.frag b/src/gfx_apis/vulkan/shaders/blur_down.frag new file mode 100644 index 00000000..8afb493a --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/blur_down.frag @@ -0,0 +1,21 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D tex; + +layout(push_constant, std430) uniform Data { + vec2 halfpixel; + float offset; +} data; + +layout(location = 0) in vec2 v_texcoord; +layout(location = 0) out vec4 out_color; + +void main() { + vec2 hp = data.halfpixel * data.offset; + vec4 sum = textureLod(tex, v_texcoord, 0.0) * 4.0; + sum += textureLod(tex, v_texcoord - hp, 0.0); + sum += textureLod(tex, v_texcoord + hp, 0.0); + sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0); + sum += textureLod(tex, v_texcoord - vec2(hp.x, -hp.y), 0.0); + out_color = sum / 8.0; +} diff --git a/src/gfx_apis/vulkan/shaders/blur_up.frag b/src/gfx_apis/vulkan/shaders/blur_up.frag new file mode 100644 index 00000000..2397d687 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/blur_up.frag @@ -0,0 +1,24 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D tex; + +layout(push_constant, std430) uniform Data { + vec2 halfpixel; + float offset; +} data; + +layout(location = 0) in vec2 v_texcoord; +layout(location = 0) out vec4 out_color; + +void main() { + vec2 hp = data.halfpixel * data.offset; + vec4 sum = textureLod(tex, v_texcoord + vec2(-hp.x * 2.0, 0.0), 0.0); + sum += textureLod(tex, v_texcoord + vec2(-hp.x, hp.y), 0.0) * 2.0; + sum += textureLod(tex, v_texcoord + vec2(0.0, hp.y * 2.0), 0.0); + sum += textureLod(tex, v_texcoord + vec2(hp.x, hp.y), 0.0) * 2.0; + sum += textureLod(tex, v_texcoord + vec2(hp.x * 2.0, 0.0), 0.0); + sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0) * 2.0; + sum += textureLod(tex, v_texcoord + vec2(0.0, -hp.y * 2.0), 0.0); + sum += textureLod(tex, v_texcoord + vec2(-hp.x, -hp.y), 0.0) * 2.0; + out_color = sum / 12.0; +} diff --git a/src/gfx_apis/vulkan/shaders_bin/blur.vert.spv b/src/gfx_apis/vulkan/shaders_bin/blur.vert.spv new file mode 100644 index 00000000..6b57ef62 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/blur.vert.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv b/src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv new file mode 100644 index 00000000..d455ae52 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv b/src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv new file mode 100644 index 00000000..56b6fe73 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv differ diff --git a/src/gfx_apis/vulkan/shaders_hash.txt b/src/gfx_apis/vulkan/shaders_hash.txt index de378df5..2fde0603 100644 --- a/src/gfx_apis/vulkan/shaders_hash.txt +++ b/src/gfx_apis/vulkan/shaders_hash.txt @@ -1,6 +1,9 @@ 302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl +65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert 16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag 6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert +a04b2453c39efb018754fc25d45a369b5813359c55fad1c99020804cbb3a18e0 src/gfx_apis/vulkan/shaders/blur_down.frag +f6d51f3b5410387d1474529c44e71bfdc31ceb80174ea6e3e4c2df30d03f11c3 src/gfx_apis/vulkan/shaders/blur_up.frag b6a0df1e231fab533499329636b7a580384784418baee06c147af5fcc384cf5c src/gfx_apis/vulkan/shaders/eotfs.glsl 8a38df18851cd13884499820f26939fb7319f45d913d867f254d8118d59fb117 src/gfx_apis/vulkan/shaders/fill.common.glsl 21c488d12aa5ad2f109ec44cb856dfe837e02ea9025b5ed64439d742c17cbf30 src/gfx_apis/vulkan/shaders/fill.frag