diff --git a/README.md b/README.md
index 63cae92..e0b6560 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,8 @@ src/
├── main.rs
├── wallbash.rs
├── wayland.rs
-└── vulkan.rs
+├── vulkan.rs
+└── filters.rs
```
The core project is structured in simple modules:
@@ -64,6 +65,7 @@ The core project is structured in simple modules:
- `wallbash.rs` The core daemon module. It manages the IPC listener, handles incoming commands, and orchestrates the wallpaper loading and rendering process.
- `wayland.rs` Handles the Wayland integration. It creates a Wayland surface, binds to the layer shell protocol, and sets up the layer surface for the wallpaper.
- `vulkan.rs` Manages the Vulkan rendering pipeline. It initializes the Vulkan instance, selects a physical device (preferring a discrete GPU), creates a swapchain, and renders the wallpaper image.
+- `filters.rs` – Implements image filters and post‑processing effects, including dynamic background blur, scaling algorithms, and other visual transformations.
###### *
// HyDE
*

diff --git a/src/filters.rs b/src/filters.rs
new file mode 100644
index 0000000..e4bbaab
--- /dev/null
+++ b/src/filters.rs
@@ -0,0 +1,304 @@
+// --------------------------------------------------------------------- / tittu
+// wallbash
+// a filter module for HyDE
+//
+
+
+// --------------------------------------------------------------------- / imports
+
+use crate::vulkan::{VulkanCore, VulkanTexture, create_texture};
+use std::error::Error;
+use ash::vk;
+
+
+// --------------------------------------------------------------------- / generic compute
+
+pub fn compute_pipeline(
+ device: &ash::Device,
+ spv: &[u32],
+ bindings: &[vk::DescriptorSetLayoutBinding],
+) -> Result<(vk::ShaderModule, vk::Pipeline, vk::DescriptorSetLayout), Box> {
+
+ // Shader module
+ let create_info = vk::ShaderModuleCreateInfo::default().code(spv);
+ let module = unsafe { device.create_shader_module(&create_info, None)? };
+
+ // Descriptor set layout
+ let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(bindings);
+ let desc_layout = unsafe { device.create_descriptor_set_layout(&layout_info, None)? };
+
+ // Pipeline layout
+ let set_layouts = [desc_layout];
+ let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
+ let pipeline_layout = unsafe { device.create_pipeline_layout(&pipeline_layout_info, None)? };
+
+ // Compute pipeline
+ let stage = vk::PipelineShaderStageCreateInfo::default()
+ .stage(vk::ShaderStageFlags::COMPUTE)
+ .module(module)
+ .name(c"main");
+ let pipeline_info = vk::ComputePipelineCreateInfo::default()
+ .stage(stage)
+ .layout(pipeline_layout);
+ let pipelines = unsafe {
+ device.create_compute_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
+ }.expect("Failed to create compute pipeline");
+ let pipeline = pipelines[0];
+
+ unsafe { device.destroy_pipeline_layout(pipeline_layout, None) };
+ Ok((module, pipeline, desc_layout))
+}
+
+
+// --------------------------------------------------------------------- / build filter
+
+pub fn filter_pipeline(
+ device: &ash::Device,
+ filter: &str,
+) -> Result<(vk::ShaderModule, vk::Pipeline, vk::DescriptorSetLayout), Box> {
+ match filter {
+ "blur" => {
+ let blur_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/blur.comp.spv"));
+ let blur_words: Vec = blur_bytes
+ .chunks_exact(4)
+ .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
+ .collect();
+
+ let bindings = [
+ vk::DescriptorSetLayoutBinding::default()
+ .binding(0)
+ .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
+ .descriptor_count(1)
+ .stage_flags(vk::ShaderStageFlags::COMPUTE),
+ vk::DescriptorSetLayoutBinding::default()
+ .binding(1)
+ .descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
+ .descriptor_count(1)
+ .stage_flags(vk::ShaderStageFlags::COMPUTE),
+ ];
+ compute_pipeline(device, &blur_words, &bindings)
+ }
+ _ => Err(format!("unknown filter: {}", filter).into()),
+ }
+}
+
+
+// --------------------------------------------------------------------- / blur texture
+
+pub fn blur_texture(
+ vk_core: &VulkanCore,
+ input_texture: &VulkanTexture,
+ width: u32,
+ height: u32,
+ blur_pipeline: vk::Pipeline,
+ blur_desc_layout: vk::DescriptorSetLayout,
+) -> Result> {
+
+ // create the output texture (same size, with STORAGE + TRANSFER_SRC)
+ let (output_image, output_memory) = create_texture(
+ &vk_core.instance,
+ &vk_core.device,
+ vk_core.physical_device,
+ width,
+ height,
+ vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::STORAGE,
+ vk::Format::R8G8B8A8_UNORM,
+ )?;
+
+ // create image views and a sampler
+ let input_view = {
+ let view_info = vk::ImageViewCreateInfo::default()
+ .image(input_texture.image)
+ .view_type(vk::ImageViewType::TYPE_2D)
+ .format(vk::Format::R8G8B8A8_SRGB)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ });
+ unsafe { vk_core.device.create_image_view(&view_info, None)? }
+ };
+ let output_view = {
+ let view_info = vk::ImageViewCreateInfo::default()
+ .image(output_image)
+ .view_type(vk::ImageViewType::TYPE_2D)
+ .format(vk::Format::R8G8B8A8_UNORM)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ });
+ unsafe { vk_core.device.create_image_view(&view_info, None)? }
+ };
+ let sampler_info = vk::SamplerCreateInfo::default()
+ .mag_filter(vk::Filter::LINEAR)
+ .min_filter(vk::Filter::LINEAR)
+ .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
+ .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
+ .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
+ let sampler = unsafe { vk_core.device.create_sampler(&sampler_info, None)? };
+
+ // descriptor set (bindings 0=sampler, 1=storage image)
+ let pool_sizes = [
+ vk::DescriptorPoolSize {
+ ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
+ descriptor_count: 1,
+ },
+ vk::DescriptorPoolSize {
+ ty: vk::DescriptorType::STORAGE_IMAGE,
+ descriptor_count: 1,
+ },
+ ];
+ let pool_info = vk::DescriptorPoolCreateInfo::default()
+ .max_sets(1)
+ .pool_sizes(&pool_sizes);
+ let desc_pool = unsafe { vk_core.device.create_descriptor_pool(&pool_info, None)? };
+
+ let set_layouts = [blur_desc_layout];
+ let alloc_info = vk::DescriptorSetAllocateInfo::default()
+ .descriptor_pool(desc_pool)
+ .set_layouts(&set_layouts);
+ let desc_sets = unsafe { vk_core.device.allocate_descriptor_sets(&alloc_info)? };
+ let desc_set = desc_sets[0];
+
+ let input_image_info = vk::DescriptorImageInfo::default()
+ .sampler(sampler)
+ .image_view(input_view)
+ .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
+ let output_image_info = vk::DescriptorImageInfo::default()
+ .image_view(output_view)
+ .image_layout(vk::ImageLayout::GENERAL);
+ let input_image_infos = [input_image_info];
+ let output_image_infos = [output_image_info];
+ let write_descriptors = [
+ vk::WriteDescriptorSet::default()
+ .dst_set(desc_set)
+ .dst_binding(0)
+ .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
+ .image_info(&input_image_infos),
+ vk::WriteDescriptorSet::default()
+ .dst_set(desc_set)
+ .dst_binding(1)
+ .descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
+ .image_info(&output_image_infos),
+ ];
+ unsafe { vk_core.device.update_descriptor_sets(&write_descriptors, &[]) };
+
+ // pipeline layout (no push constants, same descriptor layout as the pipeline)
+ let set_layouts2 = [blur_desc_layout];
+ let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default()
+ .set_layouts(&set_layouts2);
+ let pipeline_layout = unsafe { vk_core.device.create_pipeline_layout(&pipeline_layout_info, None)? };
+
+ // record compute commands
+ unsafe {
+ vk_core.device.reset_command_pool(vk_core.command_pool, vk::CommandPoolResetFlags::empty())?;
+ }
+ let begin_info = vk::CommandBufferBeginInfo::default()
+ .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
+ unsafe { vk_core.device.begin_command_buffer(vk_core.command_buffer, &begin_info) }?;
+
+ // transition output to GENERAL
+ let barrier = vk::ImageMemoryBarrier::default()
+ .image(output_image)
+ .old_layout(vk::ImageLayout::UNDEFINED)
+ .new_layout(vk::ImageLayout::GENERAL)
+ .src_access_mask(vk::AccessFlags::empty())
+ .dst_access_mask(vk::AccessFlags::SHADER_WRITE)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ });
+ unsafe {
+ vk_core.device.cmd_pipeline_barrier(
+ vk_core.command_buffer,
+ vk::PipelineStageFlags::TOP_OF_PIPE,
+ vk::PipelineStageFlags::COMPUTE_SHADER,
+ vk::DependencyFlags::empty(),
+ &[],
+ &[],
+ &[barrier],
+ );
+ }
+
+ // bind pipeline and descriptor set
+ unsafe {
+ vk_core.device.cmd_bind_pipeline(
+ vk_core.command_buffer,
+ vk::PipelineBindPoint::COMPUTE,
+ blur_pipeline,
+ );
+ vk_core.device.cmd_bind_descriptor_sets(
+ vk_core.command_buffer,
+ vk::PipelineBindPoint::COMPUTE,
+ pipeline_layout,
+ 0,
+ &[desc_set],
+ &[],
+ );
+ }
+
+ // dispatch
+ let group_x = (width + 15) / 16;
+ let group_y = (height + 15) / 16;
+ unsafe { vk_core.device.cmd_dispatch(vk_core.command_buffer, group_x, group_y, 1) };
+
+ // transition output to SHADER_READ_ONLY_OPTIMAL for later blitting
+ let barrier2 = vk::ImageMemoryBarrier::default()
+ .image(output_image)
+ .old_layout(vk::ImageLayout::GENERAL)
+ .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+ .src_access_mask(vk::AccessFlags::SHADER_WRITE)
+ .dst_access_mask(vk::AccessFlags::SHADER_READ)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ });
+ unsafe {
+ vk_core.device.cmd_pipeline_barrier(
+ vk_core.command_buffer,
+ vk::PipelineStageFlags::COMPUTE_SHADER,
+ vk::PipelineStageFlags::FRAGMENT_SHADER,
+ vk::DependencyFlags::empty(),
+ &[],
+ &[],
+ &[barrier2],
+ );
+ }
+ unsafe { vk_core.device.end_command_buffer(vk_core.command_buffer) }?;
+
+ // submit
+ let command_buffers = [vk_core.command_buffer];
+ let submit_info = vk::SubmitInfo::default().command_buffers(&command_buffers);
+ let fence = unsafe { vk_core.device.create_fence(&vk::FenceCreateInfo::default(), None) }?;
+ unsafe { vk_core.device.queue_submit(vk_core.graphics_queue, &[submit_info], fence) }?;
+ unsafe { vk_core.device.wait_for_fences(&[fence], true, u64::MAX) }?;
+ unsafe { vk_core.device.destroy_fence(fence, None) };
+
+ // cleanup transient objects
+ unsafe {
+ vk_core.device.destroy_sampler(sampler, None);
+ vk_core.device.destroy_image_view(input_view, None);
+ vk_core.device.destroy_image_view(output_view, None);
+ vk_core.device.destroy_descriptor_pool(desc_pool, None);
+ vk_core.device.destroy_pipeline_layout(pipeline_layout, None);
+ }
+
+ Ok(VulkanTexture {
+ image: output_image,
+ _memory: output_memory,
+ width,
+ height,
+ })
+}
+
diff --git a/src/main.rs b/src/main.rs
index 19f73f3..5bf67ab 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@
pub mod wallbash;
pub mod wayland;
pub mod vulkan;
+pub mod filters;
use std::{
env, io::Write,
os::unix::net::UnixStream,
diff --git a/src/vulkan.rs b/src/vulkan.rs
index 54fc3f6..37ea84e 100644
--- a/src/vulkan.rs
+++ b/src/vulkan.rs
@@ -31,9 +31,6 @@ pub struct VulkanCore {
pub graphics_queue: vk::Queue,
pub command_pool: vk::CommandPool,
pub command_buffer: vk::CommandBuffer,
- pub blur_module: vk::ShaderModule,
- pub blur_pipeline: vk::Pipeline,
- pub blur_desc_layout: vk::DescriptorSetLayout,
}
pub struct VulkanSurfchain {
@@ -45,15 +42,8 @@ pub struct VulkanSurfchain {
pub struct VulkanTexture {
pub image: vk::Image,
pub _memory: vk::DeviceMemory,
-}
-
-pub enum PipelineType<'a> {
- Compute,
- Graphics {
- vert_spv: &'a [u32],
- extent: vk::Extent2D,
- render_pass: vk::RenderPass,
- },
+ pub width: u32,
+ pub height: u32,
}
@@ -137,35 +127,6 @@ pub fn vulkan_core() -> Result> {
let command_buffer = unsafe { device.allocate_command_buffers(&alloc_info)? }[0];
println!("[v] command pool {:?} >> command buffer {:?}", command_pool, command_buffer);
- // build blur compute pipeline
- let blur_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/blur.comp.spv"));
- let blur_words = unsafe {
- std::slice::from_raw_parts(blur_bytes.as_ptr() as *const u32, blur_bytes.len() / 4)
- };
-
- // descriptor layout: binding 0 = sampler, binding 1 = storage image
- let bindings = [
- vk::DescriptorSetLayoutBinding::default()
- .binding(0)
- .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
- .descriptor_count(1)
- .stage_flags(vk::ShaderStageFlags::COMPUTE),
- vk::DescriptorSetLayoutBinding::default()
- .binding(1)
- .descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
- .descriptor_count(1)
- .stage_flags(vk::ShaderStageFlags::COMPUTE),
- ];
- let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
- let blur_desc_layout = unsafe { device.create_descriptor_set_layout(&layout_info, None)? };
-
- let (blur_module, blur_pipeline) = build_pipeline(
- &device,
- PipelineType::Compute,
- &blur_words,
- blur_desc_layout,
- )?;
-
Ok(VulkanCore {
entry,
instance,
@@ -175,140 +136,10 @@ pub fn vulkan_core() -> Result> {
graphics_queue,
command_pool,
command_buffer,
- blur_module: blur_module[0],
- blur_pipeline,
- blur_desc_layout,
})
}
-// --------------------------------------------------------------------- / build pipeline
-
-fn build_pipeline(
- device: &ash::Device,
- pipeline_type: PipelineType,
- frag_spv: &[u32],
- desc_layout: vk::DescriptorSetLayout,
-) -> Result<(Vec, vk::Pipeline), Box> {
- let mut modules = Vec::new();
-
- let stages = match pipeline_type {
- PipelineType::Compute => {
- let module = load_shader(device, frag_spv)?;
- modules.push(module);
- let stage = vk::PipelineShaderStageCreateInfo::default()
- .stage(vk::ShaderStageFlags::COMPUTE)
- .module(module)
- .name(c"main");
- vec![stage]
- }
- PipelineType::Graphics { vert_spv, extent: _, .. } => {
- let vert_module = load_shader(device, vert_spv)?;
- let frag_module = load_shader(device, frag_spv)?;
- modules.push(vert_module);
- modules.push(frag_module);
- let vert_stage = vk::PipelineShaderStageCreateInfo::default()
- .stage(vk::ShaderStageFlags::VERTEX)
- .module(vert_module)
- .name(c"main");
- let frag_stage = vk::PipelineShaderStageCreateInfo::default()
- .stage(vk::ShaderStageFlags::FRAGMENT)
- .module(frag_module)
- .name(c"main");
- vec![vert_stage, frag_stage]
- }
- };
-
- let set_layouts = [desc_layout];
- let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default()
- .set_layouts(&set_layouts);
- let pipeline_layout = unsafe { device.create_pipeline_layout(&pipeline_layout_info, None)? };
-
- let pipeline = match pipeline_type {
- PipelineType::Compute => {
- let info = vk::ComputePipelineCreateInfo::default()
- .stage(stages[0]) // pass by value, not reference
- .layout(pipeline_layout);
- let pipelines = unsafe {
- device.create_compute_pipelines(vk::PipelineCache::null(), &[info], None)
- }.unwrap();
- pipelines[0]
- }
- PipelineType::Graphics { extent, render_pass, .. } => {
- let vertex_input = vk::PipelineVertexInputStateCreateInfo::default();
- let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
- .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
-
- let viewport = vk::Viewport {
- x: 0.0, y: 0.0,
- width: extent.width as f32,
- height: extent.height as f32,
- min_depth: 0.0, max_depth: 1.0,
- };
- let scissor = vk::Rect2D {
- offset: vk::Offset2D { x: 0, y: 0 },
- extent,
- };
- let viewports = [viewport];
- let scissors = [scissor];
- let viewport_state = vk::PipelineViewportStateCreateInfo::default()
- .viewports(&viewports)
- .scissors(&scissors);
-
- let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
- .polygon_mode(vk::PolygonMode::FILL)
- .cull_mode(vk::CullModeFlags::NONE)
- .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
- .line_width(1.0);
-
- let multisample = vk::PipelineMultisampleStateCreateInfo::default()
- .rasterization_samples(vk::SampleCountFlags::TYPE_1);
-
- let color_blend = vk::PipelineColorBlendAttachmentState::default()
- .color_write_mask(vk::ColorComponentFlags::RGBA)
- .blend_enable(false);
- let color_blends = [color_blend];
- let blend_state = vk::PipelineColorBlendStateCreateInfo::default()
- .attachments(&color_blends);
-
- let dynamic_state = vk::PipelineDynamicStateCreateInfo::default()
- .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
-
- let info = vk::GraphicsPipelineCreateInfo::default()
- .stages(&stages)
- .vertex_input_state(&vertex_input)
- .input_assembly_state(&input_assembly)
- .viewport_state(&viewport_state)
- .rasterization_state(&rasterizer)
- .multisample_state(&multisample)
- .color_blend_state(&blend_state)
- .dynamic_state(&dynamic_state)
- .layout(pipeline_layout)
- .render_pass(render_pass)
- .subpass(0);
-
- let pipelines = unsafe {
- device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)
- }.unwrap();
- pipelines[0]
- }
- };
-
- unsafe { device.destroy_pipeline_layout(pipeline_layout, None) };
-
- Ok((modules, pipeline))
-}
-
-
-// --------------------------------------------------------------------- / load shader
-
-fn load_shader(device: &ash::Device, spv: &[u32]) -> Result> {
- let create_info = vk::ShaderModuleCreateInfo::default().code(spv);
- let module = unsafe { device.create_shader_module(&create_info, None)? };
- Ok(module)
-}
-
-
// --------------------------------------------------------------------- / surface swapchain
pub fn vulkan_surfchain(
@@ -456,6 +287,8 @@ pub fn vulkan_pipeline(
Ok(VulkanTexture {
image: texture,
_memory: vram,
+ width,
+ height,
})
}
@@ -680,195 +513,6 @@ pub fn load_texture(
}
-// --------------------------------------------------------------------- / blur texture
-
-pub fn blur_texture(
- vk_core: &VulkanCore,
- input_texture: &VulkanTexture,
- width: u32,
- height: u32,
-) -> Result> {
-
- // create the output texture (same size, with STORAGE + TRANSFER_SRC)
- let (output_image, output_memory) = create_texture(
- &vk_core.instance,
- &vk_core.device,
- vk_core.physical_device,
- width, height,
- vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::STORAGE,
- vk::Format::R8G8B8A8_UNORM,
- )?;
-
- // create image views and a sampler
- let input_view = {
- let view_info = vk::ImageViewCreateInfo::default()
- .image(input_texture.image)
- .view_type(vk::ImageViewType::TYPE_2D)
- .format(vk::Format::R8G8B8A8_SRGB)
- .subresource_range(vk::ImageSubresourceRange {
- aspect_mask: vk::ImageAspectFlags::COLOR,
- base_mip_level: 0, level_count: 1,
- base_array_layer: 0, layer_count: 1,
- });
- unsafe { vk_core.device.create_image_view(&view_info, None)? }
- };
- let output_view = {
- let view_info = vk::ImageViewCreateInfo::default()
- .image(output_image)
- .view_type(vk::ImageViewType::TYPE_2D)
- .format(vk::Format::R8G8B8A8_UNORM)
- .subresource_range(vk::ImageSubresourceRange {
- aspect_mask: vk::ImageAspectFlags::COLOR,
- base_mip_level: 0, level_count: 1,
- base_array_layer: 0, layer_count: 1,
- });
- unsafe { vk_core.device.create_image_view(&view_info, None)? }
- };
- let sampler_info = vk::SamplerCreateInfo::default()
- .mag_filter(vk::Filter::LINEAR)
- .min_filter(vk::Filter::LINEAR)
- .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
- .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
- .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
- let sampler = unsafe { vk_core.device.create_sampler(&sampler_info, None)? };
-
- // descriptor set (bindings 0=sampler, 1=storage image)
- let pool_sizes = [
- vk::DescriptorPoolSize { ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, descriptor_count: 1 },
- vk::DescriptorPoolSize { ty: vk::DescriptorType::STORAGE_IMAGE, descriptor_count: 1 },
- ];
- let pool_info = vk::DescriptorPoolCreateInfo::default()
- .max_sets(1)
- .pool_sizes(&pool_sizes);
- let desc_pool = unsafe { vk_core.device.create_descriptor_pool(&pool_info, None)? };
-
- let set_layouts = [vk_core.blur_desc_layout];
- let alloc_info = vk::DescriptorSetAllocateInfo::default()
- .descriptor_pool(desc_pool)
- .set_layouts(&set_layouts);
- let desc_sets = unsafe { vk_core.device.allocate_descriptor_sets(&alloc_info)? };
- let desc_set = desc_sets[0];
-
- let input_image_info = vk::DescriptorImageInfo::default()
- .sampler(sampler)
- .image_view(input_view)
- .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
- let output_image_info = vk::DescriptorImageInfo::default()
- .image_view(output_view)
- .image_layout(vk::ImageLayout::GENERAL);
- let input_image_infos = [input_image_info];
- let output_image_infos = [output_image_info];
- let write_descriptors = [
- vk::WriteDescriptorSet::default()
- .dst_set(desc_set)
- .dst_binding(0)
- .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
- .image_info(&input_image_infos),
- vk::WriteDescriptorSet::default()
- .dst_set(desc_set)
- .dst_binding(1)
- .descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
- .image_info(&output_image_infos),
- ];
- unsafe { vk_core.device.update_descriptor_sets(&write_descriptors, &[]) };
-
- // pipeline layout (no push constants, same descriptor layout as the pipeline)
- let set_layouts2 = [vk_core.blur_desc_layout];
- let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default()
- .set_layouts(&set_layouts2);
- let pipeline_layout = unsafe { vk_core.device.create_pipeline_layout(&pipeline_layout_info, None)? };
-
- // record compute commands
- unsafe { vk_core.device.reset_command_pool(vk_core.command_pool, vk::CommandPoolResetFlags::empty()) }?;
- let begin_info = vk::CommandBufferBeginInfo::default()
- .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
- unsafe { vk_core.device.begin_command_buffer(vk_core.command_buffer, &begin_info) }?;
-
- // transition output to GENERAL
- let barrier = vk::ImageMemoryBarrier::default()
- .image(output_image)
- .old_layout(vk::ImageLayout::UNDEFINED)
- .new_layout(vk::ImageLayout::GENERAL)
- .src_access_mask(vk::AccessFlags::empty())
- .dst_access_mask(vk::AccessFlags::SHADER_WRITE)
- .subresource_range(vk::ImageSubresourceRange {
- aspect_mask: vk::ImageAspectFlags::COLOR,
- base_mip_level: 0, level_count: 1,
- base_array_layer: 0, layer_count: 1,
- });
- unsafe {
- vk_core.device.cmd_pipeline_barrier(
- vk_core.command_buffer,
- vk::PipelineStageFlags::TOP_OF_PIPE,
- vk::PipelineStageFlags::COMPUTE_SHADER,
- vk::DependencyFlags::empty(),
- &[], &[], &[barrier],
- );
- }
-
- // bind pipeline and descriptor set
- unsafe {
- vk_core.device.cmd_bind_pipeline(vk_core.command_buffer, vk::PipelineBindPoint::COMPUTE, vk_core.blur_pipeline);
- vk_core.device.cmd_bind_descriptor_sets(
- vk_core.command_buffer,
- vk::PipelineBindPoint::COMPUTE,
- pipeline_layout,
- 0, &[desc_set], &[],
- );
- }
-
- // dispatch
- let group_x = (width + 15) / 16;
- let group_y = (height + 15) / 16;
- unsafe { vk_core.device.cmd_dispatch(vk_core.command_buffer, group_x, group_y, 1); }
-
- // transition output to SHADER_READ_ONLY_OPTIMAL for later blitting
- let barrier2 = vk::ImageMemoryBarrier::default()
- .image(output_image)
- .old_layout(vk::ImageLayout::GENERAL)
- .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
- .src_access_mask(vk::AccessFlags::SHADER_WRITE)
- .dst_access_mask(vk::AccessFlags::SHADER_READ)
- .subresource_range(vk::ImageSubresourceRange {
- aspect_mask: vk::ImageAspectFlags::COLOR,
- base_mip_level: 0, level_count: 1,
- base_array_layer: 0, layer_count: 1,
- });
- unsafe {
- vk_core.device.cmd_pipeline_barrier(
- vk_core.command_buffer,
- vk::PipelineStageFlags::COMPUTE_SHADER,
- vk::PipelineStageFlags::FRAGMENT_SHADER,
- vk::DependencyFlags::empty(),
- &[], &[], &[barrier2],
- );
- }
- unsafe { vk_core.device.end_command_buffer(vk_core.command_buffer) }?;
-
- // submit
- let command_buffers = [vk_core.command_buffer];
- let submit_info = vk::SubmitInfo::default().command_buffers(&command_buffers);
- let fence = unsafe { vk_core.device.create_fence(&vk::FenceCreateInfo::default(), None) }?;
- unsafe { vk_core.device.queue_submit(vk_core.graphics_queue, &[submit_info], fence) }?;
- unsafe { vk_core.device.wait_for_fences(&[fence], true, u64::MAX) }?;
- unsafe { vk_core.device.destroy_fence(fence, None) };
-
- // cleanup transient objects
- unsafe {
- vk_core.device.destroy_sampler(sampler, None);
- vk_core.device.destroy_image_view(input_view, None);
- vk_core.device.destroy_image_view(output_view, None);
- vk_core.device.destroy_descriptor_pool(desc_pool, None);
- vk_core.device.destroy_pipeline_layout(pipeline_layout, None);
- }
-
- Ok(VulkanTexture {
- image: output_image,
- _memory: output_memory,
- })
-}
-
-
// --------------------------------------------------------------------- / draw wallpaper
pub fn draw_wallpaper(
@@ -1201,21 +845,46 @@ fn mode_set(
}
-// --------------------------------------------------------------------- / destroy surfchain
+// --------------------------------------------------------------------- / destroy level
-pub fn destroy_surfchain(
- entry: &ash::Entry,
- instance: &ash::Instance,
- device: &ash::Device,
- surfchain: &mut VulkanSurfchain,
+pub fn destroy_wallbash(
+ vk_core: &VulkanCore,
+ surfchain: Option<&mut VulkanSurfchain>,
+ filter_module: Option,
+ filter_pipeline: Option,
+ filter_desc_layout: Option,
+ level: u32,
) {
- let swapchain_loader = ash::khr::swapchain::Device::new(instance, device);
- unsafe {
- swapchain_loader.destroy_swapchain(surfchain.swapchain, None);
+
+ // Destroy filter resources if provided and level ≥ 0
+ if let (Some(module), Some(pipeline), Some(desc)) = (filter_module, filter_pipeline, filter_desc_layout) {
+ unsafe {
+ vk_core.device.destroy_pipeline(pipeline, None);
+ vk_core.device.destroy_descriptor_set_layout(desc, None);
+ vk_core.device.destroy_shader_module(module, None);
+ }
}
- let surface_loader = surface::Instance::new(entry, instance);
- unsafe {
- surface_loader.destroy_surface(surfchain.surface, None);
+
+ // Destroy swapchain and surface (level ≥ 1)
+ if level >= 1 {
+ if let Some(sc) = surfchain {
+ unsafe {
+ let swapchain_loader = ash::khr::swapchain::Device::new(&vk_core.instance, &vk_core.device);
+ swapchain_loader.destroy_swapchain(sc.swapchain, None);
+ let surface_loader = ash::khr::surface::Instance::new(&vk_core.entry, &vk_core.instance);
+ surface_loader.destroy_surface(sc.surface, None);
+ }
+ }
+ }
+
+ // Destroy core Vulkan objects (level ≥ 2)
+ if level >= 2 {
+ unsafe {
+ vk_core.device.device_wait_idle().expect("device wait failed");
+ vk_core.device.destroy_command_pool(vk_core.command_pool, None);
+ vk_core.device.destroy_device(None);
+ vk_core.instance.destroy_instance(None);
+ }
}
}
diff --git a/src/wallbash.rs b/src/wallbash.rs
index a5bb412..4210c2e 100644
--- a/src/wallbash.rs
+++ b/src/wallbash.rs
@@ -6,7 +6,7 @@
// --------------------------------------------------------------------- / imports
-use crate::{vulkan, wayland};
+use crate::{vulkan, wayland, filters};
use std::{
os::unix::net::{UnixListener, UnixStream},
io::{BufRead, BufReader},
@@ -65,6 +65,7 @@ fn set_wallpaper(
anchor_x: f32,
anchor_y: f32,
mode: &str,
+ effect: impl FnOnce(&vulkan::VulkanTexture) -> Option,
) -> Result<(), Box> {
// load the wallpaper
@@ -90,20 +91,8 @@ fn set_wallpaper(
*wallpaper = Some(texture);
// create a blurred version for fit/original modes
- let blurred_bg = if mode != "cover" {
- let bg = vulkan::blur_texture(
- vk_core,
- wallpaper.as_ref().unwrap(),
- img.width(),
- img.height(),
- )?;
- Some(bg)
- } else {
- None
- };
-
- // prepare the background parameter for draw_wallpaper
- let blur_bg = blurred_bg.as_ref().map(|b| (b.image, img.width(), img.height()));
+ let background_texture = effect(wallpaper.as_ref().unwrap());
+ let background = background_texture.as_ref().map(|b| (b.image, b.width, b.height));
// draw the wallpaper
vulkan::draw_wallpaper(
@@ -121,12 +110,12 @@ fn set_wallpaper(
layer_height,
anchor_x,
anchor_y,
- blur_bg,
+ background,
mode,
)?;
// destroy the temporary blurred texture (if any)
- if let Some(bg) = blurred_bg {
+ if let Some(bg) = background_texture {
unsafe {
vk_core.device.destroy_image(bg.image, None);
vk_core.device.free_memory(bg._memory, None);
@@ -137,6 +126,33 @@ fn set_wallpaper(
}
+// --------------------------------------------------------------------- / surfchain
+
+fn set_surfchain(
+ vk_core: &vulkan::VulkanCore,
+ wl_core: &wayland::WaylandCore,
+ old: Option<&mut vulkan::VulkanSurfchain>,
+) -> Result> {
+
+ // destroy only the swapchain (level 1) – no filter resources
+ if let Some(old_sc) = old {
+ vulkan::destroy_wallbash(vk_core, Some(old_sc), None, None, None, 1);
+ }
+
+ vulkan::vulkan_surfchain(
+ &vk_core.entry,
+ &vk_core.instance,
+ vk_core.physical_device,
+ vk_core.graphics_family_index,
+ &vk_core.device,
+ &wl_core.display,
+ &wl_core.surface,
+ wl_core.state.layer_width,
+ wl_core.state.layer_height,
+ )
+}
+
+
// --------------------------------------------------------------------- / daemon
pub fn run(socket_path: &str) -> Result<(), Box> {
@@ -153,17 +169,8 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
// init vulkan
let vk_core = vulkan::vulkan_core()?;
- let mut vk_surfchain = vulkan::vulkan_surfchain(
- &vk_core.entry,
- &vk_core.instance,
- vk_core.physical_device,
- vk_core.graphics_family_index,
- &vk_core.device,
- &wl_core.display,
- &wl_core.surface,
- wl_core.state.layer_width,
- wl_core.state.layer_height,
- )?;
+ let (blur_module, blur_pipeline, blur_desc_layout) = filters::filter_pipeline(&vk_core.device, "blur")?;
+ let mut vk_surfchain = set_surfchain(&vk_core, &wl_core, None)?;
// blank surface until a command arrives
let mut wallpaper: Option = None;
@@ -191,8 +198,25 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
let resolved = std::fs::canonicalize(&path)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or(path);
+ println!("[wallbash] loading '{}' ({} | ax:{:?} | ay:{:?})", resolved, mode, anchor_x, anchor_y);
+
+ // apply blur for non cover mode
+ let effect = |tex: &vulkan::VulkanTexture| {
+ if mode != "cover" {
+ filters::blur_texture(
+ &vk_core,
+ tex,
+ tex.width,
+ tex.height,
+ blur_pipeline,
+ blur_desc_layout,
+ )
+ .ok()
+ } else {
+ None
+ }
+ };
- println!("[wallbash] loading '{}' ({}-{}x{})", resolved, mode, anchor_x, anchor_y);
match set_wallpaper(
&resolved,
&vk_core,
@@ -203,31 +227,14 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
anchor_x,
anchor_y,
&mode,
+ effect,
) {
Ok(()) => println!("[wallbash] wallpaper set."),
Err(e) if e.to_string().contains("out of date") => {
println!("[wallbash] swapchain out of date, recreating...");
// destroy old swapchain and surface
- vulkan::destroy_surfchain(
- &vk_core.entry,
- &vk_core.instance,
- &vk_core.device,
- &mut vk_surfchain,
- );
-
- // create a new one with the current layer dimensions
- vk_surfchain = match vulkan::vulkan_surfchain(
- &vk_core.entry,
- &vk_core.instance,
- vk_core.physical_device,
- vk_core.graphics_family_index,
- &vk_core.device,
- &wl_core.display,
- &wl_core.surface,
- wl_core.state.layer_width,
- wl_core.state.layer_height,
- ) {
+ vk_surfchain = match set_surfchain(&vk_core, &wl_core, Some(&mut vk_surfchain)) {
Ok(sc) => sc,
Err(e2) => {
eprintln!("[wallbash] failed to recreate swapchain {}", e2);
@@ -246,6 +253,7 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
anchor_x,
anchor_y,
&mode,
+ effect,
) {
eprintln!("[wallbash] error after swapchain recreation {}", e3);
}
@@ -256,7 +264,6 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
eprintln!("[wallbash] unknown {}", raw);
}
}
-
std::thread::sleep(std::time::Duration::from_millis(25));
}
@@ -268,13 +275,14 @@ pub fn run(socket_path: &str) -> Result<(), Box> {
vk_core.device.free_memory(tex._memory, None);
}
}
- vulkan::destroy_surfchain(&vk_core.entry, &vk_core.instance, &vk_core.device, &mut vk_surfchain);
- unsafe { vk_core.device.destroy_command_pool(vk_core.command_pool, None); }
- unsafe { vk_core.device.destroy_pipeline(vk_core.blur_pipeline, None); }
- unsafe { vk_core.device.destroy_descriptor_set_layout(vk_core.blur_desc_layout, None); }
- unsafe { vk_core.device.destroy_shader_module(vk_core.blur_module, None); }
- unsafe { vk_core.device.destroy_device(None); }
- unsafe { vk_core.instance.destroy_instance(None); }
+ vulkan::destroy_wallbash(
+ &vk_core,
+ Some(&mut vk_surfchain),
+ Some(blur_module),
+ Some(blur_pipeline),
+ Some(blur_desc_layout),
+ 2,
+ );
println!("[wallbash] daemon stopped.");
Ok(())