From 15ae18a1015849d93efdce5fa716d303c137bfe8 Mon Sep 17 00:00:00 2001 From: tittu Date: Sat, 13 Jun 2026 23:58:12 +0530 Subject: [PATCH] adding blur pipeline and code cleanup --- Cargo.lock | 70 +++++- Cargo.toml | 3 + build.rs | 18 ++ shaders/blur.comp | 25 ++ src/main.rs | 99 ++++---- src/vulkan.rs | 571 ++++++++++++++++++++++++++++++++++++++-------- src/wallbashed.rs | 31 ++- src/wayland.rs | 12 +- 8 files changed, 666 insertions(+), 163 deletions(-) create mode 100644 build.rs create mode 100644 shaders/blur.comp diff --git a/Cargo.lock b/Cargo.lock index 0469366..e8b7ea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,9 +169,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -185,6 +185,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -824,6 +833,15 @@ version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustix" version = "1.1.4" @@ -849,6 +867,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "shaderc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e07913ada18607bb60d12431cbe3358d3bbebbe95948e1618851dc01e63b7b" +dependencies = [ + "libc", + "shaderc-sys", +] + +[[package]] +name = "shaderc-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73120d240fe22196300f39ca8547ca2d014960f27b19b47b21288b396272f7f7" +dependencies = [ + "cmake", + "libc", + "roxmltree", +] + [[package]] name = "shlex" version = "2.0.1" @@ -950,6 +989,7 @@ version = "0.1.0" dependencies = [ "ash", "image", + "shaderc", "wayland-backend", "wayland-client", "wayland-protocols-wlr", @@ -957,18 +997,18 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -979,9 +1019,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -989,9 +1029,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -1002,9 +1042,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] @@ -1109,6 +1149,12 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "y4m" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index ac96950..7dadea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,6 @@ image = "0.25" wayland-client = "0.31.14" wayland-protocols-wlr = { version = "0.3", features = ["client"] } wayland-backend = { version = "0.3", features = ["client_system"] } + +[build-dependencies] +shaderc = "0.8" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d7e3f23 --- /dev/null +++ b/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::fs; +use std::path::Path; + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let path = Path::new("shaders").join("blur.comp"); + let source = fs::read_to_string(&path).expect("Failed to read blur.comp"); + let compiler = shaderc::Compiler::new().unwrap(); + let mut options = shaderc::CompileOptions::new().unwrap(); + options.set_optimization_level(shaderc::OptimizationLevel::Performance); + let result = compiler + .compile_into_spirv(&source, shaderc::ShaderKind::Compute, "blur.comp", "main", Some(&options)) + .unwrap(); + let out_name = Path::new(&out_dir).join("blur.comp.spv"); + fs::write(out_name, result.as_binary_u8()).unwrap(); +} + diff --git a/shaders/blur.comp b/shaders/blur.comp new file mode 100644 index 0000000..e3ea30f --- /dev/null +++ b/shaders/blur.comp @@ -0,0 +1,25 @@ +#version 450 +layout(local_size_x = 16, local_size_y = 16) in; +layout(binding = 0) uniform sampler2D srcImage; +layout(rgba8, binding = 1) uniform image2D dstImage; + +void main() { + ivec2 gid = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(dstImage); + if (gid.x >= size.x || gid.y >= size.y) return; + + vec2 texelSize = 1.0 / vec2(size); + vec4 color = vec4(0.0); + int radius = 5; + int samples = 0; + + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + vec2 offset = vec2(dx, dy) * texelSize; + color += texture(srcImage, (vec2(gid) + 0.5) / vec2(size) + offset); + samples++; + } + } + imageStore(dstImage, gid, color / float(samples)); +} + diff --git a/src/main.rs b/src/main.rs index a2f3caa..d6617d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,12 @@ fn print_usage() { eprintln!(" wallbash status | Show daemon status"); } +fn send_command(cmd: &str) -> Result<(), Box> { + let mut stream = UnixStream::connect(SOCKET_PATH)?; + writeln!(stream, "{}", cmd)?; + Ok(()) +} + fn check_daemon() -> bool { UnixStream::connect(SOCKET_PATH).is_ok() } @@ -44,38 +50,47 @@ fn wait_loop() -> Result<(), Box> { Err("Waiting for daemon...".into()) } -fn send_command(cmd: &str) -> Result<(), Box> { - let mut stream = UnixStream::connect(SOCKET_PATH)?; - writeln!(stream, "{}", cmd)?; - Ok(()) -} - -fn parse_mode(args: &[String]) -> &str { - let i = args.iter().position(|a| a == "--mode" || a == "-m"); - match i.and_then(|i| args.get(i + 1)).map(|s| s.as_str()) { - Some("fit") => "fit", - Some("original") => "original", - _ => "cover", - } -} +fn parse_args(args: &[String]) -> (String, String, f32, f32) { + + // wallpaper – mandatory + let wall = args.iter().position(|a| a == "--wall" || a == "-w") + .and_then(|i| args.get(i + 1).cloned()) + .or_else(|| { + args.iter().skip(2).find(|a| !a.starts_with('-')).cloned() + }) + .unwrap_or_else(|| { + eprintln!("Missing wallpaper (use --wall or bare path)"); + print_usage(); + std::process::exit(1); + }); + + // mode – default "cover" + let mode = args.iter().position(|a| a == "--mode" || a == "-m") + .and_then(|i| args.get(i + 1)) + .filter(|s| matches!(s.as_str(), "cover" | "fit" | "original")) + .map(|s| s.clone()) + .unwrap_or_else(|| "cover".into()); -fn parse_anchor(args: &[String]) -> (f32, f32) { - let i = args.iter().position(|a| a == "--anchor" || a == "-a"); - let num = i + // anchor – default "center" + let anchor_num = args.iter().position(|a| a == "--anchor" || a == "-a") .and_then(|i| args.get(i + 1)) - .and_then(|val| val.parse::().ok()); - match num { - Some(1) => (0.0, 0.0), - Some(2) => (0.5, 0.0), - Some(3) => (1.0, 0.0), - Some(4) => (0.0, 0.5), - Some(5) => (0.5, 0.5), - Some(6) => (1.0, 0.5), - Some(7) => (0.0, 1.0), - Some(8) => (0.5, 1.0), - Some(9) => (1.0, 1.0), + .and_then(|s| s.parse::().ok()) + .filter(|&n| (1..10).contains(&n)) + .unwrap_or(5); + let (ax, ay) = match anchor_num { + 1 => (0.0, 0.0), + 2 => (0.5, 0.0), + 3 => (1.0, 0.0), + 4 => (0.0, 0.5), + 5 => (0.5, 0.5), + 6 => (1.0, 0.5), + 7 => (0.0, 1.0), + 8 => (0.5, 1.0), + 9 => (1.0, 1.0), _ => (0.5, 0.5), - } + }; + + (wall, mode, ax, ay) } @@ -83,13 +98,8 @@ fn parse_anchor(args: &[String]) -> (f32, f32) { fn main() { let args: Vec = env::args().collect(); - if args.len() < 2 { - print_usage(); - return; - } - - match args[1].as_str() { - "start" => { + match args.get(1).map(|s| s.as_str()) { + Some("start") => { if check_daemon() { eprintln!("Daemon is already running."); return; @@ -98,11 +108,9 @@ fn main() { eprintln!("Failed to start daemon {}", e); } } - "set" => { - if args.len() < 3 { - eprintln!("Missing image path."); - return; - } + Some("set") => { + let (wall, mode, ax, ay) = parse_args(&args); + let cmd = format!("set{}\x01{}\x01{}\x01{}", mode, ax, ay, wall); if !check_daemon() { println!("Starting daemon"); let log_file = std::fs::File::create(LOG_FILE).expect("Cannot create log"); @@ -118,26 +126,23 @@ fn main() { return; } } - let mode = parse_mode(&args); - let (anchor_h, anchor_v) = parse_anchor(&args); - let cmd = format!("set{}\x01{}\x01{}\x01{}", mode, anchor_h, anchor_v, args[2]); if let Err(e) = send_command(&cmd) { eprintln!("Failed to set wallpaper {}. Is the daemon running?", e); } } - "stop" => { + Some("stop") => { if let Err(e) = send_command("stop") { eprintln!("Failed to stop daemon {}. Is it running?", e); } } - "status" => { + Some("status") => { if check_daemon() { println!("Daemon is running."); } else { println!("Daemon is not running."); } } - _ => print_usage(), + _ => print_usage() } } diff --git a/src/vulkan.rs b/src/vulkan.rs index ac96b5e..d704a0f 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -31,6 +31,8 @@ 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 struct VulkanSurfchain { @@ -69,6 +71,7 @@ pub fn vulkan_core() -> Result> { .application_info(&app_info) .enabled_extension_names(&extensions); let instance = unsafe { entry.create_instance(&create_info, None)? }; + println!("[v] instance: {:?}", instance.handle()); // find and pick dGPU as physical device let list_devices = unsafe { instance.enumerate_physical_devices()? }; @@ -99,19 +102,19 @@ pub fn vulkan_core() -> Result> { ..Default::default() }; - // enable swapchain extension (device feature) + // enable swapchain extension let swapchain_ext = c"VK_KHR_swapchain"; let device_extensions = [swapchain_ext.as_ptr()]; - // create logical device + // create logical device and queue let device_info = vk::DeviceCreateInfo::default() .queue_create_infos(std::slice::from_ref(&queue_info)) .enabled_extension_names(&device_extensions); - let device = unsafe { instance.create_device(physical_device, &device_info, None)? }; + let device: ash::Device = unsafe { instance.create_device(physical_device, &device_info, None)? }; let graphics_queue = unsafe { device.get_device_queue(graphics_family_index, 0) }; - println!("[v{}] logical device: {:?} queue {:?}", graphics_family_index, device.handle(), graphics_queue); + println!("[v{}] logical device {:?} >> graphics queue {:?}", graphics_family_index, device.handle(), graphics_queue); - // create persistent command pool + // create persistent command pool and buffer let command_pool_info = vk::CommandPoolCreateInfo::default() .queue_family_index(graphics_family_index); let command_pool = unsafe { device.create_command_pool(&command_pool_info, None)? }; @@ -122,6 +125,10 @@ pub fn vulkan_core() -> Result> { .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(1); let command_buffer = unsafe { device.allocate_command_buffers(&alloc_info)? }[0]; + println!("[v] command pool {:?} >> command buffer {:?}", command_pool, command_buffer); + + // call blur pipeline + let (blur_module, blur_pipeline) = blur_pipeline(&device)?; Ok(VulkanCore { entry, @@ -132,10 +139,92 @@ pub fn vulkan_core() -> Result> { graphics_queue, command_pool, command_buffer, + blur_module, + blur_pipeline, }) } +// --------------------------------------------------------------------- / blur pipeline + +pub fn blur_pipeline( + device: &ash::Device, +) -> Result<(vk::ShaderModule, vk::Pipeline), Box> { + // load the compiled SPIR‑V + 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 set layout (b0 = sampler, b1 = 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 desc_layout = unsafe { device.create_descriptor_set_layout(&layout_info, None)? }; + + // build the pipeline using the generic helper + let (module, pipeline) = compute_pipeline(device, &blur_words, desc_layout)?; + + // the pipeline has its own copy of the layout; we can destroy this one + unsafe { device.destroy_descriptor_set_layout(desc_layout, None); } + + println!("[v] blur pipeline created: {:?}", pipeline); + Ok((module, pipeline)) +} + + +// --------------------------------------------------------------------- / compute pipeline + +fn compute_pipeline( + device: &ash::Device, + spv: &[u32], + desc_layout: vk::DescriptorSetLayout, +) -> Result<(vk::ShaderModule, vk::Pipeline), Box> { + let module = load_shader(device, spv)?; + + 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 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 pipeline = unsafe { + device.create_compute_pipelines(vk::PipelineCache::null(), &[pipeline_info], None) + }.unwrap()[0]; + + unsafe { + device.destroy_pipeline_layout(pipeline_layout, None); + } + + Ok((module, 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( @@ -235,22 +324,43 @@ pub fn vulkan_surfchain( } -// --------------------------------------------------------------------- / destroy surfchain +// --------------------------------------------------------------------- / vulkan wrapper -pub fn destroy_surfchain( - entry: &ash::Entry, +pub fn vulkan_pipeline( instance: &ash::Instance, device: &ash::Device, - surfchain: &mut VulkanSurfchain, -) { - let swapchain_loader = ash::khr::swapchain::Device::new(instance, device); - unsafe { - swapchain_loader.destroy_swapchain(surfchain.swapchain, None); - } - let surface_loader = surface::Instance::new(entry, instance); + physical_device: vk::PhysicalDevice, + graphics_queue: vk::Queue, + command_pool: vk::CommandPool, + command_buffer: vk::CommandBuffer, + pixel_bytes: &[u8], + width: u32, + height: u32, +) -> Result> { + + // copy raw image data to staging buffer + let (buffer, memory) = create_buffer(instance, device, physical_device, pixel_bytes)?; + + // allocate vram for image data + let (texture, vram) = create_texture( + instance, device, physical_device, width, height, + vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, + vk::Format::R8G8B8A8_SRGB, + )?; + + // load pixed data from buffer to texture + load_texture(device, graphics_queue, command_pool, command_buffer, buffer, texture, width, height)?; + + // drop the staging buffer (no longer needed) unsafe { - surface_loader.destroy_surface(surfchain.surface, None); + device.destroy_buffer(buffer, None); + device.free_memory(memory, None); } + + Ok(VulkanTexture { + image: texture, + _memory: vram, + }) } @@ -315,18 +425,20 @@ pub fn create_texture( physical_device: vk::PhysicalDevice, width: u32, height: u32, + usage: vk::ImageUsageFlags, + format: vk::Format, ) -> Result<(vk::Image, vk::DeviceMemory), Box> { // describe the image let image_info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) - .format(vk::Format::R8G8B8A8_SRGB) + .format(format) .extent(vk::Extent3D { width, height, depth: 1 }) .mip_levels(1) .array_layers(1) .samples(vk::SampleCountFlags::TYPE_1) .tiling(vk::ImageTiling::OPTIMAL) - .usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED) + .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE) .initial_layout(vk::ImageLayout::UNDEFINED); @@ -472,83 +584,209 @@ pub fn load_texture( } -// --------------------------------------------------------------------- / vulkan wrapper +// --------------------------------------------------------------------- / blur texture -pub fn vulkan_pipeline( - instance: &ash::Instance, - device: &ash::Device, - physical_device: vk::PhysicalDevice, - graphics_queue: vk::Queue, - command_pool: vk::CommandPool, - command_buffer: vk::CommandBuffer, - pixel_bytes: &[u8], +pub fn blur_texture( + vk_core: &VulkanCore, + input_texture: &VulkanTexture, width: u32, height: u32, ) -> Result> { - // copy raw image data to staging buffer - let (buffer, memory) = create_buffer(instance, device, physical_device, pixel_bytes)?; - - // allocate vram for image data - let (texture, vram) = create_texture(instance, device, physical_device, width, height)?; - - // load pixed data from buffer to texture - load_texture(device, graphics_queue, command_pool, command_buffer, buffer, texture, width, height)?; + // 1. 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, + )?; + + // 2. 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)? }; + + // 3. Descriptor set (bindings 0=sampler, 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 desc_layout = unsafe { vk_core.device.create_descriptor_set_layout(&layout_info, None)? }; + + 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 = [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, &[]) }; + + // 4. Pipeline layout (no push constants, same descriptor layout as the pipeline) + let set_layouts2 = [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)? }; + + // 5. 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) }?; - // drop the staging buffer (no longer needed) + // 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 { - device.destroy_buffer(buffer, None); - device.free_memory(memory, None); + vk_core.device.cmd_pipeline_barrier( + vk_core.command_buffer, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::COMPUTE_SHADER, + vk::DependencyFlags::empty(), + &[], &[], &[barrier], + ); } - Ok(VulkanTexture { - image: texture, - _memory: vram, - }) -} + // 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); } -// --------------------------------------------------------------------- / set mode + // 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) }?; -fn mode_set( - img_w: u32, - img_h: u32, - scr_w: u32, - scr_h: u32, - anchor_x: f32, - anchor_y: f32, - mode: &str, -) -> (u32, u32, u32, u32, i32, i32, u32, u32, bool) { + // 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) }; - if mode == "fit" { - let scale = (scr_w as f64 / img_w as f64).min(scr_h as f64 / img_h as f64); - let sw = (img_w as f64 * scale) as u32; - let sh = (img_h as f64 * scale) as u32; - let dx = ((scr_w - sw) as f32 * anchor_x) as i32; - let dy = ((scr_h - sh) as f32 * anchor_y) as i32; - return (0, 0, img_w, img_h, dx, dy, sw, sh, true); - } - if mode == "original" { - if img_w <= scr_w && img_h <= scr_h { - let dx = ((scr_w - img_w) as f32 * anchor_x) as i32; - let dy = ((scr_h - img_h) as f32 * anchor_y) as i32; - return (0, 0, img_w, img_h, dx, dy, img_w, img_h, true); - } + // 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_set_layout(desc_layout, None); + vk_core.device.destroy_descriptor_pool(desc_pool, None); + vk_core.device.destroy_pipeline_layout(pipeline_layout, None); } - let src_aspect = img_w as f64 / img_h as f64; - let dst_aspect = scr_w as f64 / scr_h as f64; - let (sx, sy, sw, sh) = if src_aspect > dst_aspect { - let new_w = (img_h as f64 * dst_aspect) as u32; - let max_x = (img_w - new_w) as f32; - let x = (max_x * anchor_x) as u32; - (x, 0, new_w, img_h) - } else { - let new_h = (img_w as f64 / dst_aspect) as u32; - let max_y = (img_h - new_h) as f32; - let y = (max_y * anchor_y) as u32; - (0, y, img_w, new_h) - }; - (sx, sy, sw, sh, 0, 0, scr_w, scr_h, false) + + Ok(VulkanTexture { + image: output_image, + _memory: output_memory, + }) } @@ -569,6 +807,7 @@ pub fn draw_wallpaper( swapchain_extent_height: u32, anchor_x: f32, anchor_y: f32, + blur_bg: Option<(vk::Image, u32, u32)>, mode: &str, ) -> Result<(), Box> { @@ -649,22 +888,99 @@ pub fn draw_wallpaper( mode, ); - // if the mode requires black bars, clear the image first + // if the mode requires background, fill with blurred wallpaper or black if needs_clear { - let clear_color = vk::ClearColorValue { float32: [0.0, 0.0, 0.0, 1.0] }; - let clear_range = vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, level_count: 1, - base_array_layer: 0, layer_count: 1, - }; - unsafe { - device.cmd_clear_color_image( - command_buffer, - target_image, - vk::ImageLayout::TRANSFER_DST_OPTIMAL, - &clear_color, - &[clear_range], - ); + match blur_bg { + Some((bg_image, bg_w, bg_h)) => { + // Compute a cover-crop rectangle for the blurred background + let bg_aspect = bg_w as f64 / bg_h as f64; + let scr_aspect = swapchain_extent_width as f64 / swapchain_extent_height as f64; + let (bg_sx, bg_sy, bg_sw, bg_sh) = if bg_aspect > scr_aspect { + // background wider → crop left/right (centered) + let new_w = (bg_h as f64 * scr_aspect) as u32; + let x = (bg_w - new_w) / 2; + (x, 0, new_w, bg_h) + } else { + // background taller → crop top/bottom (centered) + let new_h = (bg_w as f64 / scr_aspect) as u32; + let y = (bg_h - new_h) / 2; + (0, y, bg_w, new_h) + }; + + // Transition bg image to TRANSFER_SRC_OPTIMAL + let bg_barrier = vk::ImageMemoryBarrier::default() + .image(bg_image) + .old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .src_access_mask(vk::AccessFlags::SHADER_READ) + .dst_access_mask(vk::AccessFlags::TRANSFER_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 { + device.cmd_pipeline_barrier( + command_buffer, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[], &[], &[bg_barrier], + ); + } + + // Blit the cropped background to cover the entire screen + let bg_blit = vk::ImageBlit::default() + .src_subresource(vk::ImageSubresourceLayers { + aspect_mask: vk::ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }) + .src_offsets([ + vk::Offset3D { x: bg_sx as i32, y: bg_sy as i32, z: 0 }, + vk::Offset3D { x: (bg_sx + bg_sw) as i32, y: (bg_sy + bg_sh) as i32, z: 1 }, + ]) + .dst_subresource(vk::ImageSubresourceLayers { + aspect_mask: vk::ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }) + .dst_offsets([ + vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { x: swapchain_extent_width as i32, y: swapchain_extent_height as i32, z: 1 }, + ]); + unsafe { + device.cmd_blit_image( + command_buffer, + bg_image, + vk::ImageLayout::TRANSFER_SRC_OPTIMAL, + target_image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[bg_blit], + vk::Filter::LINEAR, + ); + } + } + None => { + // fallback to solid black + let clear_color = vk::ClearColorValue { float32: [0.0, 0.0, 0.0, 1.0] }; + let clear_range = vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, level_count: 1, + base_array_layer: 0, layer_count: 1, + }; + unsafe { + device.cmd_clear_color_image( + command_buffer, + target_image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &clear_color, + &[clear_range], + ); + } + } } } @@ -761,3 +1077,66 @@ pub fn draw_wallpaper( } } + +// --------------------------------------------------------------------- / set mode + +fn mode_set( + img_w: u32, + img_h: u32, + scr_w: u32, + scr_h: u32, + anchor_x: f32, + anchor_y: f32, + mode: &str, +) -> (u32, u32, u32, u32, i32, i32, u32, u32, bool) { + + if mode == "fit" { + let scale = (scr_w as f64 / img_w as f64).min(scr_h as f64 / img_h as f64); + let sw = (img_w as f64 * scale) as u32; + let sh = (img_h as f64 * scale) as u32; + let dx = ((scr_w - sw) as f32 * anchor_x) as i32; + let dy = ((scr_h - sh) as f32 * anchor_y) as i32; + return (0, 0, img_w, img_h, dx, dy, sw, sh, true); + } + if mode == "original" { + if img_w <= scr_w && img_h <= scr_h { + let dx = ((scr_w - img_w) as f32 * anchor_x) as i32; + let dy = ((scr_h - img_h) as f32 * anchor_y) as i32; + return (0, 0, img_w, img_h, dx, dy, img_w, img_h, true); + } + } + let src_aspect = img_w as f64 / img_h as f64; + let dst_aspect = scr_w as f64 / scr_h as f64; + let (sx, sy, sw, sh) = if src_aspect > dst_aspect { + let new_w = (img_h as f64 * dst_aspect) as u32; + let max_x = (img_w - new_w) as f32; + let x = (max_x * anchor_x) as u32; + (x, 0, new_w, img_h) + } else { + let new_h = (img_w as f64 / dst_aspect) as u32; + let max_y = (img_h - new_h) as f32; + let y = (max_y * anchor_y) as u32; + (0, y, img_w, new_h) + }; + (sx, sy, sw, sh, 0, 0, scr_w, scr_h, false) +} + + +// --------------------------------------------------------------------- / destroy surfchain + +pub fn destroy_surfchain( + entry: &ash::Entry, + instance: &ash::Instance, + device: &ash::Device, + surfchain: &mut VulkanSurfchain, +) { + let swapchain_loader = ash::khr::swapchain::Device::new(instance, device); + unsafe { + swapchain_loader.destroy_swapchain(surfchain.swapchain, None); + } + let surface_loader = surface::Instance::new(entry, instance); + unsafe { + surface_loader.destroy_surface(surfchain.surface, None); + } +} + diff --git a/src/wallbashed.rs b/src/wallbashed.rs index ec30901..8b24c40 100644 --- a/src/wallbashed.rs +++ b/src/wallbashed.rs @@ -1,5 +1,5 @@ // --------------------------------------------------------------------- / tittu -// wallbashed +// wallbash // a daemon module for HyDE // @@ -18,7 +18,7 @@ use std::{ fn start_ipc(socket_path: &str) -> Result, Box> { - // Remove any stale socket file from a previous run + // remove any stale socket file from a previous run let _ = std::fs::remove_file(socket_path); let listener = UnixListener::bind(socket_path)?; let (tx, rx) = mpsc::channel::(); @@ -94,6 +94,22 @@ 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())); + // draw the wallpaper vulkan::draw_wallpaper( &vk_core.instance, @@ -110,9 +126,18 @@ fn set_wallpaper( layer_height, anchor_x, anchor_y, + blur_bg, mode, )?; + // destroy the temporary blurred texture (if any) + if let Some(bg) = blurred_bg { + unsafe { + vk_core.device.destroy_image(bg.image, None); + vk_core.device.free_memory(bg._memory, None); + } + } + Ok(()) } @@ -250,6 +275,8 @@ pub fn run(socket_path: &str) -> Result<(), Box> { } 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_shader_module(vk_core.blur_module, None); } unsafe { vk_core.device.destroy_device(None); } unsafe { vk_core.instance.destroy_instance(None); } diff --git a/src/wayland.rs b/src/wayland.rs index fbe66fd..4e4e441 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -61,12 +61,12 @@ impl Dispatch for AppData { "wl_compositor" => { let compositor = registry.bind::(name, version, qh, ()); state.compositor = Some(compositor); - println!("[{}] ✓ {} (v{})", name, interface, version); + println!("[w{}] ✓ {} (v{})", name, interface, version); } "zwlr_layer_shell_v1" => { let layer_shell = registry.bind::(name, version, qh, ()); state.layer_shell = Some(layer_shell); - println!("[{}] ✓ {} (v{})", name, interface, version); + println!("[w{}] ✓ {} (v{})", name, interface, version); } "wl_output" => { let index = state.monitors.len(); @@ -79,9 +79,9 @@ impl Dispatch for AppData { refresh: 0, detected: false, }); - println!("[{}] ✓ {} (v{})", name, interface, version); + println!("[w{}] ✓ {} (v{})", name, interface, version); } - _ => println!("[{}] {} (v{})", name, interface, version) + _ => println!("[w{}] {} (v{})", name, interface, version) } } } @@ -130,7 +130,7 @@ impl Dispatch for AppData { } wl_output::Event::Done => { if !monitor.detected { - println!("[{}] monitor: {}x{}@{:.1}Hz ({})", index, + println!("[w{}] monitor: {}x{}@{:.1}Hz ({})", index, monitor.width, monitor.height, monitor.refresh as f32 / 1000.0, @@ -167,7 +167,7 @@ impl Dispatch for AppData { state.layer_width = width; state.layer_height = height; proxy.ack_configure(serial); - println!("[{}] layer surface: {}x{}", serial, width, height); + println!("[w] layer surface: {} >> {}x{}", serial, width, height); } } }