From cb9da22ec2e7a665a7b60bfeda311decba5dfc35 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 22 Feb 2025 17:18:20 +0100 Subject: [PATCH] vulkan: add support for blend buffers --- build/vulkan.rs | 2 + src/format.rs | 2 +- src/gfx_api.rs | 4 +- src/gfx_apis/vulkan.rs | 13 +- src/gfx_apis/vulkan/blend_buffer.rs | 139 ++++++++ src/gfx_apis/vulkan/descriptor.rs | 27 ++ src/gfx_apis/vulkan/device.rs | 9 +- src/gfx_apis/vulkan/format.rs | 45 ++- src/gfx_apis/vulkan/image.rs | 28 +- src/gfx_apis/vulkan/pipeline.rs | 6 +- src/gfx_apis/vulkan/renderer.rs | 333 +++++++++++++++--- src/gfx_apis/vulkan/shaders.rs | 10 + .../vulkan/shaders/frag_spec_const.glsl | 6 + src/gfx_apis/vulkan/shaders/out.common.glsl | 3 + src/gfx_apis/vulkan/shaders/out.frag | 17 + src/gfx_apis/vulkan/shaders/out.vert | 16 + src/gfx_apis/vulkan/shaders/tex.frag | 18 +- .../vulkan/shaders/transfer_functions.glsl | 21 ++ src/gfx_apis/vulkan/shm_image.rs | 2 + src/theme.rs | 15 +- 20 files changed, 638 insertions(+), 78 deletions(-) create mode 100644 src/gfx_apis/vulkan/blend_buffer.rs create mode 100644 src/gfx_apis/vulkan/shaders/out.common.glsl create mode 100644 src/gfx_apis/vulkan/shaders/out.frag create mode 100644 src/gfx_apis/vulkan/shaders/out.vert create mode 100644 src/gfx_apis/vulkan/shaders/transfer_functions.glsl diff --git a/build/vulkan.rs b/build/vulkan.rs index 52c306bb..fb649b57 100644 --- a/build/vulkan.rs +++ b/build/vulkan.rs @@ -13,6 +13,8 @@ pub fn main() -> anyhow::Result<()> { compile_simple("fill.vert")?; compile_simple("tex.vert")?; compile_simple("tex.frag")?; + compile_simple("out.vert")?; + compile_simple("out.frag")?; Ok(()) } diff --git a/src/format.rs b/src/format.rs index 03dec08d..b4f7c3dc 100644 --- a/src/format.rs +++ b/src/format.rs @@ -408,7 +408,7 @@ static XBGR16161616: &Format = &Format { ..default(ConfigFormat::XBGR16161616) }; -static ABGR16161616F: &Format = &Format { +pub static ABGR16161616F: &Format = &Format { name: "abgr16161616f", vk_format: vk::Format::R16G16B16A16_SFLOAT, drm: fourcc_code('A', 'B', '4', 'H'), diff --git a/src/gfx_api.rs b/src/gfx_api.rs index ce69fa93..8c490aae 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -267,7 +267,9 @@ pub enum ResetStatus { Other(u32), } -pub trait GfxBlendBuffer: Debug {} +pub trait GfxBlendBuffer: Debug { + fn into_any(self: Rc) -> Rc; +} pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 6f6c5988..24b13165 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -1,4 +1,5 @@ mod allocator; +mod blend_buffer; mod bo_allocator; mod command; mod descriptor; @@ -204,8 +205,8 @@ pub enum VulkanError { UndefinedContents, #[error("The framebuffer is being used by the transfer queue")] BusyInTransfer, - #[error("Vulkan does not support blend buffers")] - NoBlendBuffer, + #[error("Driver does not support descriptor buffers")] + NoDescriptorBuffer, } impl From for GfxError { @@ -272,6 +273,7 @@ impl GfxContext for Context { let old = old.into_texture().into_vk(&self.0.device.device); let shm = match &old.ty { VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Blend(_) => unreachable!(), VulkanImageMemory::Internal(shm) => shm, }; if old.width as i32 == width @@ -355,10 +357,11 @@ impl GfxContext for Context { fn acquire_blend_buffer( &self, - _width: i32, - _height: i32, + width: i32, + height: i32, ) -> Result, GfxError> { - Err(GfxError(Box::new(VulkanError::NoBlendBuffer))) + let buffer = self.0.acquire_blend_buffer(width, height)?; + Ok(buffer) } } diff --git a/src/gfx_apis/vulkan/blend_buffer.rs b/src/gfx_apis/vulkan/blend_buffer.rs new file mode 100644 index 00000000..298c6527 --- /dev/null +++ b/src/gfx_apis/vulkan/blend_buffer.rs @@ -0,0 +1,139 @@ +use { + crate::{ + gfx_api::GfxBlendBuffer, + gfx_apis::vulkan::{ + VulkanError, + format::{BLEND_FORMAT, BLEND_USAGE}, + image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory}, + renderer::VulkanRenderer, + }, + utils::on_drop::OnDrop, + }, + ash::vk::{ + DescriptorDataEXT, DescriptorGetInfoEXT, DescriptorImageInfo, DescriptorType, Extent3D, + ImageAspectFlags, ImageCreateInfo, ImageLayout, ImageSubresourceRange, ImageTiling, + ImageType, ImageViewCreateInfo, ImageViewType, SampleCountFlags, SharingMode, + }, + gpu_alloc::UsageFlags, + std::{any::Any, cell::Cell, collections::hash_map::Entry, rc::Rc}, +}; + +impl VulkanRenderer { + pub fn acquire_blend_buffer( + self: &Rc, + width: i32, + height: i32, + ) -> Result, VulkanError> { + let Some(db) = &self.device.descriptor_buffer else { + return Err(VulkanError::NoDescriptorBuffer); + }; + if width <= 0 || height <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = width as u32; + let height = height as u32; + let cached = &mut *self.blend_buffers.borrow_mut(); + let cached = cached.entry((width, height)); + if let Entry::Occupied(entry) = &cached { + if let Some(buffer) = entry.get().upgrade() { + return Ok(buffer); + } + } + let limits = self.device.blend_limits; + if width > limits.max_width || height > limits.max_height { + return Err(VulkanError::ImageTooLarge); + } + let create_info = ImageCreateInfo::default() + .image_type(ImageType::TYPE_2D) + .format(BLEND_FORMAT.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::OPTIMAL) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .usage(BLEND_USAGE); + let image = unsafe { self.device.device.create_image(&create_info, None) }; + let image = image.map_err(VulkanError::CreateImage)?; + let destroy_image = OnDrop(|| unsafe { self.device.device.destroy_image(image, None) }); + let memory_requirements = + unsafe { self.device.device.get_image_memory_requirements(image) }; + let allocation = + self.allocator + .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; + let res = unsafe { + self.device + .device + .bind_image_memory(image, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindImageMemory)?; + let image_view_create_info = ImageViewCreateInfo::default() + .image(image) + .format(BLEND_FORMAT.vk_format) + .view_type(ImageViewType::TYPE_2D) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { + self.device + .device + .create_image_view(&image_view_create_info, None) + }; + let view = view.map_err(VulkanError::CreateImageView)?; + destroy_image.forget(); + let sampled_image_descriptor = { + let mut buf = vec![0; self.device.sampled_image_descriptor_size].into_boxed_slice(); + let image_info = DescriptorImageInfo::default() + .image_view(view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let info = DescriptorGetInfoEXT::default() + .ty(DescriptorType::SAMPLED_IMAGE) + .data(DescriptorDataEXT { + p_sampled_image: &image_info, + }); + unsafe { + db.get_descriptor(&info, &mut buf); + } + buf + }; + let img = Rc::new(VulkanImage { + renderer: self.clone(), + format: BLEND_FORMAT, + width, + height, + stride: 0, + texture_view: view, + render_view: None, + image, + is_undefined: Cell::new(true), + contents_are_undefined: Cell::new(true), + queue_state: Cell::new(QueueState::Acquired { + family: QueueFamily::Gfx, + }), + ty: VulkanImageMemory::Blend(allocation), + bridge: None, + shader_read_only_optimal_descriptor: Box::new([]), + sampled_image_descriptor, + descriptor_buffer_version: Default::default(), + descriptor_buffer_offset: Default::default(), + execution_version: Default::default(), + }); + cached.insert_entry(Rc::downgrade(&img)); + Ok(img) + } +} + +impl GfxBlendBuffer for VulkanImage { + fn into_any(self: Rc) -> Rc { + self + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 1d4ae94f..8ccb725a 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -68,6 +68,33 @@ impl VulkanDevice { })) } + pub(super) fn create_out_descriptor_set_layout( + self: &Rc, + db: &descriptor_buffer::Device, + ) -> Result, VulkanError> { + let binding = DescriptorSetLayoutBinding::default() + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::SAMPLED_IMAGE); + let create_info = DescriptorSetLayoutCreateInfo::default() + .bindings(slice::from_ref(&binding)) + .flags(DescriptorSetLayoutCreateFlags::DESCRIPTOR_BUFFER_EXT); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + let size = self.get_descriptor_set_size(db, layout); + let mut offsets = ArrayVec::new(); + unsafe { + offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); + } + Ok(Rc::new(VulkanDescriptorSetLayout { + device: self.clone(), + layout, + size, + offsets, + _sampler: None, + })) + } + fn get_descriptor_set_size( &self, db: &descriptor_buffer::Device, diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index bb1eb45b..248995f9 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -3,7 +3,7 @@ use { format::XRGB8888, gfx_apis::vulkan::{ VulkanError, - format::VulkanFormat, + format::{VulkanBlendBufferLimits, VulkanFormat}, instance::{ API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance, map_extension_properties, @@ -63,6 +63,7 @@ pub struct VulkanDevice { pub(super) image_drm_format_modifier: image_drm_format_modifier::Device, pub(super) descriptor_buffer: Option, pub(super) formats: AHashMap, + pub(super) blend_limits: VulkanBlendBufferLimits, pub(super) memory_types: ArrayVec, pub(super) graphics_queue: Queue, pub(super) graphics_queue_idx: u32, @@ -71,6 +72,7 @@ pub struct VulkanDevice { pub(super) transfer_granularity_mask: (u32, u32), pub(super) descriptor_buffer_offset_mask: DeviceSize, pub(super) combined_image_sampler_descriptor_size: usize, + pub(super) sampled_image_descriptor_size: usize, } impl Drop for VulkanDevice { @@ -337,6 +339,7 @@ impl VulkanInstance { Err(e) => return Err(VulkanError::CreateDevice(e)), }; let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); + let blend_limits = self.load_blend_format_limits(phy_dev)?; let formats = self.load_formats(phy_dev)?; let supports_xrgb8888 = formats .get(&XRGB8888.drm) @@ -364,6 +367,7 @@ impl VulkanInstance { .then(|| descriptor_buffer::Device::new(&self.instance, &device)); let mut descriptor_buffer_offset_mask = 0; let mut combined_image_sampler_descriptor_size = 0; + let mut sampled_image_descriptor_size = 0; if supports_descriptor_buffer { let mut descriptor_buffer_props = PhysicalDeviceDescriptorBufferPropertiesEXT::default(); @@ -380,6 +384,7 @@ impl VulkanInstance { - 1; combined_image_sampler_descriptor_size = descriptor_buffer_props.combined_image_sampler_descriptor_size; + sampled_image_descriptor_size = descriptor_buffer_props.sampled_image_descriptor_size; } let memory_properties = unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; @@ -418,6 +423,8 @@ impl VulkanInstance { transfer_granularity_mask, descriptor_buffer_offset_mask, combined_image_sampler_descriptor_size, + sampled_image_descriptor_size, + blend_limits, })) } } diff --git a/src/gfx_apis/vulkan/format.rs b/src/gfx_apis/vulkan/format.rs index 73fca716..d8395254 100644 --- a/src/gfx_apis/vulkan/format.rs +++ b/src/gfx_apis/vulkan/format.rs @@ -1,6 +1,6 @@ use { crate::{ - format::{FORMATS, Format}, + format::{ABGR16161616F, FORMATS, Format}, gfx_apis::vulkan::{VulkanError, instance::VulkanInstance}, video::{LINEAR_MODIFIER, Modifier}, }, @@ -38,18 +38,24 @@ pub struct VulkanModifier { pub render_needs_bridge: bool, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct VulkanModifierLimits { pub max_width: u32, pub max_height: u32, pub exportable: bool, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct VulkanInternalFormat { pub limits: VulkanModifierLimits, } +#[derive(Copy, Clone, Debug)] +pub struct VulkanBlendBufferLimits { + pub max_width: u32, + pub max_height: u32, +} + const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), @@ -79,6 +85,16 @@ const TRANSFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( const SHM_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(TRANSFER_USAGE.as_raw() | TEX_USAGE.as_raw()); +pub const BLEND_FORMAT: &Format = ABGR16161616F; +const BLEND_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw() + | FormatFeatureFlags::SAMPLED_IMAGE.as_raw(), +); +pub const BLEND_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + ImageUsageFlags::COLOR_ATTACHMENT.as_raw() | ImageUsageFlags::SAMPLED.as_raw(), +); + impl VulkanInstance { pub(super) fn load_formats( &self, @@ -126,6 +142,29 @@ impl VulkanInstance { Ok(()) } + pub fn load_blend_format_limits( + &self, + phy_dev: PhysicalDevice, + ) -> Result { + let format_properties = unsafe { + self.instance + .get_physical_device_format_properties(phy_dev, BLEND_FORMAT.vk_format) + }; + let l = self + .load_internal_format( + phy_dev, + BLEND_FORMAT, + &format_properties, + BLEND_FEATURES, + BLEND_USAGE, + )? + .unwrap_or_default(); + Ok(VulkanBlendBufferLimits { + max_width: l.limits.max_width, + max_height: l.limits.max_height, + }) + } + fn load_shm_format( &self, phy_dev: PhysicalDevice, diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 969a008f..ec82f78a 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -64,6 +64,7 @@ pub struct VulkanImage { pub(super) ty: VulkanImageMemory, pub(super) bridge: Option, pub(super) shader_read_only_optimal_descriptor: Box<[u8]>, + pub(super) sampled_image_descriptor: Box<[u8]>, pub(super) descriptor_buffer_version: Cell, pub(super) descriptor_buffer_offset: Cell, pub(super) execution_version: Cell, @@ -102,6 +103,7 @@ pub enum QueueTransfer { pub enum VulkanImageMemory { DmaBuf(VulkanDmaBufImage), Internal(VulkanShmImage), + Blend(#[expect(dead_code)] VulkanAllocation), } pub struct VulkanDmaBufImage { @@ -451,6 +453,7 @@ impl VulkanDmaBufImageTemplate { shader_read_only_optimal_descriptor: self .renderer .sampler_read_only_descriptor(texture_view), + sampled_image_descriptor: Box::new([]), descriptor_buffer_version: Cell::new(0), descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), @@ -539,10 +542,30 @@ impl GfxFramebuffer for VulkanImage { ops: &[GfxApiOpt], clear: Option<&Color>, region: &Region, - _blend_buffer: Option<&Rc>, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { + let mut blend_buffer = + blend_buffer.map(|b| b.clone().into_vk(&self.renderer.device.device)); + if let Some(bb) = &blend_buffer { + if bb.size() != self.size() { + log::error!( + "Blend buffer has invalid size: {:?} != {:?}", + bb.size(), + self.size() + ); + blend_buffer = None; + } + } self.renderer - .execute(&self, acquire_sync, release_sync, ops, clear, region) + .execute( + &self, + acquire_sync, + release_sync, + ops, + clear, + region, + blend_buffer, + ) .map_err(|e| e.into()) } @@ -610,6 +633,7 @@ impl GfxTexture for VulkanImage { match &self.ty { VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf), VulkanImageMemory::Internal(_) => None, + VulkanImageMemory::Blend(_) => None, } } diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index 44e42dbd..f4a4e4f9 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -39,6 +39,7 @@ pub(super) struct PipelineCreateInfo { pub(super) blend: bool, pub(super) src_has_alpha: bool, pub(super) has_alpha_mult: bool, + pub(super) with_linear_output: bool, pub(super) frag_descriptor_set_layout: Option>, } @@ -76,8 +77,8 @@ impl VulkanDevice { }; let destroy_layout = OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); - let mut frag_spec_data = ArrayVec::<_, 8>::new(); - let mut frag_spec_entries = ArrayVec::<_, 2>::new(); + let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new(); + let mut frag_spec_entries = ArrayVec::<_, 3>::new(); let mut frag_spec_entry = |data: &[u8]| { let entry = SpecializationMapEntry::default() .constant_id(frag_spec_entries.len() as _) @@ -88,6 +89,7 @@ impl VulkanDevice { }; frag_spec_entry(&(info.src_has_alpha as u32).to_ne_bytes()); frag_spec_entry(&(info.has_alpha_mult as u32).to_ne_bytes()); + frag_spec_entry(&(info.with_linear_output as u32).to_ne_bytes()); let frag_spec = SpecializationInfo::default() .map_entries(&frag_spec_entries) .data(&frag_spec_data); diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 543b479a..d5cf958c 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -4,8 +4,8 @@ use { cpu_worker::PendingJob, format::XRGB8888, gfx_api::{ - AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, - GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, + AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat, + GfxFramebuffer, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, }, gfx_apis::vulkan::{ VulkanError, @@ -17,13 +17,14 @@ use { }, device::VulkanDevice, fence::VulkanFence, + format::BLEND_FORMAT, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, pipeline::{PipelineCreateInfo, VulkanPipeline}, sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ - FILL_FRAG, FILL_VERT, FillPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, - VulkanShader, + FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, + TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader, }, }, io_uring::IoUring, @@ -55,9 +56,10 @@ use { linearize::{Linearize, StaticMap, static_map}, std::{ cell::{Cell, RefCell}, + collections::hash_map::Entry, fmt::{Debug, Formatter}, mem, ptr, - rc::Rc, + rc::{Rc, Weak}, slice, }, uapi::OwnedFd, @@ -67,6 +69,8 @@ pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, pub(super) pipelines: CopyHashMap>, + pub(super) pipelines_with_blend_buffer: CopyHashMap>, + pub(super) out_pipelines: RefCell>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, pub(super) wait_semaphores: Stack>, @@ -82,12 +86,17 @@ pub struct VulkanRenderer { pub(super) fill_frag_shader: Rc, pub(super) tex_vert_shader: Rc, pub(super) tex_frag_shader: Rc, + pub(super) out_vert_shader: Rc, + pub(super) out_frag_shader: Rc, pub(super) tex_descriptor_set_layout: Rc, + pub(super) out_descriptor_set_layout: Option>, pub(super) defunct: Cell, pub(super) pending_cpu_jobs: CopyHashMap, pub(super) shm_allocator: Rc, pub(super) sampler: Rc, pub(super) sampler_descriptor_buffer_cache: Rc, + pub(super) resource_descriptor_buffer_cache: Rc, + pub(super) blend_buffers: RefCell>>, } pub(super) struct CachedCommandBuffers { @@ -139,11 +148,13 @@ pub(super) struct Memory { wait_semaphore_infos: Vec>, release_fence: Option>, release_sync_file: Option, - descriptor_buffers: ArrayVec, + descriptor_buffers: ArrayVec, paint_regions: Vec, clear_rects: Vec, image_copy_regions: Vec>, + is_full_clear: bool, sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter, + resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter, } struct PaintRegion { @@ -159,11 +170,12 @@ pub(super) struct PendingFrame { renderer: Rc, cmd: Cell>>, _fb: Rc, + _bb: Option>, _textures: Vec, wait_semaphores: Cell>>, waiter: Cell>>, _release_fence: Option>, - _descriptor_buffers: ArrayVec, + _descriptor_buffers: ArrayVec, } pub(super) struct VulkanFormatPipelines { @@ -181,6 +193,13 @@ impl VulkanDevice { let fill_frag_shader = self.create_shader(FILL_FRAG)?; let sampler = self.create_sampler()?; let tex_descriptor_set_layout = self.create_tex_descriptor_set_layout(&sampler)?; + let out_descriptor_set_layout = self + .descriptor_buffer + .as_ref() + .map(|db| self.create_out_descriptor_set_layout(db)) + .transpose()?; + let out_vert_shader = self.create_shader(OUT_VERT)?; + let out_frag_shader = self.create_shader(OUT_FRAG)?; let tex_vert_shader = self.create_shader(TEX_VERT)?; let tex_frag_shader = self.create_shader(TEX_FRAG)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; @@ -223,10 +242,14 @@ impl VulkanDevice { let shm_allocator = self.create_threaded_allocator()?; let sampler_descriptor_buffer_cache = Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true)); + let resource_descriptor_buffer_cache = + Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false)); let render = Rc::new(VulkanRenderer { formats: Rc::new(formats), device: self.clone(), pipelines: Default::default(), + pipelines_with_blend_buffer: Default::default(), + out_pipelines: Default::default(), gfx_command_buffers, transfer_command_buffers, wait_semaphores: Default::default(), @@ -242,14 +265,19 @@ impl VulkanDevice { fill_frag_shader, tex_vert_shader, tex_frag_shader, + out_vert_shader, + out_frag_shader, tex_descriptor_set_layout, + out_descriptor_set_layout, defunct: Cell::new(false), pending_cpu_jobs: Default::default(), shm_allocator, sampler, sampler_descriptor_buffer_cache, + resource_descriptor_buffer_cache, + blend_buffers: Default::default(), }); - render.get_or_create_pipelines(XRGB8888.vk_format)?; + render.get_or_create_pipelines(XRGB8888.vk_format, None)?; Ok(render) } } @@ -258,8 +286,14 @@ impl VulkanRenderer { fn get_or_create_pipelines( &self, format: vk::Format, + bb: Option<&VulkanImage>, ) -> Result, VulkanError> { - if let Some(pl) = self.pipelines.get(&format) { + let with_linear_output = bb.is_some(); + let pipelines = match with_linear_output { + false => &self.pipelines, + true => &self.pipelines_with_blend_buffer, + }; + if let Some(pl) = pipelines.get(&format) { return Ok(pl); } let fill = self @@ -271,6 +305,7 @@ impl VulkanRenderer { blend: true, src_has_alpha: true, has_alpha_mult: false, + with_linear_output, frag_descriptor_set_layout: None, })?; let create_tex_pipeline = |src_has_alpha, has_alpha_mult| { @@ -282,6 +317,7 @@ impl VulkanRenderer { blend: src_has_alpha || has_alpha_mult, src_has_alpha, has_alpha_mult, + with_linear_output, frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), }) }; @@ -289,7 +325,7 @@ impl VulkanRenderer { let tex_alpha = create_tex_pipeline(true, false)?; let tex_mult_opaque = create_tex_pipeline(false, true)?; let tex_mult_alpha = create_tex_pipeline(true, true)?; - let pipelines = Rc::new(VulkanFormatPipelines { + let format_pipelines = Rc::new(VulkanFormatPipelines { fill, tex: static_map! { TexCopyType::Identity => static_map! { @@ -302,8 +338,8 @@ impl VulkanRenderer { }, }, }); - self.pipelines.set(format, pipelines.clone()); - Ok(pipelines) + pipelines.set(format, format_pipelines.clone()); + Ok(format_pipelines) } pub(super) fn allocate_point(&self) -> u64 { @@ -314,6 +350,7 @@ impl VulkanRenderer { &self, buf: CommandBuffer, opts: &[GfxApiOpt], + bb: Option<&VulkanImage>, ) -> Result<(), VulkanError> { let Some(db) = &self.device.descriptor_buffer else { return Ok(()); @@ -324,6 +361,15 @@ impl VulkanRenderer { memory.descriptor_buffers.clear(); let sampler_writer = &mut memory.sampler_descriptor_buffer_writer; sampler_writer.clear(); + let resource_writer = &mut memory.resource_descriptor_buffer_writer; + resource_writer.clear(); + if let Some(bb) = bb { + let layout = self.out_descriptor_set_layout.as_ref().unwrap(); + let offset = resource_writer.next_offset(); + bb.descriptor_buffer_offset.set(offset); + let mut writer = resource_writer.add_set(layout); + writer.write(layout.offsets[0], &bb.sampled_image_descriptor); + } for cmd in opts { let GfxApiOpt::CopyTexture(c) = cmd else { continue; @@ -341,8 +387,10 @@ impl VulkanRenderer { ); } let mut infos = ArrayVec::<_, 2>::new(); - #[expect(clippy::single_element_loop)] - for (writer, cache) in [(&sampler_writer, &self.sampler_descriptor_buffer_cache)] { + for (writer, cache) in [ + (&sampler_writer, &self.sampler_descriptor_buffer_cache), + (&resource_writer, &self.resource_descriptor_buffer_cache), + ] { let buffer = cache.allocate(writer.len() as DeviceSize)?; buffer.buffer.allocation.upload(|ptr, _| unsafe { ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) @@ -404,11 +452,31 @@ impl VulkanRenderer { } } - fn initial_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) -> Result<(), VulkanError> { + fn initial_barriers( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + bb: Option<&VulkanImage>, + ) -> Result<(), VulkanError> { zone!("initial_barriers"); let mut memory = self.memory.borrow_mut(); let memory = &mut *memory; memory.image_barriers.clear(); + if let Some(bb) = bb { + let barrier = image_barrier() + .image(bb.image) + .old_layout(if bb.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::SHADER_READ_ONLY_OPTIMAL + }) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .src_access_mask(AccessFlags2::SHADER_READ) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE); + memory.image_barriers.push(barrier); + } let mut need_fb_barrier = true; if let VulkanImageMemory::Internal(..) = &fb.ty { need_fb_barrier = fb.is_undefined.get() @@ -498,36 +566,59 @@ impl VulkanRenderer { Ok(()) } - fn begin_rendering(&self, buf: CommandBuffer, fb: &VulkanImage, clear: Option<&Color>) { + fn begin_rendering( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + clear: Option<&Color>, + bb: Option<&VulkanImage>, + ) { zone!("begin_rendering"); let memory = &mut *self.memory.borrow_mut(); - let clear_value = clear.map(|clear| ClearValue { - color: ClearColorValue { - float32: clear.to_array_srgb(None), - }, - }); - let load_clear = memory.paint_regions.len() == 1 && { - let rect = &memory.paint_regions[0].rect; - rect.offset.x == 0 - && rect.offset.y == 0 - && rect.extent.width == fb.width - && rect.extent.height == fb.height - }; - let (load_clear, manual_clear) = match load_clear { - false => (None, clear_value), - true => (clear_value, None), - }; - let rendering_attachment_info = { - let mut rai = RenderingAttachmentInfo::default() - .image_view(fb.render_view.unwrap_or(fb.texture_view)) - .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .load_op(AttachmentLoadOp::LOAD) - .store_op(AttachmentStoreOp::STORE); - if let Some(clear) = load_clear { - rai = rai.clear_value(clear).load_op(AttachmentLoadOp::CLEAR); + let mut load_clear = None; + let mut manual_clear = None; + memory.is_full_clear = false; + if let Some(clear) = clear { + let clear_value = ClearValue { + color: ClearColorValue { + float32: if bb.is_some() { + clear.to_array_linear(None) + } else { + clear.to_array_srgb(None) + }, + }, + }; + let use_load_clear = memory.paint_regions.len() == 1 && { + let rect = &memory.paint_regions[0].rect; + rect.offset.x == 0 + && rect.offset.y == 0 + && rect.extent.width == fb.width + && rect.extent.height == fb.height + }; + if use_load_clear { + load_clear = Some(clear_value); + } else { + manual_clear = Some(clear_value); } - rai + memory.is_full_clear = use_load_clear; + } + let mut rendering_attachment_info = RenderingAttachmentInfo::default() + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .store_op(AttachmentStoreOp::STORE); + rendering_attachment_info = if let Some(bb) = bb { + rendering_attachment_info + .image_view(bb.render_view.unwrap_or(bb.texture_view)) + .load_op(AttachmentLoadOp::DONT_CARE) + } else { + rendering_attachment_info + .image_view(fb.render_view.unwrap_or(fb.texture_view)) + .load_op(AttachmentLoadOp::LOAD) }; + if let Some(clear) = load_clear { + rendering_attachment_info = rendering_attachment_info + .clear_value(clear) + .load_op(AttachmentLoadOp::CLEAR); + } let rendering_info = RenderingInfo::default() .render_area(Rect2D { offset: Default::default(), @@ -598,10 +689,15 @@ impl VulkanRenderer { buf: CommandBuffer, fb: &VulkanImage, opts: &[GfxApiOpt], + bb: Option<&VulkanImage>, ) -> Result<(), VulkanError> { zone!("record_draws"); let memory = &*self.memory.borrow(); - let pipelines = self.get_or_create_pipelines(fb.format.vk_format)?; + let format = match bb.is_some() { + false => fb.format.vk_format, + true => BLEND_FORMAT.vk_format, + }; + let pipelines = self.get_or_create_pipelines(format, bb)?; let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -618,7 +714,11 @@ impl VulkanRenderer { GfxApiOpt::FillRect(r) => { let push = FillPushConstants { pos: r.rect.to_points(), - color: r.color.to_array_srgb(r.alpha), + color: if bb.is_some() { + r.color.to_array_linear(r.alpha) + } else { + r.color.to_array_srgb(r.alpha) + }, }; for region in &memory.paint_regions { let mut push = push; @@ -715,6 +815,103 @@ impl VulkanRenderer { Ok(()) } + fn copy_blend_buffer( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + bb: Option<&VulkanImage>, + ) -> Result<(), VulkanError> { + let Some(bb) = bb else { + return Ok(()); + }; + zone!("copy_blend_buffer"); + let memory = &*self.memory.borrow(); + let dev = &self.device.device; + let db = self.device.descriptor_buffer.as_ref().unwrap(); + let pipeline = match self.out_pipelines.borrow_mut().entry(fb.format.vk_format) { + Entry::Occupied(pipeline) => pipeline.get().clone(), + Entry::Vacant(e) => { + let layout = self.out_descriptor_set_layout.as_ref().unwrap(); + let out = self + .device + .create_pipeline::(PipelineCreateInfo { + format: fb.format.vk_format, + vert: self.out_vert_shader.clone(), + frag: self.out_frag_shader.clone(), + blend: false, + src_has_alpha: true, + has_alpha_mult: false, + with_linear_output: true, + frag_descriptor_set_layout: Some(layout.clone()), + })?; + e.insert(out.clone()); + out + } + }; + let image_barrier = image_barrier() + .image(bb.image) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER); + let dependency_info = + DependencyInfoKHR::default().image_memory_barriers(slice::from_ref(&image_barrier)); + let rendering_attachment_info = RenderingAttachmentInfo::default() + .image_view(fb.render_view.unwrap_or(fb.texture_view)) + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(if memory.is_full_clear { + AttachmentLoadOp::DONT_CARE + } else { + AttachmentLoadOp::LOAD + }) + .store_op(AttachmentStoreOp::STORE); + let rendering_info = RenderingInfo::default() + .render_area(Rect2D { + offset: Default::default(), + extent: Extent2D { + width: fb.width, + height: fb.height, + }, + }) + .layer_count(1) + .color_attachments(slice::from_ref(&rendering_attachment_info)); + unsafe { + dev.cmd_pipeline_barrier2(buf, &dependency_info); + dev.cmd_begin_rendering(buf, &rendering_info); + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + db.cmd_set_descriptor_buffer_offsets( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &[1], + &[bb.descriptor_buffer_offset.get()], + ); + for region in &memory.paint_regions { + let push = OutPushConstants { + pos: [ + [region.x2, region.y1], + [region.x1, region.y1], + [region.x2, region.y2], + [region.x1, region.y2], + ], + }; + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + dev.cmd_end_rendering(buf); + } + Ok(()) + } + fn end_rendering(&self, buf: CommandBuffer) { zone!("end_rendering"); unsafe { @@ -997,7 +1194,10 @@ impl VulkanRenderer { Ok(()) } - fn store_layouts(&self, fb: &VulkanImage) { + fn store_layouts(&self, fb: &VulkanImage, bb: Option<&VulkanImage>) { + if let Some(bb) = bb { + bb.is_undefined.set(false); + } fb.is_undefined.set(false); fb.contents_are_undefined.set(false); fb.queue_state.set(QueueState::Acquired { @@ -1011,7 +1211,12 @@ impl VulkanRenderer { } } - fn create_pending_frame(self: &Rc, buf: Rc, fb: &Rc) { + fn create_pending_frame( + self: &Rc, + buf: Rc, + fb: &Rc, + bb: Option>, + ) { zone!("create_pending_frame"); let point = self.allocate_point(); let mut memory = self.memory.borrow_mut(); @@ -1020,6 +1225,7 @@ impl VulkanRenderer { renderer: self.clone(), cmd: Cell::new(Some(buf)), _fb: fb.clone(), + _bb: bb, _textures: mem::take(&mut memory.textures), wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), waiter: Cell::new(None), @@ -1047,9 +1253,18 @@ impl VulkanRenderer { opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + blend_buffer: Option>, ) -> Result, VulkanError> { zone!("execute"); - let res = self.try_execute(fb, fb_acquire_sync, fb_release_sync, opts, clear, region); + let res = self.try_execute( + fb, + fb_acquire_sync, + fb_release_sync, + opts, + clear, + region, + blend_buffer, + ); let sync_file = { let mut memory = self.memory.borrow_mut(); memory.textures.clear(); @@ -1119,26 +1334,29 @@ impl VulkanRenderer { opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + blend_buffer: Option>, ) -> Result<(), VulkanError> { + let bb = blend_buffer.as_deref(); self.check_defunct()?; self.create_paint_regions(fb, region); let buf = self.gfx_command_buffers.allocate()?; self.collect_memory(opts); self.begin_command_buffer(buf.buffer)?; - self.create_descriptor_buffers(buf.buffer, opts)?; - self.initial_barriers(buf.buffer, fb)?; - self.begin_rendering(buf.buffer, fb, clear); + self.create_descriptor_buffers(buf.buffer, opts, bb)?; + self.initial_barriers(buf.buffer, fb, bb)?; + self.begin_rendering(buf.buffer, fb, clear, bb); self.set_viewport(buf.buffer, fb); - self.record_draws(buf.buffer, fb, opts)?; + self.record_draws(buf.buffer, fb, opts, bb)?; self.end_rendering(buf.buffer); + self.copy_blend_buffer(buf.buffer, fb, bb)?; self.copy_bridge_to_dmabuf(buf.buffer, fb); self.final_barriers(buf.buffer, fb); self.end_command_buffer(buf.buffer)?; self.create_wait_semaphores(fb, &fb_acquire_sync)?; self.submit(buf.buffer)?; self.import_release_semaphore(fb, fb_release_sync); - self.store_layouts(fb); - self.create_pending_frame(buf, fb); + self.store_layouts(fb, bb); + self.create_pending_frame(buf, fb, blend_buffer); Ok(()) } @@ -1210,6 +1428,17 @@ impl dyn GfxTexture { } } +impl dyn GfxBlendBuffer { + pub(super) fn into_vk(self: Rc, device: &Device) -> Rc { + let img: Rc = self + .into_any() + .downcast() + .expect("Non-vulkan blend buffer passed into vulkan"); + img.assert_device(device); + img + } +} + pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> { ImageMemoryBarrier2::default().subresource_range( ImageSubresourceRange::default() diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index a17117fb..4e019e37 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -9,6 +9,8 @@ pub const FILL_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.vert pub const FILL_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.frag.spv")); pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.spv")); pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv")); +pub const OUT_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.vert.spv")); +pub const OUT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.frag.spv")); pub struct VulkanShader { pub(super) device: Rc, @@ -34,6 +36,14 @@ pub struct TexPushConstants { unsafe impl Packed for TexPushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct OutPushConstants { + pub pos: [[f32; 2]; 4], +} + +unsafe impl Packed for OutPushConstants {} + impl VulkanDevice { pub(super) fn create_shader( self: &Rc, diff --git a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl index b2be1f76..751b18bf 100644 --- a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl +++ b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl @@ -1,2 +1,8 @@ +#ifndef FRAG_SPEC_CONST_GLSL +#define FRAG_SPEC_CONST_GLSL + layout(constant_id = 0) const bool src_has_alpha = false; layout(constant_id = 1) const bool has_alpha_multiplier = false; +layout(constant_id = 2) const bool color_management = false; + +#endif diff --git a/src/gfx_apis/vulkan/shaders/out.common.glsl b/src/gfx_apis/vulkan/shaders/out.common.glsl new file mode 100644 index 00000000..23a3579d --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.common.glsl @@ -0,0 +1,3 @@ +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; +} data; diff --git a/src/gfx_apis/vulkan/shaders/out.frag b/src/gfx_apis/vulkan/shaders/out.frag new file mode 100644 index 00000000..8401e5be --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.frag @@ -0,0 +1,17 @@ +#version 450 +#extension GL_EXT_samplerless_texture_functions : require + +#include "frag_spec_const.glsl" +#include "transfer_functions.glsl" +#include "out.common.glsl" + +layout(set = 0, binding = 0) uniform texture2D in_color; +layout(location = 0) out vec4 out_color; + +void main() { + vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0); + c.rgb /= mix(c.a, 1.0, c.a == 0.0); + c.rgb = oetf_srgb(c.rgb); + c.rgb *= c.a; + out_color = c; +} diff --git a/src/gfx_apis/vulkan/shaders/out.vert b/src/gfx_apis/vulkan/shaders/out.vert new file mode 100644 index 00000000..4bad8d2f --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.vert @@ -0,0 +1,16 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +#include "out.common.glsl" + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; break; + case 1: pos = data.pos[1]; break; + case 2: pos = data.pos[2]; break; + case 3: pos = data.pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("X gl_Position = %v4f, pos = %v2f", gl_Position, pos); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag index 2d5a29ad..9d9476fd 100644 --- a/src/gfx_apis/vulkan/shaders/tex.frag +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -1,6 +1,7 @@ #version 450 #include "frag_spec_const.glsl" +#include "transfer_functions.glsl" #include "tex.common.glsl" layout(set = 0, binding = 0) uniform sampler2D tex; @@ -8,13 +9,22 @@ layout(location = 0) in vec2 tex_pos; layout(location = 0) out vec4 out_color; void main() { + vec4 c = textureLod(tex, tex_pos, 0); + if (color_management) { + if (src_has_alpha) { + c.rgb /= mix(c.a, 1.0, c.a == 0.0); + } + c.rgb = eotf_srgb(c.rgb); + if (src_has_alpha) { + c.rgb *= c.a; + } + } if (has_alpha_multiplier) { if (src_has_alpha) { - out_color = textureLod(tex, tex_pos, 0) * data.mul; + c *= data.mul; } else { - out_color = vec4(textureLod(tex, tex_pos, 0).rgb * data.mul, data.mul); + c = vec4(c.rgb * data.mul, data.mul); } - } else { - out_color = textureLod(tex, tex_pos, 0); } + out_color = c; } diff --git a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl new file mode 100644 index 00000000..d72332a0 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl @@ -0,0 +1,21 @@ +#ifndef TRANSFER_FUNCTIONS_GLSL +#define TRANSFER_FUNCTIONS_GLSL + +vec3 eotf_srgb(vec3 c) { + return mix( + c * vec3(1.0 / 12.92), + pow((c + vec3(0.055)) / vec3(1.055), vec3(2.4)), + greaterThan(c, vec3(0.04045)) + ); +} + +vec3 oetf_srgb(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + c * vec3(12.92), + vec3(1.055) * pow(c, vec3(1/2.4)) - vec3(0.055), + greaterThan(c, vec3(0.0031308)) + ); +} + +#endif diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index b405a9a0..987c9dcb 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -460,12 +460,14 @@ impl VulkanRenderer { ty: VulkanImageMemory::Internal(shm), bridge: None, shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view), + sampled_image_descriptor: Box::new([]), descriptor_buffer_version: Cell::new(0), descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), }); let shm = match &img.ty { VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Blend(_) => unreachable!(), VulkanImageMemory::Internal(s) => s, }; if data.is_not_empty() { diff --git a/src/theme.rs b/src/theme.rs index d1fb8d87..16dd235c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -120,20 +120,21 @@ impl Color { [self.r * a, self.g * a, self.b * a, self.a * a] } - #[expect(dead_code)] - pub fn to_array_linear(self) -> [f32; 4] { + pub fn to_array_linear(self, alpha: Option) -> [f32; 4] { fn to_linear(srgb: f32) -> f32 { if srgb <= 0.04045 { srgb / 12.92 } else { - (srgb + 0.055 / 1.055).powf(2.4) + ((srgb + 0.055) / 1.055).powf(2.4) } } + let a1 = if self.a == 0.0 { 1.0 } else { self.a }; + let a2 = self.a * alpha.unwrap_or(1.0); [ - to_linear(self.r), - to_linear(self.g), - to_linear(self.b), - self.a, + to_linear(self.r / a1) * a2, + to_linear(self.g / a1) * a2, + to_linear(self.b / a1) * a2, + a2, ] }