1
0
Fork 0
forked from wry/wry

vulkan: add support for blend buffers

This commit is contained in:
Julian Orth 2025-02-22 17:18:20 +01:00
parent 1220539a41
commit cb9da22ec2
20 changed files with 638 additions and 78 deletions

View file

@ -13,6 +13,8 @@ pub fn main() -> anyhow::Result<()> {
compile_simple("fill.vert")?; compile_simple("fill.vert")?;
compile_simple("tex.vert")?; compile_simple("tex.vert")?;
compile_simple("tex.frag")?; compile_simple("tex.frag")?;
compile_simple("out.vert")?;
compile_simple("out.frag")?;
Ok(()) Ok(())
} }

View file

@ -408,7 +408,7 @@ static XBGR16161616: &Format = &Format {
..default(ConfigFormat::XBGR16161616) ..default(ConfigFormat::XBGR16161616)
}; };
static ABGR16161616F: &Format = &Format { pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f", name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT, vk_format: vk::Format::R16G16B16A16_SFLOAT,
drm: fourcc_code('A', 'B', '4', 'H'), drm: fourcc_code('A', 'B', '4', 'H'),

View file

@ -267,7 +267,9 @@ pub enum ResetStatus {
Other(u32), Other(u32),
} }
pub trait GfxBlendBuffer: Debug {} pub trait GfxBlendBuffer: Debug {
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
}
pub trait GfxFramebuffer: Debug { pub trait GfxFramebuffer: Debug {
fn physical_size(&self) -> (i32, i32); fn physical_size(&self) -> (i32, i32);

View file

@ -1,4 +1,5 @@
mod allocator; mod allocator;
mod blend_buffer;
mod bo_allocator; mod bo_allocator;
mod command; mod command;
mod descriptor; mod descriptor;
@ -204,8 +205,8 @@ pub enum VulkanError {
UndefinedContents, UndefinedContents,
#[error("The framebuffer is being used by the transfer queue")] #[error("The framebuffer is being used by the transfer queue")]
BusyInTransfer, BusyInTransfer,
#[error("Vulkan does not support blend buffers")] #[error("Driver does not support descriptor buffers")]
NoBlendBuffer, NoDescriptorBuffer,
} }
impl From<VulkanError> for GfxError { impl From<VulkanError> for GfxError {
@ -272,6 +273,7 @@ impl GfxContext for Context {
let old = old.into_texture().into_vk(&self.0.device.device); let old = old.into_texture().into_vk(&self.0.device.device);
let shm = match &old.ty { let shm = match &old.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(), VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(shm) => shm, VulkanImageMemory::Internal(shm) => shm,
}; };
if old.width as i32 == width if old.width as i32 == width
@ -355,10 +357,11 @@ impl GfxContext for Context {
fn acquire_blend_buffer( fn acquire_blend_buffer(
&self, &self,
_width: i32, width: i32,
_height: i32, height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> { ) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> {
Err(GfxError(Box::new(VulkanError::NoBlendBuffer))) let buffer = self.0.acquire_blend_buffer(width, height)?;
Ok(buffer)
} }
} }

View file

@ -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<Self>,
width: i32,
height: i32,
) -> Result<Rc<VulkanImage>, 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<Self>) -> Rc<dyn Any> {
self
}
}

View file

@ -68,6 +68,33 @@ impl VulkanDevice {
})) }))
} }
pub(super) fn create_out_descriptor_set_layout(
self: &Rc<Self>,
db: &descriptor_buffer::Device,
) -> Result<Rc<VulkanDescriptorSetLayout>, 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( fn get_descriptor_set_size(
&self, &self,
db: &descriptor_buffer::Device, db: &descriptor_buffer::Device,

View file

@ -3,7 +3,7 @@ use {
format::XRGB8888, format::XRGB8888,
gfx_apis::vulkan::{ gfx_apis::vulkan::{
VulkanError, VulkanError,
format::VulkanFormat, format::{VulkanBlendBufferLimits, VulkanFormat},
instance::{ instance::{
API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance, API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance,
map_extension_properties, map_extension_properties,
@ -63,6 +63,7 @@ pub struct VulkanDevice {
pub(super) image_drm_format_modifier: image_drm_format_modifier::Device, pub(super) image_drm_format_modifier: image_drm_format_modifier::Device,
pub(super) descriptor_buffer: Option<descriptor_buffer::Device>, pub(super) descriptor_buffer: Option<descriptor_buffer::Device>,
pub(super) formats: AHashMap<u32, VulkanFormat>, pub(super) formats: AHashMap<u32, VulkanFormat>,
pub(super) blend_limits: VulkanBlendBufferLimits,
pub(super) memory_types: ArrayVec<MemoryType, MAX_MEMORY_TYPES>, pub(super) memory_types: ArrayVec<MemoryType, MAX_MEMORY_TYPES>,
pub(super) graphics_queue: Queue, pub(super) graphics_queue: Queue,
pub(super) graphics_queue_idx: u32, pub(super) graphics_queue_idx: u32,
@ -71,6 +72,7 @@ pub struct VulkanDevice {
pub(super) transfer_granularity_mask: (u32, u32), pub(super) transfer_granularity_mask: (u32, u32),
pub(super) descriptor_buffer_offset_mask: DeviceSize, pub(super) descriptor_buffer_offset_mask: DeviceSize,
pub(super) combined_image_sampler_descriptor_size: usize, pub(super) combined_image_sampler_descriptor_size: usize,
pub(super) sampled_image_descriptor_size: usize,
} }
impl Drop for VulkanDevice { impl Drop for VulkanDevice {
@ -337,6 +339,7 @@ impl VulkanInstance {
Err(e) => return Err(VulkanError::CreateDevice(e)), Err(e) => return Err(VulkanError::CreateDevice(e)),
}; };
let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); 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 formats = self.load_formats(phy_dev)?;
let supports_xrgb8888 = formats let supports_xrgb8888 = formats
.get(&XRGB8888.drm) .get(&XRGB8888.drm)
@ -364,6 +367,7 @@ impl VulkanInstance {
.then(|| descriptor_buffer::Device::new(&self.instance, &device)); .then(|| descriptor_buffer::Device::new(&self.instance, &device));
let mut descriptor_buffer_offset_mask = 0; let mut descriptor_buffer_offset_mask = 0;
let mut combined_image_sampler_descriptor_size = 0; let mut combined_image_sampler_descriptor_size = 0;
let mut sampled_image_descriptor_size = 0;
if supports_descriptor_buffer { if supports_descriptor_buffer {
let mut descriptor_buffer_props = let mut descriptor_buffer_props =
PhysicalDeviceDescriptorBufferPropertiesEXT::default(); PhysicalDeviceDescriptorBufferPropertiesEXT::default();
@ -380,6 +384,7 @@ impl VulkanInstance {
- 1; - 1;
combined_image_sampler_descriptor_size = combined_image_sampler_descriptor_size =
descriptor_buffer_props.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 = let memory_properties =
unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; unsafe { self.instance.get_physical_device_memory_properties(phy_dev) };
@ -418,6 +423,8 @@ impl VulkanInstance {
transfer_granularity_mask, transfer_granularity_mask,
descriptor_buffer_offset_mask, descriptor_buffer_offset_mask,
combined_image_sampler_descriptor_size, combined_image_sampler_descriptor_size,
sampled_image_descriptor_size,
blend_limits,
})) }))
} }
} }

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
format::{FORMATS, Format}, format::{ABGR16161616F, FORMATS, Format},
gfx_apis::vulkan::{VulkanError, instance::VulkanInstance}, gfx_apis::vulkan::{VulkanError, instance::VulkanInstance},
video::{LINEAR_MODIFIER, Modifier}, video::{LINEAR_MODIFIER, Modifier},
}, },
@ -38,18 +38,24 @@ pub struct VulkanModifier {
pub render_needs_bridge: bool, pub render_needs_bridge: bool,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Default)]
pub struct VulkanModifierLimits { pub struct VulkanModifierLimits {
pub max_width: u32, pub max_width: u32,
pub max_height: u32, pub max_height: u32,
pub exportable: bool, pub exportable: bool,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct VulkanInternalFormat { pub struct VulkanInternalFormat {
pub limits: VulkanModifierLimits, 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( const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw(
0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw()
| FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(),
@ -79,6 +85,16 @@ const TRANSFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(
const SHM_USAGE: ImageUsageFlags = const SHM_USAGE: ImageUsageFlags =
ImageUsageFlags::from_raw(TRANSFER_USAGE.as_raw() | TEX_USAGE.as_raw()); 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 { impl VulkanInstance {
pub(super) fn load_formats( pub(super) fn load_formats(
&self, &self,
@ -126,6 +142,29 @@ impl VulkanInstance {
Ok(()) Ok(())
} }
pub fn load_blend_format_limits(
&self,
phy_dev: PhysicalDevice,
) -> Result<VulkanBlendBufferLimits, VulkanError> {
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( fn load_shm_format(
&self, &self,
phy_dev: PhysicalDevice, phy_dev: PhysicalDevice,

View file

@ -64,6 +64,7 @@ pub struct VulkanImage {
pub(super) ty: VulkanImageMemory, pub(super) ty: VulkanImageMemory,
pub(super) bridge: Option<VulkanFramebufferBridge>, pub(super) bridge: Option<VulkanFramebufferBridge>,
pub(super) shader_read_only_optimal_descriptor: Box<[u8]>, pub(super) shader_read_only_optimal_descriptor: Box<[u8]>,
pub(super) sampled_image_descriptor: Box<[u8]>,
pub(super) descriptor_buffer_version: Cell<u64>, pub(super) descriptor_buffer_version: Cell<u64>,
pub(super) descriptor_buffer_offset: Cell<DeviceSize>, pub(super) descriptor_buffer_offset: Cell<DeviceSize>,
pub(super) execution_version: Cell<u64>, pub(super) execution_version: Cell<u64>,
@ -102,6 +103,7 @@ pub enum QueueTransfer {
pub enum VulkanImageMemory { pub enum VulkanImageMemory {
DmaBuf(VulkanDmaBufImage), DmaBuf(VulkanDmaBufImage),
Internal(VulkanShmImage), Internal(VulkanShmImage),
Blend(#[expect(dead_code)] VulkanAllocation),
} }
pub struct VulkanDmaBufImage { pub struct VulkanDmaBufImage {
@ -451,6 +453,7 @@ impl VulkanDmaBufImageTemplate {
shader_read_only_optimal_descriptor: self shader_read_only_optimal_descriptor: self
.renderer .renderer
.sampler_read_only_descriptor(texture_view), .sampler_read_only_descriptor(texture_view),
sampled_image_descriptor: Box::new([]),
descriptor_buffer_version: Cell::new(0), descriptor_buffer_version: Cell::new(0),
descriptor_buffer_offset: Cell::new(0), descriptor_buffer_offset: Cell::new(0),
execution_version: Cell::new(0), execution_version: Cell::new(0),
@ -539,10 +542,30 @@ impl GfxFramebuffer for VulkanImage {
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>, blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, 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 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()) .map_err(|e| e.into())
} }
@ -610,6 +633,7 @@ impl GfxTexture for VulkanImage {
match &self.ty { match &self.ty {
VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf), VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf),
VulkanImageMemory::Internal(_) => None, VulkanImageMemory::Internal(_) => None,
VulkanImageMemory::Blend(_) => None,
} }
} }

View file

@ -39,6 +39,7 @@ pub(super) struct PipelineCreateInfo {
pub(super) blend: bool, pub(super) blend: bool,
pub(super) src_has_alpha: bool, pub(super) src_has_alpha: bool,
pub(super) has_alpha_mult: bool, pub(super) has_alpha_mult: bool,
pub(super) with_linear_output: bool,
pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>, pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
} }
@ -76,8 +77,8 @@ impl VulkanDevice {
}; };
let destroy_layout = let destroy_layout =
OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) });
let mut frag_spec_data = ArrayVec::<_, 8>::new(); let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new();
let mut frag_spec_entries = ArrayVec::<_, 2>::new(); let mut frag_spec_entries = ArrayVec::<_, 3>::new();
let mut frag_spec_entry = |data: &[u8]| { let mut frag_spec_entry = |data: &[u8]| {
let entry = SpecializationMapEntry::default() let entry = SpecializationMapEntry::default()
.constant_id(frag_spec_entries.len() as _) .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.src_has_alpha as u32).to_ne_bytes());
frag_spec_entry(&(info.has_alpha_mult 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() let frag_spec = SpecializationInfo::default()
.map_entries(&frag_spec_entries) .map_entries(&frag_spec_entries)
.data(&frag_spec_data); .data(&frag_spec_data);

View file

@ -4,8 +4,8 @@ use {
cpu_worker::PendingJob, cpu_worker::PendingJob,
format::XRGB8888, format::XRGB8888,
gfx_api::{ gfx_api::{
AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer, AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat,
GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, GfxFramebuffer, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile,
}, },
gfx_apis::vulkan::{ gfx_apis::vulkan::{
VulkanError, VulkanError,
@ -17,13 +17,14 @@ use {
}, },
device::VulkanDevice, device::VulkanDevice,
fence::VulkanFence, fence::VulkanFence,
format::BLEND_FORMAT,
image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory},
pipeline::{PipelineCreateInfo, VulkanPipeline}, pipeline::{PipelineCreateInfo, VulkanPipeline},
sampler::VulkanSampler, sampler::VulkanSampler,
semaphore::VulkanSemaphore, semaphore::VulkanSemaphore,
shaders::{ shaders::{
FILL_FRAG, FILL_VERT, FillPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants,
VulkanShader, TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader,
}, },
}, },
io_uring::IoUring, io_uring::IoUring,
@ -55,9 +56,10 @@ use {
linearize::{Linearize, StaticMap, static_map}, linearize::{Linearize, StaticMap, static_map},
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::hash_map::Entry,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, ptr, mem, ptr,
rc::Rc, rc::{Rc, Weak},
slice, slice,
}, },
uapi::OwnedFd, uapi::OwnedFd,
@ -67,6 +69,8 @@ pub struct VulkanRenderer {
pub(super) formats: Rc<AHashMap<u32, GfxFormat>>, pub(super) formats: Rc<AHashMap<u32, GfxFormat>>,
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
pub(super) pipelines: CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>, pub(super) pipelines: CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>,
pub(super) pipelines_with_blend_buffer: CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>,
pub(super) out_pipelines: RefCell<AHashMap<vk::Format, Rc<VulkanPipeline>>>,
pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) gfx_command_buffers: CachedCommandBuffers,
pub(super) transfer_command_buffers: Option<CachedCommandBuffers>, pub(super) transfer_command_buffers: Option<CachedCommandBuffers>,
pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>, pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>,
@ -82,12 +86,17 @@ pub struct VulkanRenderer {
pub(super) fill_frag_shader: Rc<VulkanShader>, pub(super) fill_frag_shader: Rc<VulkanShader>,
pub(super) tex_vert_shader: Rc<VulkanShader>, pub(super) tex_vert_shader: Rc<VulkanShader>,
pub(super) tex_frag_shader: Rc<VulkanShader>, pub(super) tex_frag_shader: Rc<VulkanShader>,
pub(super) out_vert_shader: Rc<VulkanShader>,
pub(super) out_frag_shader: Rc<VulkanShader>,
pub(super) tex_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>, pub(super) tex_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
pub(super) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
pub(super) defunct: Cell<bool>, pub(super) defunct: Cell<bool>,
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>, pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>, pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
pub(super) sampler: Rc<VulkanSampler>, pub(super) sampler: Rc<VulkanSampler>,
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>, pub(super) sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) resource_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
} }
pub(super) struct CachedCommandBuffers { pub(super) struct CachedCommandBuffers {
@ -139,11 +148,13 @@ pub(super) struct Memory {
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>, wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
release_fence: Option<Rc<VulkanFence>>, release_fence: Option<Rc<VulkanFence>>,
release_sync_file: Option<SyncFile>, release_sync_file: Option<SyncFile>,
descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 1>, descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
paint_regions: Vec<PaintRegion>, paint_regions: Vec<PaintRegion>,
clear_rects: Vec<ClearRect>, clear_rects: Vec<ClearRect>,
image_copy_regions: Vec<ImageCopy2<'static>>, image_copy_regions: Vec<ImageCopy2<'static>>,
is_full_clear: bool,
sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter, sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
} }
struct PaintRegion { struct PaintRegion {
@ -159,11 +170,12 @@ pub(super) struct PendingFrame {
renderer: Rc<VulkanRenderer>, renderer: Rc<VulkanRenderer>,
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>, cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
_fb: Rc<VulkanImage>, _fb: Rc<VulkanImage>,
_bb: Option<Rc<VulkanImage>>,
_textures: Vec<UsedTexture>, _textures: Vec<UsedTexture>,
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>, wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>, waiter: Cell<Option<SpawnedFuture<()>>>,
_release_fence: Option<Rc<VulkanFence>>, _release_fence: Option<Rc<VulkanFence>>,
_descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 1>, _descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
} }
pub(super) struct VulkanFormatPipelines { pub(super) struct VulkanFormatPipelines {
@ -181,6 +193,13 @@ impl VulkanDevice {
let fill_frag_shader = self.create_shader(FILL_FRAG)?; let fill_frag_shader = self.create_shader(FILL_FRAG)?;
let sampler = self.create_sampler()?; let sampler = self.create_sampler()?;
let tex_descriptor_set_layout = self.create_tex_descriptor_set_layout(&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_vert_shader = self.create_shader(TEX_VERT)?;
let tex_frag_shader = self.create_shader(TEX_FRAG)?; let tex_frag_shader = self.create_shader(TEX_FRAG)?;
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; 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 shm_allocator = self.create_threaded_allocator()?;
let sampler_descriptor_buffer_cache = let sampler_descriptor_buffer_cache =
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true)); Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true));
let resource_descriptor_buffer_cache =
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false));
let render = Rc::new(VulkanRenderer { let render = Rc::new(VulkanRenderer {
formats: Rc::new(formats), formats: Rc::new(formats),
device: self.clone(), device: self.clone(),
pipelines: Default::default(), pipelines: Default::default(),
pipelines_with_blend_buffer: Default::default(),
out_pipelines: Default::default(),
gfx_command_buffers, gfx_command_buffers,
transfer_command_buffers, transfer_command_buffers,
wait_semaphores: Default::default(), wait_semaphores: Default::default(),
@ -242,14 +265,19 @@ impl VulkanDevice {
fill_frag_shader, fill_frag_shader,
tex_vert_shader, tex_vert_shader,
tex_frag_shader, tex_frag_shader,
out_vert_shader,
out_frag_shader,
tex_descriptor_set_layout, tex_descriptor_set_layout,
out_descriptor_set_layout,
defunct: Cell::new(false), defunct: Cell::new(false),
pending_cpu_jobs: Default::default(), pending_cpu_jobs: Default::default(),
shm_allocator, shm_allocator,
sampler, sampler,
sampler_descriptor_buffer_cache, 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) Ok(render)
} }
} }
@ -258,8 +286,14 @@ impl VulkanRenderer {
fn get_or_create_pipelines( fn get_or_create_pipelines(
&self, &self,
format: vk::Format, format: vk::Format,
bb: Option<&VulkanImage>,
) -> Result<Rc<VulkanFormatPipelines>, VulkanError> { ) -> Result<Rc<VulkanFormatPipelines>, 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); return Ok(pl);
} }
let fill = self let fill = self
@ -271,6 +305,7 @@ impl VulkanRenderer {
blend: true, blend: true,
src_has_alpha: true, src_has_alpha: true,
has_alpha_mult: false, has_alpha_mult: false,
with_linear_output,
frag_descriptor_set_layout: None, frag_descriptor_set_layout: None,
})?; })?;
let create_tex_pipeline = |src_has_alpha, has_alpha_mult| { let create_tex_pipeline = |src_has_alpha, has_alpha_mult| {
@ -282,6 +317,7 @@ impl VulkanRenderer {
blend: src_has_alpha || has_alpha_mult, blend: src_has_alpha || has_alpha_mult,
src_has_alpha, src_has_alpha,
has_alpha_mult, has_alpha_mult,
with_linear_output,
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), 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_alpha = create_tex_pipeline(true, false)?;
let tex_mult_opaque = create_tex_pipeline(false, true)?; let tex_mult_opaque = create_tex_pipeline(false, true)?;
let tex_mult_alpha = create_tex_pipeline(true, true)?; let tex_mult_alpha = create_tex_pipeline(true, true)?;
let pipelines = Rc::new(VulkanFormatPipelines { let format_pipelines = Rc::new(VulkanFormatPipelines {
fill, fill,
tex: static_map! { tex: static_map! {
TexCopyType::Identity => static_map! { TexCopyType::Identity => static_map! {
@ -302,8 +338,8 @@ impl VulkanRenderer {
}, },
}, },
}); });
self.pipelines.set(format, pipelines.clone()); pipelines.set(format, format_pipelines.clone());
Ok(pipelines) Ok(format_pipelines)
} }
pub(super) fn allocate_point(&self) -> u64 { pub(super) fn allocate_point(&self) -> u64 {
@ -314,6 +350,7 @@ impl VulkanRenderer {
&self, &self,
buf: CommandBuffer, buf: CommandBuffer,
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
bb: Option<&VulkanImage>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
let Some(db) = &self.device.descriptor_buffer else { let Some(db) = &self.device.descriptor_buffer else {
return Ok(()); return Ok(());
@ -324,6 +361,15 @@ impl VulkanRenderer {
memory.descriptor_buffers.clear(); memory.descriptor_buffers.clear();
let sampler_writer = &mut memory.sampler_descriptor_buffer_writer; let sampler_writer = &mut memory.sampler_descriptor_buffer_writer;
sampler_writer.clear(); 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 { for cmd in opts {
let GfxApiOpt::CopyTexture(c) = cmd else { let GfxApiOpt::CopyTexture(c) = cmd else {
continue; continue;
@ -341,8 +387,10 @@ impl VulkanRenderer {
); );
} }
let mut infos = ArrayVec::<_, 2>::new(); let mut infos = ArrayVec::<_, 2>::new();
#[expect(clippy::single_element_loop)] for (writer, cache) in [
for (writer, cache) in [(&sampler_writer, &self.sampler_descriptor_buffer_cache)] { (&sampler_writer, &self.sampler_descriptor_buffer_cache),
(&resource_writer, &self.resource_descriptor_buffer_cache),
] {
let buffer = cache.allocate(writer.len() as DeviceSize)?; let buffer = cache.allocate(writer.len() as DeviceSize)?;
buffer.buffer.allocation.upload(|ptr, _| unsafe { buffer.buffer.allocation.upload(|ptr, _| unsafe {
ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) 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"); zone!("initial_barriers");
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
let memory = &mut *memory; let memory = &mut *memory;
memory.image_barriers.clear(); 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; let mut need_fb_barrier = true;
if let VulkanImageMemory::Internal(..) = &fb.ty { if let VulkanImageMemory::Internal(..) = &fb.ty {
need_fb_barrier = fb.is_undefined.get() need_fb_barrier = fb.is_undefined.get()
@ -498,36 +566,59 @@ impl VulkanRenderer {
Ok(()) 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"); zone!("begin_rendering");
let memory = &mut *self.memory.borrow_mut(); let memory = &mut *self.memory.borrow_mut();
let clear_value = clear.map(|clear| ClearValue { let mut load_clear = None;
color: ClearColorValue { let mut manual_clear = None;
float32: clear.to_array_srgb(None), memory.is_full_clear = false;
}, if let Some(clear) = clear {
}); let clear_value = ClearValue {
let load_clear = memory.paint_regions.len() == 1 && { color: ClearColorValue {
let rect = &memory.paint_regions[0].rect; float32: if bb.is_some() {
rect.offset.x == 0 clear.to_array_linear(None)
&& rect.offset.y == 0 } else {
&& rect.extent.width == fb.width clear.to_array_srgb(None)
&& rect.extent.height == fb.height },
}; },
let (load_clear, manual_clear) = match load_clear { };
false => (None, clear_value), let use_load_clear = memory.paint_regions.len() == 1 && {
true => (clear_value, None), let rect = &memory.paint_regions[0].rect;
}; rect.offset.x == 0
let rendering_attachment_info = { && rect.offset.y == 0
let mut rai = RenderingAttachmentInfo::default() && rect.extent.width == fb.width
.image_view(fb.render_view.unwrap_or(fb.texture_view)) && rect.extent.height == fb.height
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) };
.load_op(AttachmentLoadOp::LOAD) if use_load_clear {
.store_op(AttachmentStoreOp::STORE); load_clear = Some(clear_value);
if let Some(clear) = load_clear { } else {
rai = rai.clear_value(clear).load_op(AttachmentLoadOp::CLEAR); 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() let rendering_info = RenderingInfo::default()
.render_area(Rect2D { .render_area(Rect2D {
offset: Default::default(), offset: Default::default(),
@ -598,10 +689,15 @@ impl VulkanRenderer {
buf: CommandBuffer, buf: CommandBuffer,
fb: &VulkanImage, fb: &VulkanImage,
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
bb: Option<&VulkanImage>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
zone!("record_draws"); zone!("record_draws");
let memory = &*self.memory.borrow(); 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 dev = &self.device.device;
let mut current_pipeline = None; let mut current_pipeline = None;
let mut bind = |pipeline: &VulkanPipeline| { let mut bind = |pipeline: &VulkanPipeline| {
@ -618,7 +714,11 @@ impl VulkanRenderer {
GfxApiOpt::FillRect(r) => { GfxApiOpt::FillRect(r) => {
let push = FillPushConstants { let push = FillPushConstants {
pos: r.rect.to_points(), 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 { for region in &memory.paint_regions {
let mut push = push; let mut push = push;
@ -715,6 +815,103 @@ impl VulkanRenderer {
Ok(()) 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::<OutPushConstants>(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) { fn end_rendering(&self, buf: CommandBuffer) {
zone!("end_rendering"); zone!("end_rendering");
unsafe { unsafe {
@ -997,7 +1194,10 @@ impl VulkanRenderer {
Ok(()) 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.is_undefined.set(false);
fb.contents_are_undefined.set(false); fb.contents_are_undefined.set(false);
fb.queue_state.set(QueueState::Acquired { fb.queue_state.set(QueueState::Acquired {
@ -1011,7 +1211,12 @@ impl VulkanRenderer {
} }
} }
fn create_pending_frame(self: &Rc<Self>, buf: Rc<VulkanCommandBuffer>, fb: &Rc<VulkanImage>) { fn create_pending_frame(
self: &Rc<Self>,
buf: Rc<VulkanCommandBuffer>,
fb: &Rc<VulkanImage>,
bb: Option<Rc<VulkanImage>>,
) {
zone!("create_pending_frame"); zone!("create_pending_frame");
let point = self.allocate_point(); let point = self.allocate_point();
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
@ -1020,6 +1225,7 @@ impl VulkanRenderer {
renderer: self.clone(), renderer: self.clone(),
cmd: Cell::new(Some(buf)), cmd: Cell::new(Some(buf)),
_fb: fb.clone(), _fb: fb.clone(),
_bb: bb,
_textures: mem::take(&mut memory.textures), _textures: mem::take(&mut memory.textures),
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
waiter: Cell::new(None), waiter: Cell::new(None),
@ -1047,9 +1253,18 @@ impl VulkanRenderer {
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
blend_buffer: Option<Rc<VulkanImage>>,
) -> Result<Option<SyncFile>, VulkanError> { ) -> Result<Option<SyncFile>, VulkanError> {
zone!("execute"); 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 sync_file = {
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
memory.textures.clear(); memory.textures.clear();
@ -1119,26 +1334,29 @@ impl VulkanRenderer {
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
blend_buffer: Option<Rc<VulkanImage>>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
let bb = blend_buffer.as_deref();
self.check_defunct()?; self.check_defunct()?;
self.create_paint_regions(fb, region); self.create_paint_regions(fb, region);
let buf = self.gfx_command_buffers.allocate()?; let buf = self.gfx_command_buffers.allocate()?;
self.collect_memory(opts); self.collect_memory(opts);
self.begin_command_buffer(buf.buffer)?; self.begin_command_buffer(buf.buffer)?;
self.create_descriptor_buffers(buf.buffer, opts)?; self.create_descriptor_buffers(buf.buffer, opts, bb)?;
self.initial_barriers(buf.buffer, fb)?; self.initial_barriers(buf.buffer, fb, bb)?;
self.begin_rendering(buf.buffer, fb, clear); self.begin_rendering(buf.buffer, fb, clear, bb);
self.set_viewport(buf.buffer, fb); 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.end_rendering(buf.buffer);
self.copy_blend_buffer(buf.buffer, fb, bb)?;
self.copy_bridge_to_dmabuf(buf.buffer, fb); self.copy_bridge_to_dmabuf(buf.buffer, fb);
self.final_barriers(buf.buffer, fb); self.final_barriers(buf.buffer, fb);
self.end_command_buffer(buf.buffer)?; self.end_command_buffer(buf.buffer)?;
self.create_wait_semaphores(fb, &fb_acquire_sync)?; self.create_wait_semaphores(fb, &fb_acquire_sync)?;
self.submit(buf.buffer)?; self.submit(buf.buffer)?;
self.import_release_semaphore(fb, fb_release_sync); self.import_release_semaphore(fb, fb_release_sync);
self.store_layouts(fb); self.store_layouts(fb, bb);
self.create_pending_frame(buf, fb); self.create_pending_frame(buf, fb, blend_buffer);
Ok(()) Ok(())
} }
@ -1210,6 +1428,17 @@ impl dyn GfxTexture {
} }
} }
impl dyn GfxBlendBuffer {
pub(super) fn into_vk(self: Rc<Self>, device: &Device) -> Rc<VulkanImage> {
let img: Rc<VulkanImage> = self
.into_any()
.downcast()
.expect("Non-vulkan blend buffer passed into vulkan");
img.assert_device(device);
img
}
}
pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> { pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> {
ImageMemoryBarrier2::default().subresource_range( ImageMemoryBarrier2::default().subresource_range(
ImageSubresourceRange::default() ImageSubresourceRange::default()

View file

@ -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 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_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 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 struct VulkanShader {
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
@ -34,6 +36,14 @@ pub struct TexPushConstants {
unsafe impl Packed for 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 { impl VulkanDevice {
pub(super) fn create_shader( pub(super) fn create_shader(
self: &Rc<Self>, self: &Rc<Self>,

View file

@ -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 = 0) const bool src_has_alpha = false;
layout(constant_id = 1) const bool has_alpha_multiplier = false; layout(constant_id = 1) const bool has_alpha_multiplier = false;
layout(constant_id = 2) const bool color_management = false;
#endif

View file

@ -0,0 +1,3 @@
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
} data;

View file

@ -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;
}

View file

@ -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);
}

View file

@ -1,6 +1,7 @@
#version 450 #version 450
#include "frag_spec_const.glsl" #include "frag_spec_const.glsl"
#include "transfer_functions.glsl"
#include "tex.common.glsl" #include "tex.common.glsl"
layout(set = 0, binding = 0) uniform sampler2D tex; 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; layout(location = 0) out vec4 out_color;
void main() { 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 (has_alpha_multiplier) {
if (src_has_alpha) { if (src_has_alpha) {
out_color = textureLod(tex, tex_pos, 0) * data.mul; c *= data.mul;
} else { } 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;
} }

View file

@ -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

View file

@ -460,12 +460,14 @@ impl VulkanRenderer {
ty: VulkanImageMemory::Internal(shm), ty: VulkanImageMemory::Internal(shm),
bridge: None, bridge: None,
shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view), shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view),
sampled_image_descriptor: Box::new([]),
descriptor_buffer_version: Cell::new(0), descriptor_buffer_version: Cell::new(0),
descriptor_buffer_offset: Cell::new(0), descriptor_buffer_offset: Cell::new(0),
execution_version: Cell::new(0), execution_version: Cell::new(0),
}); });
let shm = match &img.ty { let shm = match &img.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(), VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(s) => s, VulkanImageMemory::Internal(s) => s,
}; };
if data.is_not_empty() { if data.is_not_empty() {

View file

@ -120,20 +120,21 @@ impl Color {
[self.r * a, self.g * a, self.b * a, self.a * a] [self.r * a, self.g * a, self.b * a, self.a * a]
} }
#[expect(dead_code)] pub fn to_array_linear(self, alpha: Option<f32>) -> [f32; 4] {
pub fn to_array_linear(self) -> [f32; 4] {
fn to_linear(srgb: f32) -> f32 { fn to_linear(srgb: f32) -> f32 {
if srgb <= 0.04045 { if srgb <= 0.04045 {
srgb / 12.92 srgb / 12.92
} else { } 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.r / a1) * a2,
to_linear(self.g), to_linear(self.g / a1) * a2,
to_linear(self.b), to_linear(self.b / a1) * a2,
self.a, a2,
] ]
} }