834 lines
30 KiB
Rust
834 lines
30 KiB
Rust
use std::{
|
|
collections::{BTreeMap, HashMap},
|
|
mem::ManuallyDrop,
|
|
sync::Arc,
|
|
};
|
|
|
|
use ash::vk;
|
|
use gpu_allocator::MemoryLocation;
|
|
use indexmap::IndexMap;
|
|
|
|
use crate::{
|
|
EguiState,
|
|
buffers::{Buffer, BufferDesc},
|
|
commands::traits::CommandBufferExt,
|
|
device::{self, AllocationStrategy},
|
|
images::{Image, ImageDesc, ImageViewDesc},
|
|
render_graph::{
|
|
Access, Barrier, GraphResourceDesc, GraphResourceId, PassDesc, RecordFn, RenderContext,
|
|
RenderGraph, buffer_barrier, image_barrier,
|
|
},
|
|
texture::{self, TextureId},
|
|
};
|
|
|
|
pub fn egui_pre_pass(
|
|
dev: &device::Device,
|
|
rg: &mut RenderGraph,
|
|
textures: &mut crate::texture::TextureManager,
|
|
egui_state: &mut EguiState,
|
|
output: &egui::FullOutput,
|
|
) -> crate::Result<()> {
|
|
// allocate resource ids for textures in tessellated list (imported from texture manager)
|
|
// define accesses for resource ids
|
|
|
|
if output.textures_delta.set.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
use rectangle_pack::{
|
|
GroupedRectsToPlace, RectToInsert, TargetBin, contains_smallest_box, pack_rects,
|
|
volume_heuristic,
|
|
};
|
|
let mut rects_to_place = GroupedRectsToPlace::<TextureId, ()>::new();
|
|
|
|
let mut max_width = 0;
|
|
let mut max_height = 0;
|
|
|
|
// create textures for new egui textures
|
|
// these are real images that will be sampled from in the shader.
|
|
let updates = output
|
|
.textures_delta
|
|
.set
|
|
.iter()
|
|
.map(|(egui_id, delta)| {
|
|
max_width = max_width.max(delta.image.width() as u32);
|
|
max_height = max_height.max(delta.image.height() as u32);
|
|
|
|
use std::collections::hash_map::Entry;
|
|
let id = match egui_state.textures.entry(*egui_id) {
|
|
Entry::Occupied(entry) => entry.get().id,
|
|
Entry::Vacant(entry) => {
|
|
tracing::trace!("creating texture image for egui image {egui_id:?}");
|
|
|
|
let Ok(image) = Image::new(
|
|
dev.clone(),
|
|
ImageDesc {
|
|
name: Some(format!("egui-texture-{egui_id:?}").into()),
|
|
format: vk::Format::R8G8B8A8_UNORM,
|
|
extent: vk::Extent3D {
|
|
width: delta.image.width() as u32,
|
|
height: delta.image.height() as u32,
|
|
depth: 1,
|
|
},
|
|
usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
|
|
mem_location: MemoryLocation::GpuOnly,
|
|
alloc_scheme: AllocationStrategy::Dedicated,
|
|
..Default::default()
|
|
},
|
|
) else {
|
|
return Err(crate::Error::Todo(
|
|
"handle image creation failure in egui pre-pass",
|
|
));
|
|
};
|
|
|
|
let id = textures.insert_image(Arc::new(image));
|
|
entry.insert(crate::EguiTextureInfo {
|
|
id,
|
|
options: delta.options,
|
|
});
|
|
id
|
|
}
|
|
};
|
|
|
|
rects_to_place.push_rect(
|
|
id,
|
|
None,
|
|
RectToInsert::new(delta.image.width() as u32, delta.image.height() as u32, 1),
|
|
);
|
|
|
|
Ok((id, (delta.pos, &delta.image)))
|
|
})
|
|
.collect::<crate::Result<HashMap<_, _>>>()?;
|
|
|
|
let rect_placements = loop {
|
|
let mut target_bins = BTreeMap::new();
|
|
target_bins.insert(0, TargetBin::new(max_width, max_height, 1));
|
|
|
|
match pack_rects(
|
|
&rects_to_place,
|
|
&mut target_bins,
|
|
&volume_heuristic,
|
|
&contains_smallest_box,
|
|
) {
|
|
Ok(placements) => break placements,
|
|
Err(_) => {
|
|
max_width *= 2;
|
|
max_height *= 2;
|
|
}
|
|
}
|
|
};
|
|
|
|
let extent = rect_placements
|
|
.packed_locations()
|
|
.iter()
|
|
.fold((0u32, 0u32), |(w, h), (_id, (_bin, loc))| {
|
|
(w.max(loc.x() + loc.width()), h.max(loc.y() + loc.height()))
|
|
});
|
|
|
|
tracing::trace!(
|
|
"creating staging buffer {}x{} for uploading egui textures",
|
|
extent.0,
|
|
extent.1,
|
|
);
|
|
let mut staging_buffer = Buffer::new(
|
|
dev.clone(),
|
|
BufferDesc {
|
|
name: Some("egui-prepass-staging-buffer".into()),
|
|
size: (extent.0 * extent.1 * 4) as u64, // RGBA8
|
|
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
|
queue_families: device::QueueFlags::empty(),
|
|
mem_location: MemoryLocation::CpuToGpu,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
let staging_image = Image::new(
|
|
dev.clone(),
|
|
ImageDesc {
|
|
name: Some("egui-prepass-staging-buffer".into()),
|
|
format: vk::Format::R8G8B8A8_UNORM,
|
|
extent: vk::Extent3D {
|
|
width: extent.0,
|
|
height: extent.1,
|
|
depth: 1,
|
|
},
|
|
usage: vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST,
|
|
queue_families: device::QueueFlags::empty(),
|
|
mem_location: MemoryLocation::GpuOnly,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
let staging_map = staging_buffer.map_mut().unwrap();
|
|
|
|
struct ImageUpdate {
|
|
#[allow(dead_code)]
|
|
id: TextureId,
|
|
graph_id: GraphResourceId,
|
|
dest_offset: (u32, u32),
|
|
src_offset: (u32, u32),
|
|
size: (u32, u32),
|
|
}
|
|
|
|
let updates = updates
|
|
.into_iter()
|
|
.filter_map(|(id, (pos, image))| {
|
|
let (_, loc) = rect_placements.packed_locations().get(&id)?;
|
|
|
|
// copy scanlines from image data into staging buffer
|
|
let rect_width = loc.width() as usize;
|
|
let rect_height = loc.height() as usize;
|
|
let rect_x = loc.x() as usize;
|
|
let rect_y = loc.y() as usize;
|
|
|
|
let pixel_size = 4usize; // RGBA8
|
|
let atlas_width = extent.0 as usize;
|
|
|
|
let image_data = match image {
|
|
egui::ImageData::Color(color_image) => color_image.as_raw(),
|
|
};
|
|
|
|
for (atlas_y, image_y) in (rect_y..rect_y + rect_height).enumerate() {
|
|
let atlas_start = (image_y * atlas_width + rect_x) * pixel_size;
|
|
let atlas_end = atlas_start + rect_width * pixel_size;
|
|
|
|
let image_start = (atlas_y * rect_width) * pixel_size;
|
|
let image_end = image_start + rect_width * pixel_size;
|
|
staging_map[atlas_start..atlas_end]
|
|
.copy_from_slice(&image_data[image_start..image_end]);
|
|
}
|
|
|
|
let graph_id = rg.import_image(
|
|
textures.get_texture(id)?,
|
|
Access {
|
|
layout: vk::ImageLayout::GENERAL,
|
|
..Access::undefined()
|
|
},
|
|
);
|
|
|
|
Some(ImageUpdate {
|
|
id,
|
|
graph_id,
|
|
dest_offset: pos.map(|p| (p[0] as u32, p[1] as u32)).unwrap_or_default(),
|
|
src_offset: (rect_x as u32, rect_y as u32),
|
|
size: (rect_width as u32, rect_height as u32),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let writes = updates
|
|
.iter()
|
|
.map(|update| {
|
|
(
|
|
update.graph_id,
|
|
Access {
|
|
layout: vk::ImageLayout::GENERAL,
|
|
..Access::undefined()
|
|
},
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let staging_buffer = rg.import_buffer(Arc::new(staging_buffer), Access::undefined());
|
|
let staging_image = rg.import_image(Arc::new(staging_image), Access::undefined());
|
|
|
|
let record = Box::new({
|
|
// let textures = textures.clone();
|
|
move |ctx: &RenderContext| -> crate::Result<()> {
|
|
let staging_image = ctx.get_image(staging_image).unwrap().clone();
|
|
let staging_buffer = ctx.get_buffer(staging_buffer).unwrap();
|
|
|
|
let image: Barrier = image_barrier(
|
|
staging_image.raw(),
|
|
staging_image.format(),
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
},
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
},
|
|
None,
|
|
)
|
|
.into();
|
|
|
|
unsafe {
|
|
ctx.device
|
|
.dev()
|
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&image).into()));
|
|
}
|
|
|
|
ctx.cmd.copy_buffer_to_image(
|
|
staging_buffer.raw(),
|
|
staging_image.raw(),
|
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
&[vk::BufferImageCopy {
|
|
buffer_offset: 0,
|
|
buffer_row_length: staging_image.width(),
|
|
buffer_image_height: staging_image.height(),
|
|
image_subresource: vk::ImageSubresourceLayers::default()
|
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
.base_array_layer(0)
|
|
.mip_level(0)
|
|
.layer_count(1),
|
|
image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
|
|
image_extent: staging_image.size(),
|
|
}],
|
|
);
|
|
|
|
let from_barrier: Barrier = image_barrier(
|
|
staging_image.raw(),
|
|
staging_image.format(),
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
},
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_READ,
|
|
layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
},
|
|
None,
|
|
)
|
|
.into();
|
|
|
|
unsafe {
|
|
ctx.device
|
|
.dev()
|
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&from_barrier).into()));
|
|
}
|
|
|
|
for ImageUpdate {
|
|
graph_id,
|
|
dest_offset,
|
|
src_offset,
|
|
size,
|
|
..
|
|
} in &updates
|
|
{
|
|
let texture = ctx.get_image(*graph_id).unwrap();
|
|
|
|
let to_barrier: Barrier = image_barrier(
|
|
texture.raw(),
|
|
texture.format(),
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
// TODO: this is somewhat sub-optimal, but I think
|
|
// perfectly legal to not-care about the layout of a
|
|
// texture? In this case, the entire texture is
|
|
// overwritten, so the layout doesn't matter, and else
|
|
// the layout will be `GENERAL` because the texture was
|
|
// previously written to.
|
|
layout: if staging_image.size() == texture.size() {
|
|
vk::ImageLayout::UNDEFINED
|
|
} else {
|
|
vk::ImageLayout::GENERAL
|
|
},
|
|
},
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
},
|
|
None,
|
|
)
|
|
.into();
|
|
|
|
unsafe {
|
|
ctx.device
|
|
.dev()
|
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&to_barrier).into()));
|
|
}
|
|
|
|
ctx.cmd.copy_images(
|
|
staging_image.raw(),
|
|
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
texture.raw(),
|
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
&[vk::ImageCopy {
|
|
src_subresource: vk::ImageSubresourceLayers::default()
|
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
.base_array_layer(0)
|
|
.mip_level(0)
|
|
.layer_count(1),
|
|
src_offset: vk::Offset3D {
|
|
x: src_offset.0 as i32,
|
|
y: src_offset.1 as i32,
|
|
z: 0,
|
|
},
|
|
dst_subresource: vk::ImageSubresourceLayers::default()
|
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
.base_array_layer(0)
|
|
.mip_level(0)
|
|
.layer_count(1),
|
|
dst_offset: vk::Offset3D {
|
|
x: dest_offset.0 as i32,
|
|
y: dest_offset.1 as i32,
|
|
z: 0,
|
|
},
|
|
extent: vk::Extent3D {
|
|
width: size.0,
|
|
height: size.1,
|
|
depth: 1,
|
|
},
|
|
}],
|
|
);
|
|
|
|
let image: Barrier = image_barrier(
|
|
texture.raw(),
|
|
texture.format(),
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
},
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::ALL_COMMANDS,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::GENERAL,
|
|
},
|
|
None,
|
|
)
|
|
.into();
|
|
|
|
unsafe {
|
|
ctx.device
|
|
.dev()
|
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&image).into()));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
});
|
|
|
|
rg.add_pass(PassDesc {
|
|
reads: [
|
|
(
|
|
staging_buffer,
|
|
Access {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_READ,
|
|
..Access::undefined()
|
|
},
|
|
),
|
|
(staging_image, Access::undefined()),
|
|
]
|
|
.to_vec(),
|
|
writes,
|
|
record: Some(record),
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
// fn egui_pass()
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn egui_pass(
|
|
dev: &device::Device,
|
|
rg: &mut RenderGraph,
|
|
texture_handler: &mut crate::texture::TextureManager,
|
|
samplers: &mut crate::SamplerCache,
|
|
egui_state: &mut EguiState,
|
|
egui: &egui::Context,
|
|
output: egui::FullOutput,
|
|
target: GraphResourceId,
|
|
) -> crate::Result<Vec<texture::TextureId>> {
|
|
let draw_data = egui.tessellate(output.shapes, output.pixels_per_point);
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
|
struct Vertex {
|
|
pos: glam::Vec2,
|
|
uv: glam::Vec2,
|
|
color: egui::epaint::Color32,
|
|
}
|
|
|
|
#[repr(transparent)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct DrawCall(vk::DrawIndexedIndirectCommand);
|
|
unsafe impl bytemuck::Zeroable for DrawCall {}
|
|
unsafe impl bytemuck::Pod for DrawCall {}
|
|
|
|
let mut vertices = Vec::new();
|
|
let mut indices = Vec::new();
|
|
let mut draw_calls = Vec::new();
|
|
let mut textures = IndexMap::new();
|
|
let mut textures_indices = Vec::new();
|
|
for draw in draw_data {
|
|
let egui::epaint::Primitive::Mesh(mesh) = draw.primitive else {
|
|
continue;
|
|
};
|
|
|
|
draw_calls.push(DrawCall(vk::DrawIndexedIndirectCommand {
|
|
index_count: mesh.indices.len() as u32,
|
|
instance_count: 1,
|
|
first_index: indices.len() as u32,
|
|
vertex_offset: vertices.len() as i32,
|
|
first_instance: 0,
|
|
}));
|
|
|
|
vertices.extend(mesh.vertices.iter().map(|v| Vertex {
|
|
pos: glam::vec2(v.pos.x, v.pos.y),
|
|
uv: glam::vec2(v.uv.x, v.uv.y),
|
|
color: v.color,
|
|
}));
|
|
|
|
indices.extend(mesh.indices);
|
|
let texture = egui_state.textures.get(&mesh.texture_id).cloned().unwrap();
|
|
if !textures.contains_key(&texture.id) {
|
|
textures.insert(texture.id, texture);
|
|
}
|
|
let idx = textures.get_index_of(&texture.id).unwrap();
|
|
textures_indices.push(idx as u32);
|
|
}
|
|
|
|
const TEXTURE_DESCRIPTOR_CAPACITY: usize = 10;
|
|
if textures.len() > TEXTURE_DESCRIPTOR_CAPACITY {
|
|
return Err(crate::Error::Todo(
|
|
"egui frame references more textures than the descriptor array supports",
|
|
));
|
|
}
|
|
|
|
let num_draw_calls = draw_calls.len();
|
|
let vertices_size = vertices.len() * size_of::<Vertex>();
|
|
let indices_size = indices.len() * size_of::<u32>();
|
|
let draw_calls_size = draw_calls.len() * size_of::<vk::DrawIndexedIndirectCommand>();
|
|
|
|
let (draw_staging, vertices, indices, draw_calls, texture_ids) = {
|
|
let staging_size = vertices_size + indices_size + draw_calls_size;
|
|
|
|
let mut staging = Buffer::new(
|
|
dev.clone(),
|
|
BufferDesc {
|
|
name: Some("egui-draw-staging".into()),
|
|
size: staging_size as u64,
|
|
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
|
mem_location: MemoryLocation::CpuToGpu,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
{
|
|
let map = staging.map_mut().unwrap();
|
|
|
|
let (st_vertices, rest) = map.split_at_mut(vertices_size);
|
|
let (st_indices, rest) = rest.split_at_mut(indices_size);
|
|
let (st_drawcalls, _rest) = rest.split_at_mut(draw_calls_size);
|
|
st_vertices.copy_from_slice(bytemuck::cast_slice(&vertices));
|
|
st_indices.copy_from_slice(bytemuck::cast_slice(&indices));
|
|
st_drawcalls.copy_from_slice(bytemuck::cast_slice(&draw_calls));
|
|
}
|
|
let staging = rg.import_buffer(Arc::new(staging), Access::undefined());
|
|
|
|
let vertices = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
|
name: Some("egui-draw-vertices".into()),
|
|
size: vertices_size as u64,
|
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER,
|
|
mem_location: MemoryLocation::GpuOnly,
|
|
..Default::default()
|
|
}));
|
|
let indices = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
|
name: Some("egui-draw-indices".into()),
|
|
size: indices_size as u64,
|
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER,
|
|
mem_location: MemoryLocation::GpuOnly,
|
|
..Default::default()
|
|
}));
|
|
let draw_calls = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
|
name: Some("egui-draw-draw_calls".into()),
|
|
size: draw_calls_size as u64,
|
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDIRECT_BUFFER,
|
|
mem_location: MemoryLocation::GpuOnly,
|
|
..Default::default()
|
|
}));
|
|
|
|
let mut texture_ids = Buffer::new(
|
|
dev.clone(),
|
|
BufferDesc {
|
|
name: Some("egui-draw-texture_ids".into()),
|
|
size: (textures_indices.len() * size_of::<u32>()) as u64,
|
|
usage: vk::BufferUsageFlags::STORAGE_BUFFER,
|
|
mem_location: MemoryLocation::CpuToGpu,
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
{
|
|
let map = texture_ids
|
|
.map_mut()
|
|
.expect("texture id buffer should be device visible");
|
|
map.copy_from_slice(bytemuck::cast_slice(&textures_indices));
|
|
}
|
|
|
|
(staging, vertices, indices, draw_calls, texture_ids)
|
|
};
|
|
|
|
// This map contains only unique textures referenced by these meshes. Its insertion order
|
|
// is the exact index stored in `texture_ids`, matching `texture[index]` in egui.slang.
|
|
let descriptor_resources = textures
|
|
.values()
|
|
.map(|entry| {
|
|
let texture = texture_handler.get_texture(entry.id).unwrap();
|
|
let view = Arc::new(ManuallyDrop::into_inner(
|
|
texture
|
|
.create_view(
|
|
ImageViewDesc::color_2d()
|
|
.with_mip_range(0..1)
|
|
.with_layer_range(0..1),
|
|
)
|
|
.unwrap(),
|
|
));
|
|
let sampler = samplers.get_sampler_owned(entry.as_sampler_desc()).unwrap();
|
|
(view, sampler)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let texture_ids = Arc::new(texture_ids);
|
|
// Each pass gets its own managed set, so later passes/frames cannot mutate the contents
|
|
// captured by this callback.
|
|
let descriptor_pool = crate::pipeline::DescriptorPool::new(
|
|
dev.clone(),
|
|
crate::pipeline::DescriptorPoolDesc {
|
|
flags: vk::DescriptorPoolCreateFlags::UPDATE_AFTER_BIND,
|
|
name: Some("egui-pass-descriptor-pool".into()),
|
|
sizes: &[
|
|
vk::DescriptorPoolSize {
|
|
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
|
|
descriptor_count: TEXTURE_DESCRIPTOR_CAPACITY as u32,
|
|
},
|
|
vk::DescriptorPoolSize {
|
|
ty: vk::DescriptorType::STORAGE_BUFFER,
|
|
descriptor_count: 1,
|
|
},
|
|
],
|
|
max_sets: 1,
|
|
},
|
|
)?;
|
|
let mut descriptor_set = descriptor_pool
|
|
.allocate_managed(crate::pipeline::DescriptorSetAllocDesc {
|
|
name: None,
|
|
layout: &egui_state.descriptor_layout,
|
|
})
|
|
.map_err(|_| crate::Error::Todo("egui managed descriptor allocation failed"))?;
|
|
{
|
|
let mut writer = descriptor_set
|
|
.writer()
|
|
.map_err(|_| crate::Error::Todo("egui descriptor set is stale"))?;
|
|
writer
|
|
.clear_binding(EguiState::TEXTURE_BINDING)
|
|
.map_err(|_| crate::Error::Todo("egui descriptor binding clear failed"))?;
|
|
// Managed bindings are partially bound, but Vulkan descriptor contents do not have an
|
|
// unset operation. Populate every unused slot with a live descriptor to avoid stale
|
|
// shader-array entries while preserving the mesh texture indices above.
|
|
if let Some((first_view, first_sampler)) = descriptor_resources.first() {
|
|
for array_element in descriptor_resources.len()..TEXTURE_DESCRIPTOR_CAPACITY {
|
|
writer
|
|
.write_image(
|
|
EguiState::TEXTURE_BINDING,
|
|
array_element as u32,
|
|
&[crate::pipeline::ImageDescriptor {
|
|
view: first_view,
|
|
sampler: Some(first_sampler),
|
|
layout: vk::ImageLayout::GENERAL,
|
|
}],
|
|
)
|
|
.map_err(|_| crate::Error::Todo("egui descriptor image padding failed"))?;
|
|
}
|
|
}
|
|
for (array_element, (view, sampler)) in descriptor_resources.iter().enumerate() {
|
|
writer
|
|
.write_image(
|
|
EguiState::TEXTURE_BINDING,
|
|
array_element as u32,
|
|
&[crate::pipeline::ImageDescriptor {
|
|
view,
|
|
sampler: Some(sampler),
|
|
layout: vk::ImageLayout::GENERAL,
|
|
}],
|
|
)
|
|
.map_err(|_| crate::Error::Todo("egui descriptor image write failed"))?;
|
|
}
|
|
writer
|
|
.write_buffer(
|
|
EguiState::UNIFORM_BINDING,
|
|
0,
|
|
&[crate::pipeline::BufferDescriptor {
|
|
buffer: &texture_ids,
|
|
offset: 0,
|
|
range: texture_ids.len(),
|
|
}],
|
|
)
|
|
.map_err(|_| crate::Error::Todo("egui descriptor buffer write failed"))?;
|
|
writer
|
|
.commit()
|
|
.map_err(|_| crate::Error::Todo("egui descriptor commit failed"))?;
|
|
}
|
|
let descriptor_set_raw = descriptor_set
|
|
.raw()
|
|
.map_err(|_| crate::Error::Todo("egui descriptor set is stale"))?;
|
|
let descriptor_set_owner = Arc::new(descriptor_set);
|
|
|
|
let to_remove_tex_ids = output
|
|
.textures_delta
|
|
.free
|
|
.iter()
|
|
.filter_map(|id| egui_state.textures.get(id).cloned())
|
|
.map(|entry| entry.id)
|
|
.collect::<Vec<_>>();
|
|
|
|
let reads = textures
|
|
.keys()
|
|
.filter_map(|id| {
|
|
texture_handler
|
|
.get_texture(*id)
|
|
.map(|img| (rg.import_image(img, Access::general()), Access::general()))
|
|
})
|
|
.chain([(target, Access::color_attachment_read_write())])
|
|
.collect::<Vec<_>>();
|
|
let writes = [(target, Access::color_attachment_write_only())].to_vec();
|
|
|
|
let record: Box<RecordFn> = Box::new({
|
|
let pipeline = egui_state.pipeline.clone();
|
|
let descriptor_set_owner = descriptor_set_owner.clone();
|
|
let screen_rect = egui.screen_rect();
|
|
|
|
move |ctx: &RenderContext| -> crate::Result<()> {
|
|
// Keep this managed wrapper alive through recording, and validate its generation
|
|
// immediately before binding the raw handle captured for this frame.
|
|
let _descriptor_set = descriptor_set_owner
|
|
.raw()
|
|
.map_err(|_| crate::Error::Todo("egui descriptor set is stale"))?;
|
|
let cmd = &ctx.cmd;
|
|
let staging = ctx.get_buffer(draw_staging).unwrap();
|
|
let vertices = ctx.get_buffer(vertices).unwrap();
|
|
let indices = ctx.get_buffer(indices).unwrap();
|
|
let draw_calls = ctx.get_buffer(draw_calls).unwrap();
|
|
let target = ctx.get_image(target).unwrap();
|
|
|
|
cmd.copy_buffers(
|
|
staging.raw(),
|
|
vertices.raw(),
|
|
&[vk::BufferCopy {
|
|
src_offset: 0,
|
|
dst_offset: 0,
|
|
size: vertices_size as u64,
|
|
}],
|
|
);
|
|
cmd.copy_buffers(
|
|
staging.raw(),
|
|
indices.raw(),
|
|
&[vk::BufferCopy {
|
|
src_offset: vertices_size as u64,
|
|
dst_offset: 0,
|
|
size: indices_size as u64,
|
|
}],
|
|
);
|
|
cmd.copy_buffers(
|
|
staging.raw(),
|
|
draw_calls.raw(),
|
|
&[vk::BufferCopy {
|
|
src_offset: (vertices_size + indices_size) as u64,
|
|
dst_offset: 0,
|
|
size: draw_calls_size as u64,
|
|
}],
|
|
);
|
|
|
|
let barriers = [
|
|
buffer_barrier(
|
|
vertices.raw(),
|
|
0,
|
|
vertices.len(),
|
|
Access::transfer_write(),
|
|
Access::vertex_read(),
|
|
None,
|
|
),
|
|
buffer_barrier(
|
|
indices.raw(),
|
|
0,
|
|
indices.len(),
|
|
Access::transfer_write(),
|
|
Access::index_read(),
|
|
None,
|
|
),
|
|
buffer_barrier(
|
|
draw_calls.raw(),
|
|
0,
|
|
draw_calls.len(),
|
|
Access::transfer_write(),
|
|
Access::indirect_read(),
|
|
None,
|
|
),
|
|
];
|
|
unsafe {
|
|
ctx.device.dev().cmd_pipeline_barrier2(
|
|
cmd.buffer(),
|
|
&vk::DependencyInfo::default().buffer_memory_barriers(&barriers),
|
|
);
|
|
}
|
|
|
|
let color_attachment = &vk::RenderingAttachmentInfo::default()
|
|
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
|
.image_view(target.create_view(ImageViewDesc::color_2d())?.raw())
|
|
.load_op(vk::AttachmentLoadOp::LOAD)
|
|
.store_op(vk::AttachmentStoreOp::STORE);
|
|
|
|
cmd.begin_rendering(
|
|
vk::RenderingInfo::default()
|
|
.color_attachments(core::slice::from_ref(color_attachment))
|
|
.layer_count(1)
|
|
.render_area(vk::Rect2D::default().extent(target.extent_2d())),
|
|
);
|
|
|
|
cmd.set_scissors(&[vk::Rect2D::default()
|
|
.offset(vk::Offset2D::default())
|
|
.extent(target.extent_2d())]);
|
|
|
|
cmd.set_viewport(&[vk::Viewport::default()
|
|
.x(0.0)
|
|
.y(0.0)
|
|
.min_depth(0.0)
|
|
.max_depth(1.0)
|
|
.width(target.width() as f32)
|
|
.height(target.height() as f32)]);
|
|
|
|
cmd.bind_pipeline(&pipeline);
|
|
cmd.bind_indices(indices.raw(), 0, vk::IndexType::UINT32);
|
|
cmd.bind_vertex_buffers(&[vertices.raw()], &[0]);
|
|
cmd.push_constants(
|
|
pipeline.layout(),
|
|
vk::ShaderStageFlags::VERTEX,
|
|
0,
|
|
bytemuck::cast_slice(
|
|
&[screen_rect.width(), screen_rect.height()].map(|f| f.to_bits()),
|
|
),
|
|
);
|
|
cmd.bind_descriptor_sets(
|
|
pipeline.layout(),
|
|
vk::PipelineBindPoint::GRAPHICS,
|
|
&[descriptor_set_raw],
|
|
);
|
|
cmd.draw_indexed_indirect(
|
|
draw_calls.raw(),
|
|
0,
|
|
num_draw_calls as u32,
|
|
size_of::<vk::DrawIndexedIndirectCommand>() as u32,
|
|
);
|
|
|
|
cmd.end_rendering();
|
|
|
|
Ok(())
|
|
}
|
|
});
|
|
|
|
rg.add_pass(PassDesc {
|
|
reads,
|
|
writes,
|
|
record: Some(record),
|
|
});
|
|
|
|
Ok(to_remove_tex_ids)
|
|
}
|