Commit 824bf02b6e55
tarball19 files changed+2154-4
@@ -18,6 +18,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1818
checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2"1919
2020
[[package]]21
+name = "ash"22
+version = "0.38.0+1.3.281"23
+source = "registry+https://github.com/rust-lang/crates.io-index"24
+checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"25
+dependencies = [26
+ "libloading",27
+]28
+29
+[[package]]2130
name = "autocfg"2231
version = "1.5.1"2332
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -259,6 +268,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
259268
checksum = "37bfe73dc8ec21f54d181e5a60554327ec3f1c600c455a75ccbfc65d2106a6c4"260269
261270
[[package]]271
+name = "gpu-allocator"272
+version = "0.27.0"273
+source = "registry+https://github.com/rust-lang/crates.io-index"274
+checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"275
+dependencies = [276
+ "ash",277
+ "log",278
+ "presser",279
+ "thiserror 1.0.69",280
+]281
+282
+[[package]]262283
name = "hashbrown"263284
version = "0.17.1"264285
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -293,6 +314,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
293314
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"294315
295316
[[package]]317
+name = "libloading"318
+version = "0.8.9"319
+source = "registry+https://github.com/rust-lang/crates.io-index"320
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"321
+dependencies = [322
+ "cfg-if",323
+ "windows-link",324
+]325
+326
+[[package]]296327
name = "linux-raw-sys"297328
version = "0.12.1"298329
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -394,7 +425,11 @@ dependencies = [
394425
name = "monolith-render-vk"395426
version = "0.0.0"396427
dependencies = [428
+ "ash",429
+ "glam",430
+ "gpu-allocator",397431
"smithay",432
+ "thiserror 2.0.21",398433
]399434
400435
[[package]]@@ -461,6 +496,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
461496
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"462497
463498
[[package]]499
+name = "presser"500
+version = "0.3.1"501
+source = "registry+https://github.com/rust-lang/crates.io-index"502
+checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"503
+504
+[[package]]464505
name = "proc-macro-crate"465506
version = "3.5.0"466507
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -719,7 +760,7 @@ dependencies = [
719760
"siphasher",720761
"smallvec",721762
"tempfile",722
- "thiserror",763
+ "thiserror 2.0.21",723764
"tracing",724765
"wayland-protocols",725766
"wayland-protocols-misc",@@ -765,11 +806,31 @@ dependencies = [
765806
766807
[[package]]767808
name = "thiserror"809
+version = "1.0.69"810
+source = "registry+https://github.com/rust-lang/crates.io-index"811
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"812
+dependencies = [813
+ "thiserror-impl 1.0.69",814
+]815
+816
+[[package]]817
+name = "thiserror"768818
version = "2.0.21"769819
source = "registry+https://github.com/rust-lang/crates.io-index"770820
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"771821
dependencies = [772
- "thiserror-impl",822
+ "thiserror-impl 2.0.21",823
+]824
+825
+[[package]]826
+name = "thiserror-impl"827
+version = "1.0.69"828
+source = "registry+https://github.com/rust-lang/crates.io-index"829
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"830
+dependencies = [831
+ "proc-macro2",832
+ "quote",833
+ "syn 2.0.119",773834
]774835
775836
[[package]]@@ -9,4 +9,8 @@ publish.workspace = true
99
workspace = true1010
1111
[dependencies]12
+ash = "0.38"13
+glam = { version = "0.33", default-features = false, features = ["std"] }14
+gpu-allocator = { version = "0.27", default-features = false, features = ["vulkan"] }1215
smithay.workspace = true16
+thiserror = "2"@@ -0,0 +1,16 @@
1
+use std::path::PathBuf;2
+use std::process::Command;3
+4
+fn main() {5
+ let out = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));6
+ println!("cargo:rerun-if-changed=shaders");7
+ for name in ["quad.vert", "solid.frag", "texture.frag"] {8
+ let status = Command::new("glslangValidator")9
+ .args(["-V", "--target-env", "vulkan1.3", "-o"])10
+ .arg(out.join(format!("{name}.spv")))11
+ .arg(format!("shaders/{name}"))12
+ .status()13
+ .expect("glslangValidator not found, build inside the nix devshell");14
+ assert!(status.success(), "failed to compile shaders/{name}");15
+ }16
+}@@ -0,0 +1,8 @@
1
+// `color` must stay at offset 0. lavapipe in Mesa 26.2 returned the wrong2
+// fragment push constants for a member at offset 48 when `dst` held four3
+// equal values.4
+layout(push_constant) uniform Push {5
+ vec4 color;6
+ vec4 dst;7
+ vec2 uv[4];8
+} pc;@@ -0,0 +1,11 @@
1
+#version 4502
+#extension GL_GOOGLE_include_directive : require3
+#include "push.glsl"4
+5
+layout(location = 0) out vec2 v_uv;6
+7
+void main() {8
+ vec2 corner = vec2(gl_VertexIndex & 1, gl_VertexIndex >> 1);9
+ v_uv = pc.uv[gl_VertexIndex];10
+ gl_Position = vec4(pc.dst.xy + corner * pc.dst.zw, 0.0, 1.0);11
+}@@ -0,0 +1,9 @@
1
+#version 4502
+#extension GL_GOOGLE_include_directive : require3
+#include "push.glsl"4
+5
+layout(location = 0) out vec4 out_color;6
+7
+void main() {8
+ out_color = pc.color;9
+}@@ -0,0 +1,11 @@
1
+#version 4502
+#extension GL_GOOGLE_include_directive : require3
+#include "push.glsl"4
+5
+layout(set = 0, binding = 0) uniform sampler2D tex;6
+layout(location = 0) in vec2 v_uv;7
+layout(location = 0) out vec4 out_color;8
+9
+void main() {10
+ out_color = texture(tex, v_uv) * pc.color.a;11
+}@@ -0,0 +1,155 @@
1
+use crate::Error;2
+use ash::vk;3
+use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc};4
+use std::sync::Mutex;5
+6
+/// Everything that must outlive every Vulkan object the renderer hands out.7
+/// Fields drop in declaration order, so the allocator frees its memory before8
+/// the device is destroyed.9
+pub(crate) struct Shared {10
+ pub allocator: Mutex<Allocator>,11
+ pub device: Device,12
+}13
+14
+pub(crate) struct Device {15
+ pub _entry: ash::Entry,16
+ pub instance: ash::Instance,17
+ #[expect(18
+ dead_code,19
+ reason = "dmabuf import queries format properties through it"20
+ )]21
+ pub physical: vk::PhysicalDevice,22
+ pub device: ash::Device,23
+ pub queue: Mutex<vk::Queue>,24
+ pub queue_family: u32,25
+ pub push_descriptor: ash::khr::push_descriptor::Device,26
+ pub name: String,27
+}28
+29
+impl Drop for Device {30
+ fn drop(&mut self) {31
+ unsafe {32
+ let _ = self.device.device_wait_idle();33
+ self.device.destroy_device(None);34
+ self.instance.destroy_instance(None);35
+ }36
+ }37
+}38
+39
+impl Shared {40
+ /// Opens the first suitable device, or the one whose name contains41
+ /// `MONOLITH_VK_DEVICE` when that is set.42
+ pub fn new() -> Result<Self, Error> {43
+ let entry = unsafe { ash::Entry::load()? };44
+ let app = vk::ApplicationInfo::default()45
+ .application_name(c"monolith")46
+ .engine_name(c"monolith")47
+ .api_version(vk::API_VERSION_1_3);48
+ let instance = unsafe {49
+ entry.create_instance(50
+ &vk::InstanceCreateInfo::default().application_info(&app),51
+ None,52
+ )?53
+ };54
+ let wanted = std::env::var("MONOLITH_VK_DEVICE").ok();55
+ let picked = unsafe { pick_physical(&instance, wanted.as_deref()) };56
+ let Some((physical, queue_family, name)) = picked else {57
+ unsafe { instance.destroy_instance(None) };58
+ return Err(Error::NoSuitableDevice);59
+ };60
+ let priorities = [1.0];61
+ let queues = [vk::DeviceQueueCreateInfo::default()62
+ .queue_family_index(queue_family)63
+ .queue_priorities(&priorities)];64
+ let extensions = [ash::khr::push_descriptor::NAME.as_ptr()];65
+ let mut features13 = vk::PhysicalDeviceVulkan13Features::default()66
+ .dynamic_rendering(true)67
+ .synchronization2(true);68
+ let info = vk::DeviceCreateInfo::default()69
+ .queue_create_infos(&queues)70
+ .enabled_extension_names(&extensions)71
+ .push_next(&mut features13);72
+ let device = match unsafe { instance.create_device(physical, &info, None) } {73
+ Ok(d) => d,74
+ Err(e) => {75
+ unsafe { instance.destroy_instance(None) };76
+ return Err(e.into());77
+ }78
+ };79
+ let queue = unsafe { device.get_device_queue(queue_family, 0) };80
+ let push_descriptor = ash::khr::push_descriptor::Device::new(&instance, &device);81
+ let allocator = Allocator::new(&AllocatorCreateDesc {82
+ instance: instance.clone(),83
+ device: device.clone(),84
+ physical_device: physical,85
+ debug_settings: Default::default(),86
+ buffer_device_address: false,87
+ allocation_sizes: Default::default(),88
+ });89
+ let device = Device {90
+ _entry: entry,91
+ instance,92
+ physical,93
+ device,94
+ queue: Mutex::new(queue),95
+ queue_family,96
+ push_descriptor,97
+ name,98
+ };99
+ Ok(Self {100
+ allocator: Mutex::new(allocator?),101
+ device,102
+ })103
+ }104
+}105
+106
+unsafe fn pick_physical(107
+ instance: &ash::Instance,108
+ wanted: Option<&str>,109
+) -> Option<(vk::PhysicalDevice, u32, String)> {110
+ let devices = unsafe { instance.enumerate_physical_devices() }.ok()?;111
+ let mut candidates = Vec::new();112
+ for physical in devices {113
+ let props = unsafe { instance.get_physical_device_properties(physical) };114
+ if props.api_version < vk::API_VERSION_1_3 {115
+ continue;116
+ }117
+ let name = props118
+ .device_name_as_c_str()119
+ .unwrap_or(c"unknown")120
+ .to_string_lossy()121
+ .into_owned();122
+ if wanted.is_some_and(|w| !name.contains(w)) {123
+ continue;124
+ }125
+ let mut f13 = vk::PhysicalDeviceVulkan13Features::default();126
+ let mut f2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut f13);127
+ unsafe { instance.get_physical_device_features2(physical, &mut f2) };128
+ if f13.dynamic_rendering == vk::FALSE || f13.synchronization2 == vk::FALSE {129
+ continue;130
+ }131
+ let has_push = unsafe { instance.enumerate_device_extension_properties(physical) }132
+ .unwrap_or_default()133
+ .iter()134
+ .any(|e| e.extension_name_as_c_str().ok() == Some(ash::khr::push_descriptor::NAME));135
+ if !has_push {136
+ continue;137
+ }138
+ let families = unsafe { instance.get_physical_device_queue_family_properties(physical) };139
+ let Some(family) = families140
+ .iter()141
+ .position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))142
+ else {143
+ continue;144
+ };145
+ let rank = match props.device_type {146
+ vk::PhysicalDeviceType::DISCRETE_GPU => 0,147
+ vk::PhysicalDeviceType::INTEGRATED_GPU => 1,148
+ vk::PhysicalDeviceType::VIRTUAL_GPU => 2,149
+ _ => 3,150
+ };151
+ candidates.push((rank, physical, family as u32, name));152
+ }153
+ candidates.sort_by_key(|c| c.0);154
+ candidates.into_iter().next().map(|(_, p, f, n)| (p, f, n))155
+}@@ -0,0 +1,24 @@
1
+use ash::vk;2
+use smithay::backend::allocator::Fourcc;3
+4
+#[derive(Debug, thiserror::Error)]5
+pub enum Error {6
+ #[error("failed to load the Vulkan loader: {0}")]7
+ Loading(#[from] ash::LoadingError),8
+ #[error("Vulkan call failed: {0}")]9
+ Vulkan(#[from] vk::Result),10
+ #[error("GPU memory allocation failed: {0}")]11
+ Allocation(#[from] gpu_allocator::AllocationError),12
+ #[error("no Vulkan 1.3 device with dynamic rendering, synchronization2 and push descriptors")]13
+ NoSuitableDevice,14
+ #[error("unsupported pixel format {0}")]15
+ UnsupportedFormat(Fourcc),16
+ #[error("buffer of {got} bytes is too small, {needed} needed")]17
+ BufferTooSmall { needed: usize, got: usize },18
+ #[error("texture belongs to a different renderer")]19
+ ForeignTexture,20
+ #[error("interrupted while waiting for a sync point")]21
+ Interrupted,22
+ #[error("invalid size {0}x{1}")]23
+ InvalidSize(i32, i32),24
+}@@ -0,0 +1,38 @@
1
+use ash::vk;2
+use smithay::backend::allocator::Fourcc;3
+4
+/// Vulkan format and swizzle for a DRM fourcc. Formats without alpha read5
+/// alpha as one through the image view.6
+pub(crate) struct VkFormat {7
+ pub format: vk::Format,8
+ pub opaque: bool,9
+}10
+11
+pub(crate) const SUPPORTED: [Fourcc; 4] = [12
+ Fourcc::Argb8888,13
+ Fourcc::Xrgb8888,14
+ Fourcc::Abgr8888,15
+ Fourcc::Xbgr8888,16
+];17
+18
+pub(crate) fn vk_format(fourcc: Fourcc) -> Option<VkFormat> {19
+ let (format, opaque) = match fourcc {20
+ Fourcc::Argb8888 => (vk::Format::B8G8R8A8_UNORM, false),21
+ Fourcc::Xrgb8888 => (vk::Format::B8G8R8A8_UNORM, true),22
+ Fourcc::Abgr8888 => (vk::Format::R8G8B8A8_UNORM, false),23
+ Fourcc::Xbgr8888 => (vk::Format::R8G8B8A8_UNORM, true),24
+ _ => return None,25
+ };26
+ Some(VkFormat { format, opaque })27
+}28
+29
+pub(crate) fn components(opaque: bool) -> vk::ComponentMapping {30
+ let a = if opaque {31
+ vk::ComponentSwizzle::ONE32
+ } else {33
+ vk::ComponentSwizzle::IDENTITY34
+ };35
+ vk::ComponentMapping::default().a(a)36
+}37
+38
+pub(crate) const BYTES_PER_PIXEL: usize = 4;@@ -0,0 +1,201 @@
1
+use crate::Error;2
+use crate::image::{VulkanTarget, VulkanTexture};3
+use crate::pipeline::{Kind, Push};4
+use crate::renderer::VulkanRenderer;5
+use crate::transform::{rect_to_framebuffer, texture_uvs, to_framebuffer};6
+use ash::vk;7
+use smithay::backend::renderer::sync::SyncPoint;8
+use smithay::backend::renderer::{Color32F, ContextId, Frame, Texture};9
+use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};10
+use std::sync::Arc;11
+12
+pub(crate) enum Op {13
+ Clear {14
+ color: [f32; 4],15
+ rects: Vec<vk::Rect2D>,16
+ },17
+ Draw {18
+ kind: Kind,19
+ texture: Option<VulkanTexture>,20
+ push: Push,21
+ scissors: Vec<vk::Rect2D>,22
+ },23
+}24
+25
+/// A frame in progress. Draws are recorded and submitted together by [`Frame::finish`].26
+pub struct VulkanFrame<'frame, 'buffer> {27
+ renderer: &'frame mut VulkanRenderer,28
+ target: &'frame mut VulkanTarget<'buffer>,29
+ output: Size<i32, Physical>,30
+ transform: Transform,31
+ ops: Vec<Op>,32
+}33
+34
+impl std::fmt::Debug for VulkanFrame<'_, '_> {35
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {36
+ f.debug_struct("VulkanFrame")37
+ .field("output", &self.output)38
+ .field("ops", &self.ops.len())39
+ .finish()40
+ }41
+}42
+43
+impl<'frame, 'buffer> VulkanFrame<'frame, 'buffer> {44
+ pub(crate) fn new(45
+ renderer: &'frame mut VulkanRenderer,46
+ target: &'frame mut VulkanTarget<'buffer>,47
+ output: Size<i32, Physical>,48
+ transform: Transform,49
+ ) -> Self {50
+ Self {51
+ renderer,52
+ target,53
+ output,54
+ transform,55
+ ops: Vec::new(),56
+ }57
+ }58
+59
+ fn fb_size(&self) -> Size<i32, Physical> {60
+ (self.target.width() as i32, self.target.height() as i32).into()61
+ }62
+63
+ fn fb_rect(&self, r: Rectangle<i32, Physical>) -> Option<vk::Rect2D> {64
+ let r = rect_to_framebuffer(r, self.output, self.transform)65
+ .intersection(Rectangle::from_size(self.fb_size()))?;66
+ Some(vk::Rect2D {67
+ offset: vk::Offset2D {68
+ x: r.loc.x,69
+ y: r.loc.y,70
+ },71
+ extent: vk::Extent2D {72
+ width: r.size.w as u32,73
+ height: r.size.h as u32,74
+ },75
+ })76
+ }77
+78
+ fn scissors(79
+ &self,80
+ dst: Rectangle<i32, Physical>,81
+ damage: &[Rectangle<i32, Physical>],82
+ ) -> Vec<vk::Rect2D> {83
+ damage84
+ .iter()85
+ .filter_map(|d| d.intersection(Rectangle::from_size(dst.size)))86
+ .filter_map(|mut d| {87
+ d.loc += dst.loc;88
+ self.fb_rect(d)89
+ })90
+ .collect()91
+ }92
+93
+ fn ndc(&self, r: Rectangle<i32, Physical>) -> [f32; 4] {94
+ let fb = self.fb_size();95
+ let (w, h) = (fb.w as f32, fb.h as f32);96
+ [97
+ r.loc.x as f32 / w * 2.0 - 1.0,98
+ r.loc.y as f32 / h * 2.0 - 1.0,99
+ r.size.w as f32 / w * 2.0,100
+ r.size.h as f32 / h * 2.0,101
+ ]102
+ }103
+}104
+105
+impl Frame for VulkanFrame<'_, '_> {106
+ type Error = Error;107
+ type TextureId = VulkanTexture;108
+109
+ fn context_id(&self) -> ContextId<VulkanTexture> {110
+ self.renderer.context_id_ref().clone()111
+ }112
+113
+ fn clear(&mut self, color: Color32F, at: &[Rectangle<i32, Physical>]) -> Result<(), Error> {114
+ let rects: Vec<_> = at.iter().filter_map(|r| self.fb_rect(*r)).collect();115
+ if !rects.is_empty() {116
+ self.ops.push(Op::Clear {117
+ color: color.components(),118
+ rects,119
+ });120
+ }121
+ Ok(())122
+ }123
+124
+ fn draw_solid(125
+ &mut self,126
+ dst: Rectangle<i32, Physical>,127
+ damage: &[Rectangle<i32, Physical>],128
+ color: Color32F,129
+ ) -> Result<(), Error> {130
+ let scissors = self.scissors(dst, damage);131
+ if scissors.is_empty() {132
+ return Ok(());133
+ }134
+ let (fb, _) = to_framebuffer(dst, [[0.0; 2]; 4], self.output, self.transform);135
+ let push = Push {136
+ dst: self.ndc(fb),137
+ uv: [[0.0; 2]; 4],138
+ color: color.components(),139
+ };140
+ self.ops.push(Op::Draw {141
+ kind: Kind::Solid,142
+ texture: None,143
+ push,144
+ scissors,145
+ });146
+ Ok(())147
+ }148
+149
+ fn render_texture_from_to(150
+ &mut self,151
+ texture: &VulkanTexture,152
+ src: Rectangle<f64, Buffer>,153
+ dst: Rectangle<i32, Physical>,154
+ damage: &[Rectangle<i32, Physical>],155
+ _opaque_regions: &[Rectangle<i32, Physical>],156
+ src_transform: Transform,157
+ alpha: f32,158
+ ) -> Result<(), Error> {159
+ if !Arc::ptr_eq(&texture.0.shared, self.renderer.shared()) {160
+ return Err(Error::ForeignTexture);161
+ }162
+ if dst.size.w <= 0 || dst.size.h <= 0 || Arc::ptr_eq(&texture.0, &self.target.0.0) {163
+ return Ok(());164
+ }165
+ let scissors = self.scissors(dst, damage);166
+ if scissors.is_empty() {167
+ return Ok(());168
+ }169
+ let uvs = texture_uvs(src, dst.size, texture.size(), src_transform);170
+ let (fb, uv) = to_framebuffer(dst, uvs, self.output, self.transform);171
+ let push = Push {172
+ dst: self.ndc(fb),173
+ uv,174
+ color: [1.0, 1.0, 1.0, alpha],175
+ };176
+ self.ops.push(Op::Draw {177
+ kind: Kind::Texture,178
+ texture: Some(texture.clone()),179
+ push,180
+ scissors,181
+ });182
+ Ok(())183
+ }184
+185
+ fn transformation(&self) -> Transform {186
+ self.transform187
+ }188
+189
+ fn output_size(&self) -> Size<i32, Physical> {190
+ self.output191
+ }192
+193
+ fn wait(&mut self, sync: &SyncPoint) -> Result<(), Error> {194
+ sync.wait().map_err(|_| Error::Interrupted)195
+ }196
+197
+ fn finish(mut self) -> Result<SyncPoint, Error> {198
+ let ops = std::mem::take(&mut self.ops);199
+ self.renderer.submit(self.target, ops)200
+ }201
+}@@ -0,0 +1,276 @@
1
+use crate::Error;2
+use crate::device::Shared;3
+use crate::format::{components, vk_format};4
+use ash::vk;5
+use gpu_allocator::MemoryLocation;6
+use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};7
+use smithay::backend::allocator::Fourcc;8
+use smithay::backend::renderer::{Texture, TextureMapping};9
+use std::fmt;10
+use std::sync::{Arc, Mutex};11
+12
+pub(crate) struct ImageInner {13
+ pub shared: Arc<Shared>,14
+ pub image: vk::Image,15
+ pub view: vk::ImageView,16
+ pub allocation: Mutex<Option<Allocation>>,17
+ pub fourcc: Fourcc,18
+ pub format: vk::Format,19
+ pub width: u32,20
+ pub height: u32,21
+ pub layout: Mutex<vk::ImageLayout>,22
+}23
+24
+impl fmt::Debug for ImageInner {25
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {26
+ f.debug_struct("ImageInner")27
+ .field("image", &self.image)28
+ .field("fourcc", &self.fourcc)29
+ .field("size", &(self.width, self.height))30
+ .finish()31
+ }32
+}33
+34
+impl Drop for ImageInner {35
+ fn drop(&mut self) {36
+ let d = &self.shared.device.device;37
+ unsafe {38
+ d.destroy_image_view(self.view, None);39
+ d.destroy_image(self.image, None);40
+ }41
+ if let Some(a) = self42
+ .allocation43
+ .lock()44
+ .unwrap_or_else(|e| e.into_inner())45
+ .take()46
+ {47
+ let _ = self48
+ .shared49
+ .allocator50
+ .lock()51
+ .unwrap_or_else(|e| e.into_inner())52
+ .free(a);53
+ }54
+ }55
+}56
+57
+impl ImageInner {58
+ pub fn create(59
+ shared: &Arc<Shared>,60
+ fourcc: Fourcc,61
+ width: u32,62
+ height: u32,63
+ usage: vk::ImageUsageFlags,64
+ ) -> Result<Arc<Self>, Error> {65
+ let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;66
+ if width == 0 || height == 0 {67
+ return Err(Error::InvalidSize(width as i32, height as i32));68
+ }69
+ let d = &shared.device.device;70
+ let info = vk::ImageCreateInfo::default()71
+ .image_type(vk::ImageType::TYPE_2D)72
+ .format(fmt.format)73
+ .extent(vk::Extent3D {74
+ width,75
+ height,76
+ depth: 1,77
+ })78
+ .mip_levels(1)79
+ .array_layers(1)80
+ .samples(vk::SampleCountFlags::TYPE_1)81
+ .tiling(vk::ImageTiling::OPTIMAL)82
+ .usage(usage)83
+ .initial_layout(vk::ImageLayout::UNDEFINED);84
+ let image = unsafe { d.create_image(&info, None)? };85
+ let requirements = unsafe { d.get_image_memory_requirements(image) };86
+ let allocation = shared87
+ .allocator88
+ .lock()89
+ .unwrap_or_else(|e| e.into_inner())90
+ .allocate(&AllocationCreateDesc {91
+ name: "image",92
+ requirements,93
+ location: MemoryLocation::GpuOnly,94
+ linear: false,95
+ allocation_scheme: AllocationScheme::GpuAllocatorManaged,96
+ });97
+ let allocation = match allocation {98
+ Ok(a) => a,99
+ Err(e) => {100
+ unsafe { d.destroy_image(image, None) };101
+ return Err(e.into());102
+ }103
+ };104
+ unsafe { d.bind_image_memory(image, allocation.memory(), allocation.offset())? };105
+ let view_info = vk::ImageViewCreateInfo::default()106
+ .image(image)107
+ .view_type(vk::ImageViewType::TYPE_2D)108
+ .format(fmt.format)109
+ .components(components(fmt.opaque))110
+ .subresource_range(color_range());111
+ let view = unsafe { d.create_image_view(&view_info, None)? };112
+ Ok(Arc::new(Self {113
+ shared: shared.clone(),114
+ image,115
+ view,116
+ allocation: Mutex::new(Some(allocation)),117
+ fourcc,118
+ format: fmt.format,119
+ width,120
+ height,121
+ layout: Mutex::new(vk::ImageLayout::UNDEFINED),122
+ }))123
+ }124
+125
+ pub fn layout(&self) -> vk::ImageLayout {126
+ *self.layout.lock().unwrap_or_else(|e| e.into_inner())127
+ }128
+129
+ pub fn set_layout(&self, layout: vk::ImageLayout) {130
+ *self.layout.lock().unwrap_or_else(|e| e.into_inner()) = layout;131
+ }132
+}133
+134
+pub(crate) fn color_range() -> vk::ImageSubresourceRange {135
+ vk::ImageSubresourceRange::default()136
+ .aspect_mask(vk::ImageAspectFlags::COLOR)137
+ .level_count(1)138
+ .layer_count(1)139
+}140
+141
+/// A host-visible buffer for uploads and readbacks.142
+pub(crate) struct Staging {143
+ pub shared: Arc<Shared>,144
+ pub buffer: vk::Buffer,145
+ pub allocation: Option<Allocation>,146
+}147
+148
+impl Staging {149
+ pub fn new(150
+ shared: &Arc<Shared>,151
+ size: usize,152
+ usage: vk::BufferUsageFlags,153
+ location: MemoryLocation,154
+ ) -> Result<Self, Error> {155
+ let d = &shared.device.device;156
+ let buffer = unsafe {157
+ d.create_buffer(158
+ &vk::BufferCreateInfo::default()159
+ .size(size as u64)160
+ .usage(usage),161
+ None,162
+ )?163
+ };164
+ let requirements = unsafe { d.get_buffer_memory_requirements(buffer) };165
+ let allocation = shared166
+ .allocator167
+ .lock()168
+ .unwrap_or_else(|e| e.into_inner())169
+ .allocate(&AllocationCreateDesc {170
+ name: "staging",171
+ requirements,172
+ location,173
+ linear: true,174
+ allocation_scheme: AllocationScheme::GpuAllocatorManaged,175
+ });176
+ let allocation = match allocation {177
+ Ok(a) => a,178
+ Err(e) => {179
+ unsafe { d.destroy_buffer(buffer, None) };180
+ return Err(e.into());181
+ }182
+ };183
+ unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? };184
+ Ok(Self {185
+ shared: shared.clone(),186
+ buffer,187
+ allocation: Some(allocation),188
+ })189
+ }190
+191
+ pub fn bytes(&self) -> &[u8] {192
+ self.allocation193
+ .as_ref()194
+ .and_then(|a| a.mapped_slice())195
+ .unwrap_or(&[])196
+ }197
+198
+ pub fn bytes_mut(&mut self) -> &mut [u8] {199
+ self.allocation200
+ .as_mut()201
+ .and_then(|a| a.mapped_slice_mut())202
+ .unwrap_or(&mut [])203
+ }204
+}205
+206
+impl Drop for Staging {207
+ fn drop(&mut self) {208
+ unsafe { self.shared.device.device.destroy_buffer(self.buffer, None) };209
+ if let Some(a) = self.allocation.take() {210
+ let _ = self211
+ .shared212
+ .allocator213
+ .lock()214
+ .unwrap_or_else(|e| e.into_inner())215
+ .free(a);216
+ }217
+ }218
+}219
+220
+/// A sampled texture.221
+#[derive(Clone, Debug)]222
+pub struct VulkanTexture(pub(crate) Arc<ImageInner>);223
+224
+/// An image the renderer can draw into, from [`smithay::backend::renderer::Offscreen`].225
+#[derive(Debug)]226
+pub struct VulkanImage(pub(crate) Arc<ImageInner>);227
+228
+impl VulkanImage {229
+ /// The image's contents as a texture, for drawing it elsewhere.230
+ pub fn texture(&self) -> VulkanTexture {231
+ VulkanTexture(self.0.clone())232
+ }233
+}234
+235
+/// A bound render target.236
+#[derive(Debug)]237
+pub struct VulkanTarget<'a>(pub(crate) &'a mut VulkanImage);238
+239
+/// Pixels read back from the GPU.240
+#[derive(Debug)]241
+pub struct VulkanMapping {242
+ pub(crate) data: Vec<u8>,243
+ pub(crate) width: u32,244
+ pub(crate) height: u32,245
+ pub(crate) fourcc: Fourcc,246
+}247
+248
+macro_rules! texture_impl {249
+ ($t:ty, |$s:ident| $inner:expr) => {250
+ impl Texture for $t {251
+ fn width(&self) -> u32 {252
+ let $s = self;253
+ $inner.width254
+ }255
+ fn height(&self) -> u32 {256
+ let $s = self;257
+ $inner.height258
+ }259
+ fn format(&self) -> Option<Fourcc> {260
+ let $s = self;261
+ Some($inner.fourcc)262
+ }263
+ }264
+ };265
+}266
+267
+texture_impl!(VulkanTexture, |s| s.0);268
+texture_impl!(VulkanImage, |s| s.0);269
+texture_impl!(VulkanTarget<'_>, |s| s.0.0);270
+texture_impl!(VulkanMapping, |s| s);271
+272
+impl TextureMapping for VulkanMapping {273
+ fn flipped(&self) -> bool {274
+ false275
+ }276
+}@@ -1 +1,19 @@
1
+//! Vulkan renderer implementing smithay's renderer traits.2
+//!3
+//! A frame records its draws as a list of operations and submits them in one4
+//! command buffer when it finishes, returning a fence as its sync point.15
6
+mod device;7
+mod error;8
+mod format;9
+mod frame;10
+mod image;11
+mod pipeline;12
+mod renderer;13
+mod sync;14
+mod transform;15
+16
+pub use error::Error;17
+pub use frame::VulkanFrame;18
+pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};19
+pub use renderer::VulkanRenderer;@@ -0,0 +1,169 @@
1
+use crate::Error;2
+use crate::device::Shared;3
+use ash::vk;4
+use std::collections::HashMap;5
+use std::sync::Arc;6
+7
+/// Push constants shared by every shader, see `shaders/push.glsl`.8
+/// `dst` is the quad in normalized device coordinates as (x, y, w, h),9
+/// `uv` the texture coordinate at each corner (TL, TR, BL, BR).10
+#[repr(C)]11
+#[derive(Clone, Copy, Debug, Default)]12
+pub(crate) struct Push {13
+ pub color: [f32; 4],14
+ pub dst: [f32; 4],15
+ pub uv: [[f32; 2]; 4],16
+}17
+18
+impl Push {19
+ pub fn bytes(&self) -> &[u8] {20
+ unsafe { std::slice::from_raw_parts((self as *const Self).cast::<u8>(), size_of::<Self>()) }21
+ }22
+}23
+24
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]25
+pub(crate) enum Kind {26
+ Solid,27
+ Texture,28
+}29
+30
+pub(crate) struct Pipelines {31
+ shared: Arc<Shared>,32
+ pub set_layout: vk::DescriptorSetLayout,33
+ pub layout: vk::PipelineLayout,34
+ vert: vk::ShaderModule,35
+ solid: vk::ShaderModule,36
+ texture: vk::ShaderModule,37
+ cache: HashMap<(vk::Format, Kind), vk::Pipeline>,38
+}39
+40
+const QUAD_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/quad.vert.spv"));41
+const SOLID_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/solid.frag.spv"));42
+const TEXTURE_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/texture.frag.spv"));43
+44
+impl Pipelines {45
+ pub fn new(shared: &Arc<Shared>) -> Result<Self, Error> {46
+ let d = &shared.device.device;47
+ let bindings = [vk::DescriptorSetLayoutBinding::default()48
+ .binding(0)49
+ .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)50
+ .descriptor_count(1)51
+ .stage_flags(vk::ShaderStageFlags::FRAGMENT)];52
+ let set_layout = unsafe {53
+ d.create_descriptor_set_layout(54
+ &vk::DescriptorSetLayoutCreateInfo::default()55
+ .flags(vk::DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR)56
+ .bindings(&bindings),57
+ None,58
+ )?59
+ };60
+ let ranges = [vk::PushConstantRange::default()61
+ .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)62
+ .size(size_of::<Push>() as u32)];63
+ let set_layouts = [set_layout];64
+ let layout = unsafe {65
+ d.create_pipeline_layout(66
+ &vk::PipelineLayoutCreateInfo::default()67
+ .set_layouts(&set_layouts)68
+ .push_constant_ranges(&ranges),69
+ None,70
+ )?71
+ };72
+ let module = |code: &[u8]| -> Result<vk::ShaderModule, Error> {73
+ let words = ash::util::read_spv(&mut std::io::Cursor::new(code))74
+ .map_err(|_| vk::Result::ERROR_INVALID_SHADER_NV)?;75
+ Ok(unsafe {76
+ d.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&words), None)?77
+ })78
+ };79
+ Ok(Self {80
+ shared: shared.clone(),81
+ set_layout,82
+ layout,83
+ vert: module(QUAD_VERT)?,84
+ solid: module(SOLID_FRAG)?,85
+ texture: module(TEXTURE_FRAG)?,86
+ cache: HashMap::new(),87
+ })88
+ }89
+90
+ pub fn get(&mut self, format: vk::Format, kind: Kind) -> Result<vk::Pipeline, Error> {91
+ if let Some(&p) = self.cache.get(&(format, kind)) {92
+ return Ok(p);93
+ }94
+ let d = &self.shared.device.device;95
+ let frag = match kind {96
+ Kind::Solid => self.solid,97
+ Kind::Texture => self.texture,98
+ };99
+ let stages = [100
+ vk::PipelineShaderStageCreateInfo::default()101
+ .stage(vk::ShaderStageFlags::VERTEX)102
+ .module(self.vert)103
+ .name(c"main"),104
+ vk::PipelineShaderStageCreateInfo::default()105
+ .stage(vk::ShaderStageFlags::FRAGMENT)106
+ .module(frag)107
+ .name(c"main"),108
+ ];109
+ let vertex = vk::PipelineVertexInputStateCreateInfo::default();110
+ let assembly = vk::PipelineInputAssemblyStateCreateInfo::default()111
+ .topology(vk::PrimitiveTopology::TRIANGLE_STRIP);112
+ let viewport = vk::PipelineViewportStateCreateInfo::default()113
+ .viewport_count(1)114
+ .scissor_count(1);115
+ let raster = vk::PipelineRasterizationStateCreateInfo::default()116
+ .polygon_mode(vk::PolygonMode::FILL)117
+ .cull_mode(vk::CullModeFlags::NONE)118
+ .line_width(1.0);119
+ let multisample = vk::PipelineMultisampleStateCreateInfo::default()120
+ .rasterization_samples(vk::SampleCountFlags::TYPE_1);121
+ let attachments = [vk::PipelineColorBlendAttachmentState::default()122
+ .blend_enable(true)123
+ .src_color_blend_factor(vk::BlendFactor::ONE)124
+ .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)125
+ .color_blend_op(vk::BlendOp::ADD)126
+ .src_alpha_blend_factor(vk::BlendFactor::ONE)127
+ .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)128
+ .alpha_blend_op(vk::BlendOp::ADD)129
+ .color_write_mask(vk::ColorComponentFlags::RGBA)];130
+ let blend = vk::PipelineColorBlendStateCreateInfo::default().attachments(&attachments);131
+ let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];132
+ let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);133
+ let formats = [format];134
+ let mut rendering =135
+ vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&formats);136
+ let info = vk::GraphicsPipelineCreateInfo::default()137
+ .stages(&stages)138
+ .vertex_input_state(&vertex)139
+ .input_assembly_state(&assembly)140
+ .viewport_state(&viewport)141
+ .rasterization_state(&raster)142
+ .multisample_state(&multisample)143
+ .color_blend_state(&blend)144
+ .dynamic_state(&dynamic)145
+ .layout(self.layout)146
+ .push_next(&mut rendering);147
+ let pipeline =148
+ unsafe { d.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None) }149
+ .map_err(|(_, e)| e)?[0];150
+ self.cache.insert((format, kind), pipeline);151
+ Ok(pipeline)152
+ }153
+}154
+155
+impl Drop for Pipelines {156
+ fn drop(&mut self) {157
+ let d = &self.shared.device.device;158
+ unsafe {159
+ for (_, p) in self.cache.drain() {160
+ d.destroy_pipeline(p, None);161
+ }162
+ d.destroy_shader_module(self.vert, None);163
+ d.destroy_shader_module(self.solid, None);164
+ d.destroy_shader_module(self.texture, None);165
+ d.destroy_pipeline_layout(self.layout, None);166
+ d.destroy_descriptor_set_layout(self.set_layout, None);167
+ }168
+ }169
+}@@ -0,0 +1,698 @@
1
+use crate::Error;2
+use crate::device::Shared;3
+use crate::format::{BYTES_PER_PIXEL, SUPPORTED};4
+use crate::frame::{Op, VulkanFrame};5
+use crate::image::{6
+ ImageInner, Staging, VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture, color_range,7
+};8
+use crate::pipeline::Pipelines;9
+use crate::sync::{FenceInner, VkFence};10
+use ash::vk;11
+use gpu_allocator::MemoryLocation;12
+use smithay::backend::allocator::Fourcc;13
+use smithay::backend::renderer::sync::SyncPoint;14
+use smithay::backend::renderer::{15
+ Bind, ContextId, DebugFlags, ExportMem, ImportMem, Offscreen, Renderer, RendererSuper, Texture,16
+ TextureFilter,17
+};18
+use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};19
+use std::fmt;20
+use std::sync::Arc;21
+22
+struct Pending {23
+ fence: Arc<FenceInner>,24
+ cmd: vk::CommandBuffer,25
+ _keep: Vec<VulkanTexture>,26
+}27
+28
+pub struct VulkanRenderer {29
+ pending: Vec<Pending>,30
+ pipelines: Pipelines,31
+ pool: vk::CommandPool,32
+ samplers: [vk::Sampler; 4],33
+ min_filter: TextureFilter,34
+ mag_filter: TextureFilter,35
+ debug: DebugFlags,36
+ context: ContextId<VulkanTexture>,37
+ shared: Arc<Shared>,38
+}39
+40
+impl fmt::Debug for VulkanRenderer {41
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {42
+ f.debug_struct("VulkanRenderer")43
+ .field("device", &self.shared.device.name)44
+ .finish()45
+ }46
+}47
+48
+impl VulkanRenderer {49
+ pub fn new() -> Result<Self, Error> {50
+ let shared = Arc::new(Shared::new()?);51
+ let d = &shared.device.device;52
+ let pool = unsafe {53
+ d.create_command_pool(54
+ &vk::CommandPoolCreateInfo::default()55
+ .queue_family_index(shared.device.queue_family)56
+ .flags(vk::CommandPoolCreateFlags::TRANSIENT),57
+ None,58
+ )?59
+ };60
+ let filter = |f: TextureFilter| match f {61
+ TextureFilter::Linear => vk::Filter::LINEAR,62
+ TextureFilter::Nearest => vk::Filter::NEAREST,63
+ };64
+ let mut samplers = [vk::Sampler::null(); 4];65
+ for (i, s) in samplers.iter_mut().enumerate() {66
+ let (min, mag) = (sampler_filter(i >> 1), sampler_filter(i & 1));67
+ *s = unsafe {68
+ d.create_sampler(69
+ &vk::SamplerCreateInfo::default()70
+ .min_filter(filter(min))71
+ .mag_filter(filter(mag))72
+ .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)73
+ .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)74
+ .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE),75
+ None,76
+ )?77
+ };78
+ }79
+ Ok(Self {80
+ pending: Vec::new(),81
+ pipelines: Pipelines::new(&shared)?,82
+ pool,83
+ samplers,84
+ min_filter: TextureFilter::Linear,85
+ mag_filter: TextureFilter::Linear,86
+ debug: DebugFlags::empty(),87
+ context: ContextId::new(),88
+ shared,89
+ })90
+ }91
+92
+ /// Name of the physical device in use.93
+ pub fn device_name(&self) -> &str {94
+ &self.shared.device.name95
+ }96
+97
+ pub(crate) fn shared(&self) -> &Arc<Shared> {98
+ &self.shared99
+ }100
+101
+ pub(crate) fn context_id_ref(&self) -> &ContextId<VulkanTexture> {102
+ &self.context103
+ }104
+105
+ fn sampler(&self) -> vk::Sampler {106
+ let idx = |f: TextureFilter| usize::from(f == TextureFilter::Nearest);107
+ self.samplers[(idx(self.min_filter) << 1) | idx(self.mag_filter)]108
+ }109
+110
+ fn reap(&mut self) {111
+ let d = &self.shared.device.device;112
+ let pool = self.pool;113
+ self.pending.retain(|p| {114
+ if !p.fence.signaled() {115
+ return true;116
+ }117
+ unsafe { d.free_command_buffers(pool, &[p.cmd]) };118
+ false119
+ });120
+ }121
+122
+ fn begin(&self) -> Result<vk::CommandBuffer, Error> {123
+ let d = &self.shared.device.device;124
+ let cmd = unsafe {125
+ d.allocate_command_buffers(126
+ &vk::CommandBufferAllocateInfo::default()127
+ .command_pool(self.pool)128
+ .level(vk::CommandBufferLevel::PRIMARY)129
+ .command_buffer_count(1),130
+ )?[0]131
+ };132
+ unsafe {133
+ d.begin_command_buffer(134
+ cmd,135
+ &vk::CommandBufferBeginInfo::default()136
+ .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),137
+ )?138
+ };139
+ Ok(cmd)140
+ }141
+142
+ fn end_and_submit(&self, cmd: vk::CommandBuffer) -> Result<Arc<FenceInner>, Error> {143
+ let d = &self.shared.device.device;144
+ unsafe { d.end_command_buffer(cmd)? };145
+ let fence = FenceInner::new(&self.shared)?;146
+ let cmds = [vk::CommandBufferSubmitInfo::default().command_buffer(cmd)];147
+ let submit = vk::SubmitInfo2::default().command_buffer_infos(&cmds);148
+ let queue = self149
+ .shared150
+ .device151
+ .queue152
+ .lock()153
+ .unwrap_or_else(|e| e.into_inner());154
+ unsafe { d.queue_submit2(*queue, &[submit], fence.fence)? };155
+ Ok(fence)156
+ }157
+158
+ /// Records, submits and waits for a short command buffer.159
+ fn one_shot(160
+ &mut self,161
+ record: impl FnOnce(&ash::Device, vk::CommandBuffer),162
+ ) -> Result<(), Error> {163
+ let cmd = self.begin()?;164
+ record(&self.shared.device.device, cmd);165
+ let fence = self.end_and_submit(cmd)?;166
+ let res = fence.wait();167
+ unsafe {168
+ self.shared169
+ .device170
+ .device171
+ .free_command_buffers(self.pool, &[cmd])172
+ };173
+ Ok(res?)174
+ }175
+176
+ pub(crate) fn submit(177
+ &mut self,178
+ target: &mut VulkanTarget<'_>,179
+ ops: Vec<Op>,180
+ ) -> Result<SyncPoint, Error> {181
+ self.reap();182
+ let image = target.0.0.clone();183
+ let mut keep = vec![VulkanTexture(image.clone())];184
+ let mut transitions = Vec::new();185
+ for op in &ops {186
+ if let Op::Draw {187
+ texture: Some(t), ..188
+ } = op189
+ {190
+ if t.0.layout() != vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL {191
+ transitions.push(t.clone());192
+ }193
+ keep.push(t.clone());194
+ }195
+ }196
+ let mut pipelines = Vec::with_capacity(ops.len());197
+ for op in &ops {198
+ pipelines.push(match op {199
+ Op::Draw { kind, .. } => Some(self.pipelines.get(image.format, *kind)?),200
+ Op::Clear { .. } => None,201
+ });202
+ }203
+ let cmd = self.begin()?;204
+ let d = &self.shared.device.device;205
+ let push = &self.shared.device.push_descriptor;206
+ let (w, h) = (image.width, image.height);207
+ let sampler = self.sampler();208
+ unsafe {209
+ for t in &transitions {210
+ barrier(211
+ d,212
+ cmd,213
+ t.0.image,214
+ t.0.layout(),215
+ vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,216
+ );217
+ t.0.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);218
+ }219
+ barrier(220
+ d,221
+ cmd,222
+ image.image,223
+ image.layout(),224
+ vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,225
+ );226
+ let attachments = [vk::RenderingAttachmentInfo::default()227
+ .image_view(image.view)228
+ .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)229
+ .load_op(vk::AttachmentLoadOp::LOAD)230
+ .store_op(vk::AttachmentStoreOp::STORE)];231
+ let area = vk::Rect2D {232
+ offset: vk::Offset2D::default(),233
+ extent: vk::Extent2D {234
+ width: w,235
+ height: h,236
+ },237
+ };238
+ d.cmd_begin_rendering(239
+ cmd,240
+ &vk::RenderingInfo::default()241
+ .render_area(area)242
+ .layer_count(1)243
+ .color_attachments(&attachments),244
+ );245
+ d.cmd_set_viewport(246
+ cmd,247
+ 0,248
+ &[vk::Viewport {249
+ x: 0.0,250
+ y: 0.0,251
+ width: w as f32,252
+ height: h as f32,253
+ min_depth: 0.0,254
+ max_depth: 1.0,255
+ }],256
+ );257
+ let mut bound = vk::Pipeline::null();258
+ for (op, pipeline) in ops.iter().zip(pipelines) {259
+ match op {260
+ Op::Clear { color, rects } => {261
+ let clear = [vk::ClearAttachment {262
+ aspect_mask: vk::ImageAspectFlags::COLOR,263
+ color_attachment: 0,264
+ clear_value: vk::ClearValue {265
+ color: vk::ClearColorValue { float32: *color },266
+ },267
+ }];268
+ let rects: Vec<_> = rects269
+ .iter()270
+ .map(|&rect| vk::ClearRect {271
+ rect,272
+ base_array_layer: 0,273
+ layer_count: 1,274
+ })275
+ .collect();276
+ d.cmd_clear_attachments(cmd, &clear, &rects);277
+ }278
+ Op::Draw {279
+ texture,280
+ push: constants,281
+ scissors,282
+ ..283
+ } => {284
+ let pipeline = pipeline.unwrap_or_default();285
+ if pipeline != bound {286
+ d.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline);287
+ bound = pipeline;288
+ }289
+ if let Some(t) = texture {290
+ let info = [vk::DescriptorImageInfo::default()291
+ .image_view(t.0.view)292
+ .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)293
+ .sampler(sampler)];294
+ let write = vk::WriteDescriptorSet::default()295
+ .dst_binding(0)296
+ .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)297
+ .image_info(&info);298
+ push.cmd_push_descriptor_set(299
+ cmd,300
+ vk::PipelineBindPoint::GRAPHICS,301
+ self.pipelines.layout,302
+ 0,303
+ &[write],304
+ );305
+ }306
+ d.cmd_push_constants(307
+ cmd,308
+ self.pipelines.layout,309
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,310
+ 0,311
+ constants.bytes(),312
+ );313
+ for s in scissors {314
+ d.cmd_set_scissor(cmd, 0, &[*s]);315
+ d.cmd_draw(cmd, 4, 1, 0, 0);316
+ }317
+ }318
+ }319
+ }320
+ d.cmd_end_rendering(cmd);321
+ }322
+ image.set_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);323
+ let fence = self.end_and_submit(cmd)?;324
+ self.pending.push(Pending {325
+ fence: fence.clone(),326
+ cmd,327
+ _keep: keep,328
+ });329
+ Ok(SyncPoint::from(VkFence(fence)))330
+ }331
+332
+ fn upload(333
+ &mut self,334
+ image: &Arc<ImageInner>,335
+ data: &[u8],336
+ region: Rectangle<i32, Buffer>,337
+ flipped: bool,338
+ ) -> Result<(), Error> {339
+ let width = image.width as usize;340
+ let needed = width * image.height as usize * BYTES_PER_PIXEL;341
+ if data.len() < needed {342
+ return Err(Error::BufferTooSmall {343
+ needed,344
+ got: data.len(),345
+ });346
+ }347
+ let mut staging = Staging::new(348
+ &self.shared,349
+ needed,350
+ vk::BufferUsageFlags::TRANSFER_SRC,351
+ MemoryLocation::CpuToGpu,352
+ )?;353
+ let row = width * BYTES_PER_PIXEL;354
+ let rows = image.height as usize;355
+ let dst = staging.bytes_mut();356
+ for y in 0..rows {357
+ let from = if flipped { rows - 1 - y } else { y };358
+ dst[y * row..(y + 1) * row].copy_from_slice(&data[from * row..(from + 1) * row]);359
+ }360
+ let old = image.layout();361
+ let copy = vk::BufferImageCopy::default()362
+ .buffer_offset(363
+ ((region.loc.y as usize * width + region.loc.x as usize) * BYTES_PER_PIXEL) as u64,364
+ )365
+ .buffer_row_length(image.width)366
+ .image_subresource(367
+ vk::ImageSubresourceLayers::default()368
+ .aspect_mask(vk::ImageAspectFlags::COLOR)369
+ .layer_count(1),370
+ )371
+ .image_offset(vk::Offset3D {372
+ x: region.loc.x,373
+ y: region.loc.y,374
+ z: 0,375
+ })376
+ .image_extent(vk::Extent3D {377
+ width: region.size.w as u32,378
+ height: region.size.h as u32,379
+ depth: 1,380
+ });381
+ let (img, buffer) = (image.image, staging.buffer);382
+ self.one_shot(|d, cmd| unsafe {383
+ barrier(d, cmd, img, old, vk::ImageLayout::TRANSFER_DST_OPTIMAL);384
+ d.cmd_copy_buffer_to_image(385
+ cmd,386
+ buffer,387
+ img,388
+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,389
+ &[copy],390
+ );391
+ barrier(392
+ d,393
+ cmd,394
+ img,395
+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,396
+ vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,397
+ );398
+ })?;399
+ image.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);400
+ Ok(())401
+ }402
+403
+ fn read_back(404
+ &mut self,405
+ image: &Arc<ImageInner>,406
+ region: Rectangle<i32, Buffer>,407
+ fourcc: Fourcc,408
+ ) -> Result<VulkanMapping, Error> {409
+ let region = region410
+ .intersection(Rectangle::from_size(411
+ (image.width as i32, image.height as i32).into(),412
+ ))413
+ .unwrap_or_default();414
+ let (w, h) = (region.size.w.max(0) as u32, region.size.h.max(0) as u32);415
+ let swap = needs_swizzle(image.fourcc, fourcc)?;416
+ let len = (w as usize * h as usize * BYTES_PER_PIXEL).max(BYTES_PER_PIXEL);417
+ let staging = Staging::new(418
+ &self.shared,419
+ len,420
+ vk::BufferUsageFlags::TRANSFER_DST,421
+ MemoryLocation::GpuToCpu,422
+ )?;423
+ if w > 0 && h > 0 {424
+ let old = image.layout();425
+ let copy = vk::BufferImageCopy::default()426
+ .image_subresource(427
+ vk::ImageSubresourceLayers::default()428
+ .aspect_mask(vk::ImageAspectFlags::COLOR)429
+ .layer_count(1),430
+ )431
+ .image_offset(vk::Offset3D {432
+ x: region.loc.x,433
+ y: region.loc.y,434
+ z: 0,435
+ })436
+ .image_extent(vk::Extent3D {437
+ width: w,438
+ height: h,439
+ depth: 1,440
+ });441
+ let (img, buffer) = (image.image, staging.buffer);442
+ self.one_shot(|d, cmd| unsafe {443
+ barrier(d, cmd, img, old, vk::ImageLayout::TRANSFER_SRC_OPTIMAL);444
+ d.cmd_copy_image_to_buffer(445
+ cmd,446
+ img,447
+ vk::ImageLayout::TRANSFER_SRC_OPTIMAL,448
+ buffer,449
+ &[copy],450
+ );451
+ })?;452
+ image.set_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL);453
+ }454
+ let mut data = staging.bytes()[..w as usize * h as usize * BYTES_PER_PIXEL].to_vec();455
+ if swap {456
+ for px in data.as_chunks_mut::<BYTES_PER_PIXEL>().0 {457
+ px.swap(0, 2);458
+ }459
+ }460
+ Ok(VulkanMapping {461
+ data,462
+ width: w,463
+ height: h,464
+ fourcc,465
+ })466
+ }467
+468
+ fn own(&self, image: &Arc<ImageInner>) -> Result<(), Error> {469
+ if Arc::ptr_eq(&image.shared, &self.shared) {470
+ Ok(())471
+ } else {472
+ Err(Error::ForeignTexture)473
+ }474
+ }475
+}476
+477
+impl Drop for VulkanRenderer {478
+ fn drop(&mut self) {479
+ let d = &self.shared.device.device;480
+ unsafe {481
+ let _ = d.device_wait_idle();482
+ for p in self.pending.drain(..) {483
+ d.free_command_buffers(self.pool, &[p.cmd]);484
+ }485
+ for s in self.samplers {486
+ d.destroy_sampler(s, None);487
+ }488
+ d.destroy_command_pool(self.pool, None);489
+ }490
+ }491
+}492
+493
+fn sampler_filter(bit: usize) -> TextureFilter {494
+ if bit == 0 {495
+ TextureFilter::Linear496
+ } else {497
+ TextureFilter::Nearest498
+ }499
+}500
+501
+/// A full barrier between all earlier and later work on the image. Coarse,502
+/// but correct for every transition the renderer makes.503
+unsafe fn barrier(504
+ d: &ash::Device,505
+ cmd: vk::CommandBuffer,506
+ image: vk::Image,507
+ old: vk::ImageLayout,508
+ new: vk::ImageLayout,509
+) {510
+ let b = [vk::ImageMemoryBarrier2::default()511
+ .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)512
+ .src_access_mask(vk::AccessFlags2::MEMORY_WRITE)513
+ .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)514
+ .dst_access_mask(vk::AccessFlags2::MEMORY_READ | vk::AccessFlags2::MEMORY_WRITE)515
+ .old_layout(old)516
+ .new_layout(new)517
+ .image(image)518
+ .subresource_range(color_range())];519
+ unsafe {520
+ d.cmd_pipeline_barrier2(521
+ cmd,522
+ &vk::DependencyInfo::default().image_memory_barriers(&b),523
+ )524
+ };525
+}526
+527
+/// Whether reading `from` as `to` needs red and blue swapped.528
+fn needs_swizzle(from: Fourcc, to: Fourcc) -> Result<bool, Error> {529
+ let bgr = |f: Fourcc| match f {530
+ Fourcc::Argb8888 | Fourcc::Xrgb8888 => Ok(true),531
+ Fourcc::Abgr8888 | Fourcc::Xbgr8888 => Ok(false),532
+ f => Err(Error::UnsupportedFormat(f)),533
+ };534
+ Ok(bgr(from)? != bgr(to)?)535
+}536
+537
+impl RendererSuper for VulkanRenderer {538
+ type Error = Error;539
+ type TextureId = VulkanTexture;540
+ type Framebuffer<'buffer> = VulkanTarget<'buffer>;541
+ type Frame<'frame, 'buffer>542
+ = VulkanFrame<'frame, 'buffer>543
+ where544
+ 'buffer: 'frame,545
+ Self: 'frame;546
+}547
+548
+impl Renderer for VulkanRenderer {549
+ fn context_id(&self) -> ContextId<VulkanTexture> {550
+ self.context.clone()551
+ }552
+553
+ fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Error> {554
+ self.min_filter = filter;555
+ Ok(())556
+ }557
+558
+ fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Error> {559
+ self.mag_filter = filter;560
+ Ok(())561
+ }562
+563
+ fn set_debug_flags(&mut self, flags: DebugFlags) {564
+ self.debug = flags;565
+ }566
+567
+ fn debug_flags(&self) -> DebugFlags {568
+ self.debug569
+ }570
+571
+ fn render<'frame, 'buffer>(572
+ &'frame mut self,573
+ framebuffer: &'frame mut VulkanTarget<'buffer>,574
+ output_size: Size<i32, Physical>,575
+ dst_transform: Transform,576
+ ) -> Result<VulkanFrame<'frame, 'buffer>, Error>577
+ where578
+ 'buffer: 'frame,579
+ {580
+ self.own(&framebuffer.0.0)?;581
+ self.reap();582
+ Ok(VulkanFrame::new(583
+ self,584
+ framebuffer,585
+ output_size,586
+ dst_transform,587
+ ))588
+ }589
+590
+ fn wait(&mut self, sync: &SyncPoint) -> Result<(), Error> {591
+ sync.wait().map_err(|_| Error::Interrupted)592
+ }593
+594
+ fn cleanup_texture_cache(&mut self) -> Result<(), Error> {595
+ self.reap();596
+ Ok(())597
+ }598
+}599
+600
+impl Bind<VulkanImage> for VulkanRenderer {601
+ fn bind<'a>(&mut self, target: &'a mut VulkanImage) -> Result<VulkanTarget<'a>, Error> {602
+ self.own(&target.0)?;603
+ Ok(VulkanTarget(target))604
+ }605
+}606
+607
+impl Offscreen<VulkanImage> for VulkanRenderer {608
+ fn create_buffer(609
+ &mut self,610
+ format: Fourcc,611
+ size: Size<i32, Buffer>,612
+ ) -> Result<VulkanImage, Error> {613
+ if size.w <= 0 || size.h <= 0 {614
+ return Err(Error::InvalidSize(size.w, size.h));615
+ }616
+ let usage = vk::ImageUsageFlags::COLOR_ATTACHMENT617
+ | vk::ImageUsageFlags::SAMPLED618
+ | vk::ImageUsageFlags::TRANSFER_SRC619
+ | vk::ImageUsageFlags::TRANSFER_DST;620
+ Ok(VulkanImage(ImageInner::create(621
+ &self.shared,622
+ format,623
+ size.w as u32,624
+ size.h as u32,625
+ usage,626
+ )?))627
+ }628
+}629
+630
+impl ImportMem for VulkanRenderer {631
+ fn import_memory(632
+ &mut self,633
+ data: &[u8],634
+ format: Fourcc,635
+ size: Size<i32, Buffer>,636
+ flipped: bool,637
+ ) -> Result<VulkanTexture, Error> {638
+ if size.w <= 0 || size.h <= 0 {639
+ return Err(Error::InvalidSize(size.w, size.h));640
+ }641
+ let usage = vk::ImageUsageFlags::SAMPLED642
+ | vk::ImageUsageFlags::TRANSFER_DST643
+ | vk::ImageUsageFlags::TRANSFER_SRC;644
+ let image = ImageInner::create(&self.shared, format, size.w as u32, size.h as u32, usage)?;645
+ self.upload(&image, data, Rectangle::from_size(size), flipped)?;646
+ Ok(VulkanTexture(image))647
+ }648
+649
+ fn update_memory(650
+ &mut self,651
+ texture: &VulkanTexture,652
+ data: &[u8],653
+ region: Rectangle<i32, Buffer>,654
+ ) -> Result<(), Error> {655
+ self.own(&texture.0)?;656
+ let Some(region) = region.intersection(Rectangle::from_size(texture.size())) else {657
+ return Ok(());658
+ };659
+ self.upload(&texture.0, data, region, false)660
+ }661
+662
+ fn mem_formats(&self) -> Box<dyn Iterator<Item = Fourcc>> {663
+ Box::new(SUPPORTED.into_iter())664
+ }665
+}666
+667
+impl ExportMem for VulkanRenderer {668
+ type TextureMapping = VulkanMapping;669
+670
+ fn copy_framebuffer(671
+ &mut self,672
+ target: &VulkanTarget<'_>,673
+ region: Rectangle<i32, Buffer>,674
+ format: Fourcc,675
+ ) -> Result<VulkanMapping, Error> {676
+ self.own(&target.0.0)?;677
+ let image = target.0.0.clone();678
+ self.read_back(&image, region, format)679
+ }680
+681
+ fn copy_texture(682
+ &mut self,683
+ texture: &VulkanTexture,684
+ region: Rectangle<i32, Buffer>,685
+ format: Fourcc,686
+ ) -> Result<VulkanMapping, Error> {687
+ self.own(&texture.0)?;688
+ self.read_back(&texture.0, region, format)689
+ }690
+691
+ fn can_read_texture(&mut self, _texture: &VulkanTexture) -> Result<bool, Error> {692
+ Ok(true)693
+ }694
+695
+ fn map_texture<'a>(&mut self, mapping: &'a VulkanMapping) -> Result<&'a [u8], Error> {696
+ Ok(&mapping.data)697
+ }698
+}@@ -0,0 +1,71 @@
1
+use crate::device::Shared;2
+use ash::vk;3
+use smithay::backend::renderer::sync::{Fence, Interrupted};4
+use std::fmt;5
+use std::sync::Arc;6
+7
+pub(crate) struct FenceInner {8
+ pub shared: Arc<Shared>,9
+ pub fence: vk::Fence,10
+}11
+12
+impl FenceInner {13
+ pub fn new(shared: &Arc<Shared>) -> Result<Arc<Self>, vk::Result> {14
+ let fence = unsafe {15
+ shared16
+ .device17
+ .device18
+ .create_fence(&vk::FenceCreateInfo::default(), None)?19
+ };20
+ Ok(Arc::new(Self {21
+ shared: shared.clone(),22
+ fence,23
+ }))24
+ }25
+26
+ pub fn signaled(&self) -> bool {27
+ unsafe { self.shared.device.device.get_fence_status(self.fence) }.unwrap_or(true)28
+ }29
+30
+ pub fn wait(&self) -> Result<(), vk::Result> {31
+ unsafe {32
+ self.shared33
+ .device34
+ .device35
+ .wait_for_fences(&[self.fence], true, u64::MAX)36
+ }37
+ }38
+}39
+40
+impl Drop for FenceInner {41
+ fn drop(&mut self) {42
+ unsafe { self.shared.device.device.destroy_fence(self.fence, None) };43
+ }44
+}45
+46
+impl fmt::Debug for FenceInner {47
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {48
+ f.debug_tuple("FenceInner").field(&self.fence).finish()49
+ }50
+}51
+52
+#[derive(Debug)]53
+pub(crate) struct VkFence(pub Arc<FenceInner>);54
+55
+impl Fence for VkFence {56
+ fn is_signaled(&self) -> bool {57
+ self.0.signaled()58
+ }59
+60
+ fn wait(&self) -> Result<(), Interrupted> {61
+ self.0.wait().map_err(|_| Interrupted)62
+ }63
+64
+ fn is_exportable(&self) -> bool {65
+ false66
+ }67
+68
+ fn export(&self) -> Option<std::os::fd::OwnedFd> {69
+ None70
+ }71
+}@@ -0,0 +1,114 @@
1
+use glam::{Affine2, Vec2};2
+use smithay::utils::{Buffer, Physical, Point, Rectangle, Size, Transform};3
+4
+/// Texture coordinates at the destination's corners (TL, TR, BL, BR), using5
+/// the same mapping as smithay's GLES renderer.6
+pub(crate) fn texture_uvs(7
+ src: Rectangle<f64, Buffer>,8
+ dst: Size<i32, Physical>,9
+ texture: Size<i32, Buffer>,10
+ transform: Transform,11
+) -> [[f32; 2]; 4] {12
+ let dst_src = transform.transform_size(src.size);13
+ let scale = Vec2::new(14
+ (dst_src.w / f64::from(dst.w)) as f32,15
+ (dst_src.h / f64::from(dst.h)) as f32,16
+ );17
+ let (w, h) = (dst_src.w as f32, dst_src.h as f32);18
+ let translation = match transform {19
+ Transform::Normal | Transform::Flipped90 => Vec2::ZERO,20
+ Transform::_90 => Vec2::new(0.0, w),21
+ Transform::_180 => Vec2::new(w, h),22
+ Transform::_270 => Vec2::new(h, 0.0),23
+ Transform::Flipped => Vec2::new(w, 0.0),24
+ Transform::Flipped180 => Vec2::new(0.0, h),25
+ Transform::Flipped270 => Vec2::new(h, w),26
+ };27
+ let mat = Affine2::from_scale(Vec2::new(1.0 / texture.w as f32, 1.0 / texture.h as f32))28
+ * Affine2::from_translation(Vec2::new(src.loc.x as f32, src.loc.y as f32))29
+ * Affine2::from_translation(translation)30
+ * transform.matrix()31
+ * Affine2::from_scale(scale);32
+ let (dw, dh) = (dst.w as f32, dst.h as f32);33
+ [34
+ Vec2::ZERO,35
+ Vec2::new(dw, 0.0),36
+ Vec2::new(0.0, dh),37
+ Vec2::new(dw, dh),38
+ ]39
+ .map(|p| mat.transform_point2(p).to_array())40
+}41
+42
+/// Maps a destination in output space into the framebuffer, permuting the43
+/// per-corner texture coordinates to follow it.44
+pub(crate) fn to_framebuffer(45
+ dst: Rectangle<i32, Physical>,46
+ uvs: [[f32; 2]; 4],47
+ output: Size<i32, Physical>,48
+ transform: Transform,49
+) -> (Rectangle<i32, Physical>, [[f32; 2]; 4]) {50
+ if transform == Transform::Normal {51
+ return (dst, uvs);52
+ }53
+ let fb = transform.transform_rect_in(dst, &output);54
+ let area = output.to_f64();55
+ let (x0, y0) = (f64::from(dst.loc.x), f64::from(dst.loc.y));56
+ let (x1, y1) = (x0 + f64::from(dst.size.w), y0 + f64::from(dst.size.h));57
+ let cx = f64::from(fb.loc.x) + f64::from(fb.size.w) / 2.0;58
+ let cy = f64::from(fb.loc.y) + f64::from(fb.size.h) / 2.0;59
+ let mut out = uvs;60
+ for (corner, uv) in [(x0, y0), (x1, y0), (x0, y1), (x1, y1)]61
+ .into_iter()62
+ .zip(uvs)63
+ {64
+ let p = transform.transform_point_in(Point::<f64, Physical>::from(corner), &area);65
+ let slot = usize::from(p.x > cx) | (usize::from(p.y > cy) << 1);66
+ out[slot] = uv;67
+ }68
+ (fb, out)69
+}70
+71
+/// Maps a rectangle in output space into the framebuffer.72
+pub(crate) fn rect_to_framebuffer(73
+ rect: Rectangle<i32, Physical>,74
+ output: Size<i32, Physical>,75
+ transform: Transform,76
+) -> Rectangle<i32, Physical> {77
+ if transform == Transform::Normal {78
+ rect79
+ } else {80
+ transform.transform_rect_in(rect, &output)81
+ }82
+}83
+84
+#[cfg(test)]85
+mod tests {86
+ use super::*;87
+88
+ #[test]89
+ fn identity_covers_the_whole_texture() {90
+ let uv = texture_uvs(91
+ Rectangle::from_size((4.0, 2.0).into()),92
+ (8, 4).into(),93
+ (4, 2).into(),94
+ Transform::Normal,95
+ );96
+ assert_eq!(uv, [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);97
+ }98
+99
+ #[test]100
+ fn crop_selects_part_of_the_texture() {101
+ let src = Rectangle::new((2.0, 0.0).into(), (2.0, 2.0).into());102
+ let uv = texture_uvs(src, (2, 2).into(), (4, 2).into(), Transform::Normal);103
+ assert_eq!(uv, [[0.5, 0.0], [1.0, 0.0], [0.5, 1.0], [1.0, 1.0]]);104
+ }105
+106
+ #[test]107
+ fn rotation_permutes_corners_without_moving_identity_outputs() {108
+ let dst = Rectangle::new((0, 0).into(), (10, 10).into());109
+ let uvs = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];110
+ let (fb, out) = to_framebuffer(dst, uvs, (10, 10).into(), Transform::_180);111
+ assert_eq!(fb, dst);112
+ assert_eq!(out, [[1.0, 1.0], [0.0, 1.0], [1.0, 0.0], [0.0, 0.0]]);113
+ }114
+}@@ -0,0 +1,258 @@
1
+//! Renders small scenes offscreen and checks pixels. Runs on whatever Vulkan2
+//! driver is available. `nix flake check` forces lavapipe.3
+4
+use monolith_render_vk::{VulkanImage, VulkanRenderer, VulkanTexture};5
+use smithay::backend::allocator::Fourcc;6
+use smithay::backend::renderer::{7
+ Bind, Color32F, ExportMem, Frame, ImportMem, Offscreen, Renderer, TextureFilter,8
+};9
+use smithay::utils::{Physical, Rectangle, Size, Transform};10
+11
+const W: i32 = 64;12
+const H: i32 = 64;13
+14
+type Pixel = [u8; 4];15
+16
+const RED: Pixel = [255, 0, 0, 255];17
+const GREEN: Pixel = [0, 255, 0, 255];18
+const BLUE: Pixel = [0, 0, 255, 255];19
+const WHITE: Pixel = [255, 255, 255, 255];20
+const BLACK: Pixel = [0, 0, 0, 255];21
+22
+struct Scene {23
+ renderer: VulkanRenderer,24
+ image: VulkanImage,25
+}26
+27
+impl Scene {28
+ fn new() -> Self {29
+ let mut renderer = VulkanRenderer::new().expect("no usable Vulkan device");30
+ let image = renderer31
+ .create_buffer(Fourcc::Argb8888, (W, H).into())32
+ .unwrap();33
+ Self { renderer, image }34
+ }35
+36
+ fn draw(37
+ &mut self,38
+ transform: Transform,39
+ f: impl FnOnce(&mut monolith_render_vk::VulkanFrame<'_, '_>),40
+ ) {41
+ let mut target = self.renderer.bind(&mut self.image).unwrap();42
+ let mut frame = self43
+ .renderer44
+ .render(&mut target, (W, H).into(), transform)45
+ .unwrap();46
+ f(&mut frame);47
+ frame.finish().unwrap().wait().unwrap();48
+ }49
+50
+ /// Pixels as RGBA, whatever the image's byte order.51
+ fn pixels(&mut self) -> Vec<Pixel> {52
+ let target = self.renderer.bind(&mut self.image).unwrap();53
+ let mapping = self54
+ .renderer55
+ .copy_framebuffer(56
+ &target,57
+ Rectangle::from_size((W, H).into()),58
+ Fourcc::Abgr8888,59
+ )60
+ .unwrap();61
+ self.renderer62
+ .map_texture(&mapping)63
+ .unwrap()64
+ .as_chunks::<4>()65
+ .066
+ .to_vec()67
+ }68
+69
+ fn texture(&mut self, size: (i32, i32), rgba: &[Pixel]) -> VulkanTexture {70
+ let bytes: Vec<u8> = rgba.iter().flatten().copied().collect();71
+ self.renderer72
+ .import_memory(&bytes, Fourcc::Abgr8888, size.into(), false)73
+ .unwrap()74
+ }75
+}76
+77
+fn at(pixels: &[Pixel], x: i32, y: i32) -> Pixel {78
+ pixels[(y * W + x) as usize]79
+}80
+81
+fn full() -> Rectangle<i32, Physical> {82
+ Rectangle::from_size((W, H).into())83
+}84
+85
+fn rgba(p: Pixel) -> Color32F {86
+ p.map(|c| f32::from(c) / 255.0).into()87
+}88
+89
+#[test]90
+fn clears_to_a_colour() {91
+ let mut s = Scene::new();92
+ s.draw(Transform::Normal, |f| {93
+ f.clear(rgba(RED), &[full()]).unwrap()94
+ });95
+ let px = s.pixels();96
+ assert!(px.iter().all(|&p| p == RED));97
+}98
+99
+#[test]100
+fn solid_fill_respects_its_rectangle() {101
+ let mut s = Scene::new();102
+ s.draw(Transform::Normal, |f| {103
+ f.clear(rgba(BLACK), &[full()]).unwrap();104
+ let dst = Rectangle::new((16, 16).into(), (32, 32).into());105
+ f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))106
+ .unwrap();107
+ });108
+ let px = s.pixels();109
+ assert_eq!(at(&px, 16, 16), GREEN);110
+ assert_eq!(at(&px, 47, 47), GREEN);111
+ assert_eq!(at(&px, 15, 16), BLACK);112
+ assert_eq!(at(&px, 48, 47), BLACK);113
+}114
+115
+#[test]116
+fn damage_limits_drawing() {117
+ let mut s = Scene::new();118
+ s.draw(Transform::Normal, |f| {119
+ f.clear(rgba(BLACK), &[full()]).unwrap();120
+ let damage = Rectangle::new((0, 0).into(), (8, 8).into());121
+ f.draw_solid(full(), &[damage], rgba(BLUE)).unwrap();122
+ });123
+ let px = s.pixels();124
+ assert_eq!(at(&px, 7, 7), BLUE);125
+ assert_eq!(at(&px, 8, 8), BLACK);126
+}127
+128
+#[test]129
+fn partial_clear_keeps_previous_contents() {130
+ let mut s = Scene::new();131
+ s.draw(Transform::Normal, |f| {132
+ f.clear(rgba(RED), &[full()]).unwrap()133
+ });134
+ s.draw(Transform::Normal, |f| {135
+ f.clear(136
+ rgba(GREEN),137
+ &[Rectangle::new((0, 0).into(), (32, 64).into())],138
+ )139
+ .unwrap()140
+ });141
+ let px = s.pixels();142
+ assert_eq!(at(&px, 0, 0), GREEN);143
+ assert_eq!(at(&px, 63, 0), RED);144
+}145
+146
+#[test]147
+fn textures_sample_with_nearest_filtering() {148
+ let mut s = Scene::new();149
+ s.renderer.upscale_filter(TextureFilter::Nearest).unwrap();150
+ let tex = s.texture((2, 2), &[RED, GREEN, BLUE, WHITE]);151
+ s.draw(Transform::Normal, |f| {152
+ f.render_texture_from_to(153
+ &tex,154
+ Rectangle::from_size((2.0, 2.0).into()),155
+ full(),156
+ &[full()],157
+ &[],158
+ Transform::Normal,159
+ 1.0,160
+ )161
+ .unwrap();162
+ });163
+ let px = s.pixels();164
+ assert_eq!(at(&px, 8, 8), RED);165
+ assert_eq!(at(&px, 56, 8), GREEN);166
+ assert_eq!(at(&px, 8, 56), BLUE);167
+ assert_eq!(at(&px, 56, 56), WHITE);168
+}169
+170
+#[test]171
+fn alpha_blends_premultiplied() {172
+ let mut s = Scene::new();173
+ let tex = s.texture((1, 1), &[WHITE]);174
+ s.draw(Transform::Normal, |f| {175
+ f.clear(rgba(BLACK), &[full()]).unwrap();176
+ f.render_texture_from_to(177
+ &tex,178
+ Rectangle::from_size((1.0, 1.0).into()),179
+ full(),180
+ &[full()],181
+ &[],182
+ Transform::Normal,183
+ 0.5,184
+ )185
+ .unwrap();186
+ });187
+ let p = at(&s.pixels(), 32, 32);188
+ assert!(189
+ (127..=129).contains(&p[0]) && p[0] == p[1] && p[1] == p[2],190
+ "{p:?}"191
+ );192
+}193
+194
+#[test]195
+fn output_transform_moves_content() {196
+ let mut s = Scene::new();197
+ s.draw(Transform::_180, |f| {198
+ f.clear(rgba(BLACK), &[full()]).unwrap();199
+ let dst = Rectangle::new((0, 0).into(), (16, 16).into());200
+ f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(GREEN))201
+ .unwrap();202
+ });203
+ let px = s.pixels();204
+ assert_eq!(at(&px, 0, 0), BLACK);205
+ assert_eq!(at(&px, 63, 63), GREEN);206
+}207
+208
+#[test]209
+fn offscreen_images_can_be_sampled() {210
+ let mut s = Scene::new();211
+ let mut inner = s212
+ .renderer213
+ .create_buffer(Fourcc::Argb8888, Size::from((4, 4)))214
+ .unwrap();215
+ {216
+ let mut t = s.renderer.bind(&mut inner).unwrap();217
+ let mut f = s218
+ .renderer219
+ .render(&mut t, (4, 4).into(), Transform::Normal)220
+ .unwrap();221
+ f.clear(rgba(BLUE), &[Rectangle::from_size((4, 4).into())])222
+ .unwrap();223
+ f.finish().unwrap().wait().unwrap();224
+ }225
+ let tex = inner.texture();226
+ s.draw(Transform::Normal, |f| {227
+ f.render_texture_from_to(228
+ &tex,229
+ Rectangle::from_size((4.0, 4.0).into()),230
+ full(),231
+ &[full()],232
+ &[],233
+ Transform::Normal,234
+ 1.0,235
+ )236
+ .unwrap();237
+ });238
+ assert_eq!(at(&s.pixels(), 32, 32), BLUE);239
+}240
+241
+#[test]242
+fn reports_the_device() {243
+ let r = VulkanRenderer::new().unwrap();244
+ eprintln!("rendering on {}", r.device_name());245
+ assert!(!r.device_name().is_empty());246
+}247
+248
+#[test]249
+fn solid_fill_in_the_corner_keeps_its_colour() {250
+ let mut s = Scene::new();251
+ s.draw(Transform::Normal, |f| {252
+ f.clear(rgba(BLACK), &[full()]).unwrap();253
+ let dst = Rectangle::new((48, 48).into(), (16, 16).into());254
+ f.draw_solid(dst, &[Rectangle::from_size(dst.size)], rgba(RED))255
+ .unwrap();256
+ });257
+ assert_eq!(at(&s.pixels(), 56, 56), RED);258
+}@@ -22,14 +22,21 @@
2222
pkgs:2323
let2424
craneLib = crane.mkLib pkgs;25
- src = craneLib.cleanCargoSource ./.;25
+ src = lib.cleanSourceWith {26
+ src = ./.;27
+ filter = path: type: (craneLib.filterCargoSources path type) || lib.hasInfix "/shaders" path;28
+ name = "source";29
+ };2630
lavapipeIcd = "${pkgs.mesa}/share/vulkan/icd.d/lvp_icd.${pkgs.stdenv.hostPlatform.parsed.cpu.name}.json";2731
commonArgs = {2832
inherit src;2933
pname = "monolith";3034
version = "0.0.0";3135
strictDeps = true;32
- nativeBuildInputs = [ pkgs.pkg-config ];36
+ nativeBuildInputs = [37
+ pkgs.pkg-config38
+ pkgs.glslang39
+ ];3340
buildInputs = with pkgs; [3441
libxkbcommon3542
vulkan-loader@@ -62,6 +69,7 @@
6269
// {6370
inherit cargoArtifacts;6471
VK_DRIVER_FILES = lavapipeIcd;72
+ LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.vulkan-loader ];6573
}6674
);6775
};