egui_vulkano/
lib.rs

1//! [egui](https://docs.rs/egui) rendering backend for [Vulkano](https://docs.rs/vulkano).
2#![warn(missing_docs)]
3use std::collections::HashMap;
4use std::default::Default;
5use std::sync::Arc;
6
7use bytemuck::{Pod, Zeroable};
8use egui::epaint::{
9    textures::TexturesDelta, ClippedPrimitive, ClippedShape, ImageData, ImageDelta, Primitive,
10};
11use egui::{Color32, Context, Rect, TextureId};
12use vulkano::buffer::{BufferAccess, BufferSlice, BufferUsage, CpuAccessibleBuffer};
13use vulkano::command_buffer::SubpassContents::Inline;
14use vulkano::command_buffer::{
15    AutoCommandBufferBuilder, AutoCommandBufferBuilderContextError, CopyBufferImageError,
16    DrawIndexedError, PrimaryAutoCommandBuffer,
17};
18use vulkano::descriptor_set::{
19    DescriptorSetCreationError, PersistentDescriptorSet, WriteDescriptorSet,
20};
21use vulkano::device::{Device, Queue};
22use vulkano::format::Format;
23use vulkano::image::{
24    ImageCreateFlags, ImageCreationError, ImageDimensions, ImageUsage, StorageImage,
25};
26use vulkano::pipeline::graphics::color_blend::{AttachmentBlend, BlendFactor, ColorBlendState};
27use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
28use vulkano::pipeline::graphics::rasterization::{CullMode, RasterizationState};
29use vulkano::pipeline::graphics::viewport::{Scissor, ViewportState};
30use vulkano::pipeline::graphics::{GraphicsPipeline, GraphicsPipelineCreationError};
31use vulkano::pipeline::Pipeline;
32use vulkano::pipeline::PipelineBindPoint;
33use vulkano::sampler::{
34    Filter, Sampler, SamplerCreateInfo, SamplerCreationError, SamplerMipmapMode,
35};
36mod shaders;
37
38#[derive(Default, Debug, Clone, Copy, Zeroable, Pod)]
39#[repr(C)]
40struct Vertex {
41    pub pos: [f32; 2],
42    pub uv: [f32; 2],
43    pub color: [f32; 4],
44}
45
46impl From<&egui::epaint::Vertex> for Vertex {
47    fn from(v: &egui::epaint::Vertex) -> Self {
48        let convert = {
49            |c: Color32| {
50                [
51                    c.r() as f32 / 255.0,
52                    c.g() as f32 / 255.0,
53                    c.b() as f32 / 255.0,
54                    c.a() as f32 / 255.0,
55                ]
56            }
57        };
58
59        Self {
60            pos: [v.pos.x, v.pos.y],
61            uv: [v.uv.x, v.uv.y],
62            color: convert(v.color),
63        }
64    }
65}
66
67vulkano::impl_vertex!(Vertex, pos, uv, color);
68
69use thiserror::Error;
70use vulkano::command_buffer::pool::CommandPoolBuilderAlloc;
71use vulkano::image::view::{ImageView, ImageViewCreationError};
72use vulkano::memory::DeviceMemoryAllocationError;
73use vulkano::pipeline::graphics::vertex_input::BuffersDefinition;
74use vulkano::render_pass::Subpass;
75
76#[derive(Error, Debug)]
77pub enum PainterCreationError {
78    #[error(transparent)]
79    CreatePipelineFailed(#[from] GraphicsPipelineCreationError),
80    #[error(transparent)]
81    CreateSamplerFailed(#[from] SamplerCreationError),
82}
83
84#[derive(Error, Debug)]
85pub enum UpdateTexturesError {
86    #[error(transparent)]
87    CreateImageViewFailed(#[from] ImageViewCreationError),
88    #[error(transparent)]
89    BuildFailed(#[from] DescriptorSetCreationError),
90    #[error(transparent)]
91    Alloc(#[from] DeviceMemoryAllocationError),
92    #[error(transparent)]
93    Copy(#[from] CopyBufferImageError),
94    #[error(transparent)]
95    CreateImage(#[from] ImageCreationError),
96}
97
98#[derive(Error, Debug)]
99pub enum DrawError {
100    #[error(transparent)]
101    UpdateSetFailed(#[from] UpdateTexturesError),
102    #[error(transparent)]
103    NextSubpassFailed(#[from] AutoCommandBufferBuilderContextError),
104    #[error(transparent)]
105    CreateBuffersFailed(#[from] DeviceMemoryAllocationError),
106    #[error(transparent)]
107    DrawIndexedFailed(#[from] DrawIndexedError),
108}
109
110#[must_use = "You must use this to avoid attempting to modify a texture that's still in use"]
111#[derive(PartialEq)]
112/// You must use this to avoid attempting to modify a texture that's still in use.
113pub enum UpdateTexturesResult {
114    /// No texture will be modified in this frame.
115    Unchanged,
116    /// A texture will be modified in this frame,
117    /// and you must wait for the last frame to finish before submitting the next command buffer.
118    Changed,
119}
120
121/// Contains everything needed to render the gui.
122pub struct Painter {
123    device: Arc<Device>,
124    queue: Arc<Queue>,
125    /// Graphics pipeline used to render the gui.
126    pub pipeline: Arc<GraphicsPipeline>,
127    /// Texture sampler used to render the gui.
128    pub sampler: Arc<Sampler>,
129    images: HashMap<egui::TextureId, Arc<StorageImage>>,
130    texture_sets: HashMap<egui::TextureId, Arc<PersistentDescriptorSet>>,
131    texture_free_queue: Vec<egui::TextureId>,
132}
133
134impl Painter {
135    /// Pass in the vulkano [`Device`], [`Queue`] and [`Subpass`]
136    /// that you want to use to render the gui.
137    pub fn new(
138        device: Arc<Device>,
139        queue: Arc<Queue>,
140        subpass: Subpass,
141    ) -> Result<Self, PainterCreationError> {
142        let pipeline = create_pipeline(device.clone(), subpass.clone())?;
143        let sampler = create_sampler(device.clone())?;
144        Ok(Self {
145            device,
146            queue,
147            pipeline,
148            sampler,
149            images: Default::default(),
150            texture_sets: Default::default(),
151            texture_free_queue: Vec::new(),
152        })
153    }
154
155    fn write_image_delta<P>(
156        &mut self,
157        image: Arc<StorageImage>,
158        delta: &ImageDelta,
159        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer<P::Alloc>, P>,
160    ) -> Result<(), UpdateTexturesError>
161    where
162        P: CommandPoolBuilderAlloc,
163    {
164        let image_data = match &delta.image {
165            ImageData::Color(image) => image
166                .pixels
167                .iter()
168                .flat_map(|c| c.to_array())
169                .collect::<Vec<_>>(),
170            ImageData::Font(image) => image
171                .srgba_pixels(1.0)
172                .flat_map(|c| c.to_array())
173                .collect::<Vec<_>>(),
174        };
175
176        let img_buffer = CpuAccessibleBuffer::from_iter(
177            self.device.clone(),
178            BufferUsage::transfer_source(),
179            false,
180            image_data,
181        )?;
182
183        let size = [delta.image.width() as u32, delta.image.height() as u32, 1];
184        let offset = match delta.pos {
185            None => [0, 0, 0],
186            Some(pos) => [pos[0] as u32, pos[1] as u32, 0],
187        };
188
189        builder.copy_buffer_to_image_dimensions(img_buffer, image, offset, size, 0, 1, 0)?;
190        Ok(())
191    }
192
193    /// Uploads all newly created and modified textures to the GPU.
194    /// Has to be called before entering the first render pass.  
195    /// If the return value is [`UpdateTexturesResult::Changed`],
196    /// a texture will be changed in this frame and you need to wait for the last frame to finish
197    /// before submitting the command buffer for this frame.
198    pub fn update_textures<P>(
199        &mut self,
200        textures_delta: TexturesDelta,
201        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer<P::Alloc>, P>,
202    ) -> Result<UpdateTexturesResult, UpdateTexturesError>
203    where
204        P: CommandPoolBuilderAlloc,
205    {
206        for texture_id in textures_delta.free {
207            self.texture_free_queue.push(texture_id);
208        }
209
210        let mut result = UpdateTexturesResult::Unchanged;
211
212        for (texture_id, delta) in &textures_delta.set {
213            let image = if delta.is_whole() {
214                let image = create_image(self.queue.clone(), &delta.image)?;
215                let layout = &self.pipeline.layout().set_layouts()[0];
216
217                let set = PersistentDescriptorSet::new(
218                    layout.clone(),
219                    [WriteDescriptorSet::image_view_sampler(
220                        0,
221                        ImageView::new_default(image.clone())?,
222                        self.sampler.clone(),
223                    )],
224                )?;
225
226                self.texture_sets.insert(*texture_id, set);
227                self.images.insert(*texture_id, image.clone());
228                image
229            } else {
230                result = UpdateTexturesResult::Changed; //modifying an existing image that might be in use
231                self.images[texture_id].clone()
232            };
233            self.write_image_delta(image, delta, builder)?;
234        }
235
236        Ok(result)
237    }
238
239    /// Free textures freed by egui, *after* drawing
240    fn free_textures(&mut self) {
241        for texture_id in &self.texture_free_queue {
242            self.texture_sets.remove(texture_id);
243            self.images.remove(texture_id);
244        }
245
246        self.texture_free_queue.clear();
247    }
248
249    /// Advances to the next rendering subpass and uses the [`ClippedShape`]s from [`egui::FullOutput`] to draw the gui.
250    pub fn draw<P>(
251        &mut self,
252        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer<P::Alloc>, P>,
253        window_size_points: [f32; 2],
254        egui_ctx: &Context,
255        clipped_shapes: Vec<ClippedShape>,
256    ) -> Result<(), DrawError>
257    where
258        P: CommandPoolBuilderAlloc,
259    {
260        builder
261            .next_subpass(Inline)?
262            .bind_pipeline_graphics(self.pipeline.clone());
263
264        let clipped_primitives: Vec<ClippedPrimitive> = egui_ctx.tessellate(clipped_shapes);
265        let num_meshes = clipped_primitives.len();
266
267        let mut verts = Vec::<Vertex>::with_capacity(num_meshes * 4);
268        let mut indices = Vec::<u32>::with_capacity(num_meshes * 6);
269        let mut clips = Vec::<Rect>::with_capacity(num_meshes);
270        let mut texture_ids = Vec::<TextureId>::with_capacity(num_meshes);
271        let mut offsets = Vec::<(usize, usize)>::with_capacity(num_meshes);
272
273        for cm in clipped_primitives.iter() {
274            let clip = cm.clip_rect;
275            let mesh = match &cm.primitive {
276                Primitive::Mesh(mesh) => mesh,
277                Primitive::Callback(_) => {
278                    continue; // callbacks not supported at the moment
279                }
280            };
281
282            // Skip empty meshes
283            if mesh.vertices.len() == 0 || mesh.indices.len() == 0 {
284                continue;
285            }
286
287            offsets.push((verts.len(), indices.len()));
288            texture_ids.push(mesh.texture_id);
289
290            for v in mesh.vertices.iter() {
291                verts.push(v.into());
292            }
293
294            for i in mesh.indices.iter() {
295                indices.push(*i);
296            }
297
298            clips.push(clip);
299        }
300        offsets.push((verts.len(), indices.len()));
301
302        // Return if there's nothing to render
303        if clips.len() == 0 {
304            return Ok(());
305        }
306
307        let (vertex_buf, index_buf) = self.create_buffers((verts, indices))?;
308        for (idx, clip) in clips.iter().enumerate() {
309            let mut scissors = Vec::with_capacity(1);
310            let o = clip.min;
311            let (w, h) = (clip.width() as u32, clip.height() as u32);
312            scissors.push(Scissor {
313                origin: [(o.x as u32), (o.y as u32)],
314                dimensions: [w, h],
315            });
316            builder.set_scissor(0, scissors);
317
318            let offset = offsets[idx];
319            let end = offsets[idx + 1];
320
321            let vb_slice = BufferSlice::from_typed_buffer_access(vertex_buf.clone())
322                .slice(offset.0 as u64..end.0 as u64)
323                .unwrap();
324            let ib_slice = BufferSlice::from_typed_buffer_access(index_buf.clone())
325                .slice(offset.1 as u64..end.1 as u64)
326                .unwrap();
327
328            let texture_set = self.texture_sets.get(&texture_ids[idx]);
329            if texture_set.is_none() {
330                continue; //skip if we don't have a texture
331            }
332
333            builder
334                .bind_vertex_buffers(0, vb_slice.clone())
335                .bind_index_buffer(ib_slice.clone())
336                .bind_descriptor_sets(
337                    PipelineBindPoint::Graphics,
338                    self.pipeline.layout().clone(),
339                    0,
340                    texture_set.unwrap().clone(),
341                )
342                .push_constants(self.pipeline.layout().clone(), 0, window_size_points)
343                .draw_indexed(ib_slice.len() as u32, 1, 0, 0, 0)?;
344        }
345        self.free_textures();
346        Ok(())
347    }
348
349    /// Create vulkano CpuAccessibleBuffer objects for the vertices and indices
350    fn create_buffers(
351        &self,
352        triangles: (Vec<Vertex>, Vec<u32>),
353    ) -> Result<
354        (
355            Arc<CpuAccessibleBuffer<[Vertex]>>,
356            Arc<CpuAccessibleBuffer<[u32]>>,
357        ),
358        DeviceMemoryAllocationError,
359    > {
360        let vertex_buffer = CpuAccessibleBuffer::from_iter(
361            self.device.clone(),
362            BufferUsage::vertex_buffer(),
363            false,
364            triangles.0.iter().cloned(),
365        )?;
366
367        let index_buffer = CpuAccessibleBuffer::from_iter(
368            self.device.clone(),
369            BufferUsage::index_buffer(),
370            false,
371            triangles.1.iter().cloned(),
372        )?;
373
374        Ok((vertex_buffer, index_buffer))
375    }
376}
377
378/// Create a graphics pipeline with the shaders and settings necessary to render egui output
379fn create_pipeline(
380    device: Arc<Device>,
381    subpass: Subpass,
382) -> Result<Arc<GraphicsPipeline>, GraphicsPipelineCreationError> {
383    let vs = shaders::vs::load(device.clone()).unwrap();
384    let fs = shaders::fs::load(device.clone()).unwrap();
385
386    let mut blend = AttachmentBlend::alpha();
387    blend.color_source = BlendFactor::One;
388
389    let pipeline = GraphicsPipeline::start()
390        .vertex_input_state(BuffersDefinition::new().vertex::<Vertex>())
391        .vertex_shader(vs.entry_point("main").unwrap(), ())
392        .input_assembly_state(InputAssemblyState::new())
393        .viewport_state(ViewportState::viewport_dynamic_scissor_dynamic(1))
394        .fragment_shader(fs.entry_point("main").unwrap(), ())
395        .rasterization_state(RasterizationState::new().cull_mode(CullMode::None))
396        .color_blend_state(ColorBlendState::new(subpass.num_color_attachments()).blend(blend))
397        .render_pass(subpass)
398        .build(device.clone())?;
399    Ok(pipeline)
400}
401
402/// Create a texture sampler for the textures used by egui
403fn create_sampler(device: Arc<Device>) -> Result<Arc<Sampler>, SamplerCreationError> {
404    Sampler::new(
405        device.clone(),
406        SamplerCreateInfo {
407            mag_filter: Filter::Linear,
408            min_filter: Filter::Linear,
409            mipmap_mode: SamplerMipmapMode::Linear,
410            ..Default::default()
411        },
412    )
413}
414
415/// Create a Vulkano image for the given egui texture
416fn create_image(
417    queue: Arc<Queue>,
418    texture: &ImageData,
419) -> Result<Arc<StorageImage>, ImageCreationError> {
420    let dimensions = ImageDimensions::Dim2d {
421        width: texture.width() as u32,
422        height: texture.height() as u32,
423        array_layers: 1,
424    };
425
426    let format = Format::R8G8B8A8_SRGB;
427
428    let usage = ImageUsage {
429        transfer_destination: true,
430        sampled: true,
431        storage: false,
432        ..ImageUsage::none()
433    };
434
435    let image = StorageImage::with_usage(
436        queue.device().clone(),
437        dimensions,
438        format,
439        usage,
440        ImageCreateFlags::none(),
441        [queue.family()],
442    )?;
443
444    Ok(image)
445}