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("tex.vert")?;
compile_simple("tex.frag")?;
compile_simple("out.vert")?;
compile_simple("out.frag")?;
Ok(())
}

View file

@ -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'),

View file

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

View file

@ -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<VulkanError> 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<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(
&self,
db: &descriptor_buffer::Device,

View file

@ -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<descriptor_buffer::Device>,
pub(super) formats: AHashMap<u32, VulkanFormat>,
pub(super) blend_limits: VulkanBlendBufferLimits,
pub(super) memory_types: ArrayVec<MemoryType, MAX_MEMORY_TYPES>,
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,
}))
}
}

View file

@ -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<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(
&self,
phy_dev: PhysicalDevice,

View file

@ -64,6 +64,7 @@ pub struct VulkanImage {
pub(super) ty: VulkanImageMemory,
pub(super) bridge: Option<VulkanFramebufferBridge>,
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_offset: Cell<DeviceSize>,
pub(super) execution_version: Cell<u64>,
@ -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<dyn GfxBlendBuffer>>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> 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
.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,
}
}

View file

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

View file

@ -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<AHashMap<u32, GfxFormat>>,
pub(super) device: Rc<VulkanDevice>,
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) transfer_command_buffers: Option<CachedCommandBuffers>,
pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>,
@ -82,12 +86,17 @@ pub struct VulkanRenderer {
pub(super) fill_frag_shader: Rc<VulkanShader>,
pub(super) tex_vert_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) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
pub(super) defunct: Cell<bool>,
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
pub(super) sampler: Rc<VulkanSampler>,
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 {
@ -139,11 +148,13 @@ pub(super) struct Memory {
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
release_fence: Option<Rc<VulkanFence>>,
release_sync_file: Option<SyncFile>,
descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 1>,
descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
paint_regions: Vec<PaintRegion>,
clear_rects: Vec<ClearRect>,
image_copy_regions: Vec<ImageCopy2<'static>>,
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<VulkanRenderer>,
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
_fb: Rc<VulkanImage>,
_bb: Option<Rc<VulkanImage>>,
_textures: Vec<UsedTexture>,
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>,
_release_fence: Option<Rc<VulkanFence>>,
_descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 1>,
_descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
}
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<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);
}
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::<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) {
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<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");
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<Rc<VulkanImage>>,
) -> Result<Option<SyncFile>, 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<Rc<VulkanImage>>,
) -> 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<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> {
ImageMemoryBarrier2::default().subresource_range(
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 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<VulkanDevice>,
@ -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<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 = 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
#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;
}

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),
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() {

View file

@ -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>) -> [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,
]
}