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

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