vidya/crates/renderer/src/egui.rs
2026-10-26 02:11:29 +01:00

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