1use std::collections::HashMap;
9use std::ffi::CString;
10use std::sync::Arc;
11
12use ailloli_ui_core::math::Scale;
13use ailloli_ui_core::Color;
14use ailloli_ui_runtime::Scene;
15use ash::vk;
16
17use crate::context::VulkanRenderContext;
18use crate::error::VulkanRendererError;
19use crate::frame_plan::{
20 build_frame_geometry, full_scissor, DrawBatch, FrameStats, LUCIDE_ICON_FACE_ID,
21};
22use crate::gpu::create_buffer_with_data;
23use crate::target::VulkanFrameTarget;
24use crate::text_atlas::TextAtlas;
25use crate::vertices::{BorderRRectVertex, BoxShadowVertex, RRectVertex, SolidVertex, TextVertex};
26
27#[derive(Debug, Clone, Copy)]
37pub struct VulkanRendererOptions {
38 pub enable_debug_labels: bool,
40}
41
42impl Default for VulkanRendererOptions {
44 fn default() -> Self {
46 Self {
47 enable_debug_labels: false,
48 }
49 }
50}
51
52#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
65pub struct VulkanRendererStats {
66 pub rects_rendered: u32,
68 pub glyphs_rendered: u32,
70 pub commands_ignored: u32,
72}
73
74impl From<FrameStats> for VulkanRendererStats {
76 fn from(stats: FrameStats) -> Self {
78 Self {
79 rects_rendered: stats.rects_rendered,
80 glyphs_rendered: stats.glyphs_rendered,
81 commands_ignored: stats.commands_ignored,
82 }
83 }
84}
85
86#[derive(Clone, Copy)]
88struct PipelineHandles {
89 render_pass: vk::RenderPass,
91 rect_pipeline: vk::Pipeline,
93 rrect_pipeline: vk::Pipeline,
95 border_pipeline: vk::Pipeline,
97 shadow_pipeline: vk::Pipeline,
99 text_pipeline: vk::Pipeline,
101 text_pipeline_layout: vk::PipelineLayout,
103}
104
105struct FormatResources {
107 format: vk::Format,
109 render_pass: vk::RenderPass,
111 rect_pipeline: vk::Pipeline,
113 rrect_pipeline: vk::Pipeline,
115 border_pipeline: vk::Pipeline,
117 shadow_pipeline: vk::Pipeline,
119 text_pipeline: vk::Pipeline,
121}
122
123pub struct VulkanRenderer {
139 device: ash::Device,
141 options: VulkanRendererOptions,
143 stats: VulkanRendererStats,
145 text_face_blobs: Arc<HashMap<u64, Arc<[u8]>>>,
147 rect_pipeline_layout: vk::PipelineLayout,
149 text_descriptor_set_layout: vk::DescriptorSetLayout,
151 text_pipeline_layout: vk::PipelineLayout,
153 format_resources: Option<FormatResources>,
155 text_atlas: Option<TextAtlas>,
157}
158
159impl std::fmt::Debug for VulkanRenderer {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("VulkanRenderer")
164 .field("options", &self.options)
165 .field("stats", &self.stats)
166 .finish_non_exhaustive()
167 }
168}
169
170impl VulkanRenderer {
172 pub fn new(
191 context: &VulkanRenderContext<'_>,
192 options: VulkanRendererOptions,
193 ) -> Result<Self, VulkanRendererError> {
194 let rect_pipeline_layout = unsafe {
195 context
196 .device
197 .create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default(), None)
198 }
199 .map_err(|result| VulkanRendererError::CreatePipelineLayout { result })?;
200
201 let bindings = [vk::DescriptorSetLayoutBinding::default()
202 .binding(0)
203 .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
204 .descriptor_count(1)
205 .stage_flags(vk::ShaderStageFlags::FRAGMENT)];
206 let text_descriptor_set_layout = match unsafe {
207 context.device.create_descriptor_set_layout(
208 &vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
209 None,
210 )
211 } {
212 Ok(layout) => layout,
213 Err(result) => {
214 unsafe {
215 context
216 .device
217 .destroy_pipeline_layout(rect_pipeline_layout, None);
218 }
219 return Err(VulkanRendererError::CreateDescriptorSetLayout { result });
220 }
221 };
222
223 let set_layouts = [text_descriptor_set_layout];
224 let text_pipeline_layout = match unsafe {
225 context.device.create_pipeline_layout(
226 &vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
227 None,
228 )
229 } {
230 Ok(layout) => layout,
231 Err(result) => {
232 unsafe {
233 context
234 .device
235 .destroy_descriptor_set_layout(text_descriptor_set_layout, None);
236 context
237 .device
238 .destroy_pipeline_layout(rect_pipeline_layout, None);
239 }
240 return Err(VulkanRendererError::CreatePipelineLayout { result });
241 }
242 };
243
244 Ok(Self {
245 device: context.device.clone(),
246 options,
247 stats: VulkanRendererStats::default(),
248 text_face_blobs: Arc::new(HashMap::new()),
249 rect_pipeline_layout,
250 text_descriptor_set_layout,
251 text_pipeline_layout,
252 format_resources: None,
253 text_atlas: None,
254 })
255 }
256
257 pub fn options(&self) -> VulkanRendererOptions {
269 self.options
270 }
271
272 pub fn stats(&self) -> VulkanRendererStats {
284 self.stats
285 }
286
287 pub fn set_text_face_blobs(&mut self, blobs: Arc<HashMap<u64, Arc<[u8]>>>) {
304 self.text_face_blobs = blobs;
305 }
306
307 pub fn render_scene(
338 &mut self,
339 context: &VulkanRenderContext<'_>,
340 clear: Color,
341 scene: &Scene,
342 scale: Scale,
343 target: &VulkanFrameTarget,
344 ) -> Result<(), VulkanRendererError> {
345 validate_target(target)?;
346
347 let face_blobs = self.text_face_blobs.clone();
348 let text_descriptor_set_layout = self.text_descriptor_set_layout;
349 let text_atlas = &mut self.text_atlas;
350 let geometry = build_frame_geometry(scene, scale, target.extent, |key| {
351 let font_data: &[u8] = if key.face_id == LUCIDE_ICON_FACE_ID {
352 lucide_icons::LUCIDE_FONT_BYTES
353 } else {
354 let Some(font_data) = face_blobs.get(&key.face_id) else {
355 return Ok(None);
356 };
357 font_data.as_ref()
358 };
359 if text_atlas.is_none() {
360 *text_atlas = Some(TextAtlas::new(context, text_descriptor_set_layout)?);
361 }
362 text_atlas
363 .as_mut()
364 .expect("text atlas exists")
365 .get_or_rasterize(context, key, font_data)
366 })?;
367 self.stats = geometry.stats.into();
368
369 let handles = self.ensure_format_resources(context, target.format)?;
370 let solid_buffer = create_buffer_with_data(
371 context,
372 vk::BufferUsageFlags::VERTEX_BUFFER,
373 bytemuck::cast_slice(&geometry.solid_vertices),
374 )?;
375 let rrect_buffer = create_buffer_with_data(
376 context,
377 vk::BufferUsageFlags::VERTEX_BUFFER,
378 bytemuck::cast_slice(&geometry.rrect_vertices),
379 )?;
380 let border_buffer = create_buffer_with_data(
381 context,
382 vk::BufferUsageFlags::VERTEX_BUFFER,
383 bytemuck::cast_slice(&geometry.border_vertices),
384 )?;
385 let shadow_buffer = create_buffer_with_data(
386 context,
387 vk::BufferUsageFlags::VERTEX_BUFFER,
388 bytemuck::cast_slice(&geometry.shadow_vertices),
389 )?;
390 let text_buffer = create_buffer_with_data(
391 context,
392 vk::BufferUsageFlags::VERTEX_BUFFER,
393 bytemuck::cast_slice(&geometry.text_vertices),
394 )?;
395
396 let framebuffer = create_framebuffer(
397 context.device,
398 handles.render_pass,
399 target.view,
400 target.extent,
401 )?;
402 let render_result = submit_one_time_commands(context, |command_buffer| unsafe {
403 record_render_pass(
404 context.device,
405 command_buffer,
406 handles,
407 framebuffer,
408 target,
409 clear,
410 &geometry.batches,
411 solid_buffer.as_ref().map(|buffer| buffer.buffer),
412 rrect_buffer.as_ref().map(|buffer| buffer.buffer),
413 border_buffer.as_ref().map(|buffer| buffer.buffer),
414 shadow_buffer.as_ref().map(|buffer| buffer.buffer),
415 text_buffer.as_ref().map(|buffer| buffer.buffer),
416 self.text_atlas.as_ref(),
417 );
418 });
419 unsafe {
420 context.device.destroy_framebuffer(framebuffer, None);
421 }
422 render_result
423 }
424
425 fn ensure_format_resources(
436 &mut self,
437 context: &VulkanRenderContext<'_>,
438 format: vk::Format,
439 ) -> Result<PipelineHandles, VulkanRendererError> {
440 if self
441 .format_resources
442 .as_ref()
443 .map(|resources| resources.format == format)
444 .unwrap_or(false)
445 {
446 let resources = self.format_resources.as_ref().expect("checked resources");
447 return Ok(PipelineHandles {
448 render_pass: resources.render_pass,
449 rect_pipeline: resources.rect_pipeline,
450 rrect_pipeline: resources.rrect_pipeline,
451 border_pipeline: resources.border_pipeline,
452 shadow_pipeline: resources.shadow_pipeline,
453 text_pipeline: resources.text_pipeline,
454 text_pipeline_layout: self.text_pipeline_layout,
455 });
456 }
457
458 self.destroy_format_resources();
459 let render_pass = create_render_pass(context.device, format)?;
460 let rect_pipeline = create_graphics_pipeline(
461 context.device,
462 render_pass,
463 self.rect_pipeline_layout,
464 crate::shaders::SOLID_RECT_VERT_SPV,
465 crate::shaders::SOLID_RECT_FRAG_SPV,
466 SolidVertex::binding(),
467 &SolidVertex::attributes(),
468 true,
469 )?;
470 let rrect_pipeline = match create_graphics_pipeline(
471 context.device,
472 render_pass,
473 self.rect_pipeline_layout,
474 crate::shaders::ROUNDED_RECT_VERT_SPV,
475 crate::shaders::ROUNDED_RECT_FRAG_SPV,
476 RRectVertex::binding(),
477 &RRectVertex::attributes(),
478 true,
479 ) {
480 Ok(pipeline) => pipeline,
481 Err(error) => {
482 unsafe {
483 context.device.destroy_pipeline(rect_pipeline, None);
484 context.device.destroy_render_pass(render_pass, None);
485 }
486 return Err(error);
487 }
488 };
489 let border_pipeline = match create_graphics_pipeline(
490 context.device,
491 render_pass,
492 self.rect_pipeline_layout,
493 crate::shaders::BORDER_RRECT_VERT_SPV,
494 crate::shaders::BORDER_RRECT_FRAG_SPV,
495 BorderRRectVertex::binding(),
496 &BorderRRectVertex::attributes(),
497 true,
498 ) {
499 Ok(pipeline) => pipeline,
500 Err(error) => {
501 unsafe {
502 context.device.destroy_pipeline(rrect_pipeline, None);
503 context.device.destroy_pipeline(rect_pipeline, None);
504 context.device.destroy_render_pass(render_pass, None);
505 }
506 return Err(error);
507 }
508 };
509 let shadow_pipeline = match create_graphics_pipeline(
510 context.device,
511 render_pass,
512 self.rect_pipeline_layout,
513 crate::shaders::BOX_SHADOW_VERT_SPV,
514 crate::shaders::BOX_SHADOW_FRAG_SPV,
515 BoxShadowVertex::binding(),
516 &BoxShadowVertex::attributes(),
517 true,
518 ) {
519 Ok(pipeline) => pipeline,
520 Err(error) => {
521 unsafe {
522 context.device.destroy_pipeline(border_pipeline, None);
523 context.device.destroy_pipeline(rrect_pipeline, None);
524 context.device.destroy_pipeline(rect_pipeline, None);
525 context.device.destroy_render_pass(render_pass, None);
526 }
527 return Err(error);
528 }
529 };
530 let text_pipeline = match create_graphics_pipeline(
531 context.device,
532 render_pass,
533 self.text_pipeline_layout,
534 crate::shaders::TEXTURED_TEXT_VERT_SPV,
535 crate::shaders::TEXTURED_TEXT_FRAG_SPV,
536 TextVertex::binding(),
537 &TextVertex::attributes(),
538 true,
539 ) {
540 Ok(pipeline) => pipeline,
541 Err(error) => {
542 unsafe {
543 context.device.destroy_pipeline(shadow_pipeline, None);
544 context.device.destroy_pipeline(border_pipeline, None);
545 context.device.destroy_pipeline(rrect_pipeline, None);
546 context.device.destroy_pipeline(rect_pipeline, None);
547 context.device.destroy_render_pass(render_pass, None);
548 }
549 return Err(error);
550 }
551 };
552
553 self.format_resources = Some(FormatResources {
554 format,
555 render_pass,
556 rect_pipeline,
557 rrect_pipeline,
558 border_pipeline,
559 shadow_pipeline,
560 text_pipeline,
561 });
562 Ok(PipelineHandles {
563 render_pass,
564 rect_pipeline,
565 rrect_pipeline,
566 border_pipeline,
567 shadow_pipeline,
568 text_pipeline,
569 text_pipeline_layout: self.text_pipeline_layout,
570 })
571 }
572
573 fn destroy_format_resources(&mut self) {
575 let Some(resources) = self.format_resources.take() else {
576 return;
577 };
578 unsafe {
579 if resources.rect_pipeline != vk::Pipeline::null() {
580 self.device.destroy_pipeline(resources.rect_pipeline, None);
581 }
582 if resources.rrect_pipeline != vk::Pipeline::null() {
583 self.device.destroy_pipeline(resources.rrect_pipeline, None);
584 }
585 if resources.border_pipeline != vk::Pipeline::null() {
586 self.device
587 .destroy_pipeline(resources.border_pipeline, None);
588 }
589 if resources.shadow_pipeline != vk::Pipeline::null() {
590 self.device
591 .destroy_pipeline(resources.shadow_pipeline, None);
592 }
593 if resources.text_pipeline != vk::Pipeline::null() {
594 self.device.destroy_pipeline(resources.text_pipeline, None);
595 }
596 if resources.render_pass != vk::RenderPass::null() {
597 self.device.destroy_render_pass(resources.render_pass, None);
598 }
599 }
600 }
601}
602
603impl Drop for VulkanRenderer {
608 fn drop(&mut self) {
609 self.text_atlas.take();
610 self.destroy_format_resources();
611 unsafe {
612 if self.text_pipeline_layout != vk::PipelineLayout::null() {
613 self.device
614 .destroy_pipeline_layout(self.text_pipeline_layout, None);
615 }
616 if self.text_descriptor_set_layout != vk::DescriptorSetLayout::null() {
617 self.device
618 .destroy_descriptor_set_layout(self.text_descriptor_set_layout, None);
619 }
620 if self.rect_pipeline_layout != vk::PipelineLayout::null() {
621 self.device
622 .destroy_pipeline_layout(self.rect_pipeline_layout, None);
623 }
624 }
625 }
626}
627
628fn validate_target(target: &VulkanFrameTarget) -> Result<(), VulkanRendererError> {
636 if target.image == vk::Image::null() {
637 return Err(VulkanRendererError::InvalidTargetImage);
638 }
639 if target.view == vk::ImageView::null() {
640 return Err(VulkanRendererError::InvalidTargetView);
641 }
642 if target.extent.width == 0 || target.extent.height == 0 {
643 return Err(VulkanRendererError::InvalidTargetExtent {
644 width: target.extent.width,
645 height: target.extent.height,
646 });
647 }
648 Ok(())
649}
650
651fn create_render_pass(
661 device: &ash::Device,
662 format: vk::Format,
663) -> Result<vk::RenderPass, VulkanRendererError> {
664 let attachment = [vk::AttachmentDescription::default()
665 .format(format)
666 .samples(vk::SampleCountFlags::TYPE_1)
667 .load_op(vk::AttachmentLoadOp::CLEAR)
668 .store_op(vk::AttachmentStoreOp::STORE)
669 .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
670 .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
671 .initial_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
672 .final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];
673 let color_attachment = [vk::AttachmentReference::default()
674 .attachment(0)
675 .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];
676 let subpass = [vk::SubpassDescription::default()
677 .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
678 .color_attachments(&color_attachment)];
679 let create_info = vk::RenderPassCreateInfo::default()
680 .attachments(&attachment)
681 .subpasses(&subpass);
682 unsafe { device.create_render_pass(&create_info, None) }
683 .map_err(|result| VulkanRendererError::CreateRenderPass { result })
684}
685
686fn create_graphics_pipeline(
704 device: &ash::Device,
705 render_pass: vk::RenderPass,
706 layout: vk::PipelineLayout,
707 vertex_spv: &[u32],
708 fragment_spv: &[u32],
709 binding: vk::VertexInputBindingDescription,
710 attributes: &[vk::VertexInputAttributeDescription],
711 alpha_blend: bool,
712) -> Result<vk::Pipeline, VulkanRendererError> {
713 let vertex_module = create_shader_module(device, vertex_spv)?;
714 let fragment_module = match create_shader_module(device, fragment_spv) {
715 Ok(module) => module,
716 Err(error) => {
717 unsafe {
718 device.destroy_shader_module(vertex_module, None);
719 }
720 return Err(error);
721 }
722 };
723 let main = CString::new("main").expect("static entry point");
724 let stages = [
725 vk::PipelineShaderStageCreateInfo::default()
726 .stage(vk::ShaderStageFlags::VERTEX)
727 .module(vertex_module)
728 .name(main.as_c_str()),
729 vk::PipelineShaderStageCreateInfo::default()
730 .stage(vk::ShaderStageFlags::FRAGMENT)
731 .module(fragment_module)
732 .name(main.as_c_str()),
733 ];
734 let bindings = [binding];
735 let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
736 .vertex_binding_descriptions(&bindings)
737 .vertex_attribute_descriptions(attributes);
738 let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
739 .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
740 let viewport_state = vk::PipelineViewportStateCreateInfo::default()
741 .viewport_count(1)
742 .scissor_count(1);
743 let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
744 .polygon_mode(vk::PolygonMode::FILL)
745 .cull_mode(vk::CullModeFlags::NONE)
746 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
747 .line_width(1.0);
748 let multisample = vk::PipelineMultisampleStateCreateInfo::default()
749 .rasterization_samples(vk::SampleCountFlags::TYPE_1);
750 let color_attachment = [color_blend_attachment(alpha_blend)];
751 let color_blend =
752 vk::PipelineColorBlendStateCreateInfo::default().attachments(&color_attachment);
753 let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
754 let dynamic_state =
755 vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
756 let pipeline_info = [vk::GraphicsPipelineCreateInfo::default()
757 .stages(&stages)
758 .vertex_input_state(&vertex_input)
759 .input_assembly_state(&input_assembly)
760 .viewport_state(&viewport_state)
761 .rasterization_state(&rasterization)
762 .multisample_state(&multisample)
763 .color_blend_state(&color_blend)
764 .dynamic_state(&dynamic_state)
765 .layout(layout)
766 .render_pass(render_pass)
767 .subpass(0)];
768
769 let result = unsafe {
770 device.create_graphics_pipelines(vk::PipelineCache::null(), &pipeline_info, None)
771 };
772 unsafe {
773 device.destroy_shader_module(fragment_module, None);
774 device.destroy_shader_module(vertex_module, None);
775 }
776 match result {
777 Ok(mut pipelines) => Ok(pipelines.remove(0)),
778 Err((pipelines, result)) => {
779 unsafe {
780 for pipeline in pipelines {
781 device.destroy_pipeline(pipeline, None);
782 }
783 }
784 Err(VulkanRendererError::CreateGraphicsPipeline { result })
785 }
786 }
787}
788
789fn color_blend_attachment(alpha_blend: bool) -> vk::PipelineColorBlendAttachmentState {
794 let mut attachment = vk::PipelineColorBlendAttachmentState::default().color_write_mask(
795 vk::ColorComponentFlags::R
796 | vk::ColorComponentFlags::G
797 | vk::ColorComponentFlags::B
798 | vk::ColorComponentFlags::A,
799 );
800 if alpha_blend {
801 attachment = attachment
802 .blend_enable(true)
803 .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
804 .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
805 .color_blend_op(vk::BlendOp::ADD)
806 .src_alpha_blend_factor(vk::BlendFactor::ONE)
807 .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
808 .alpha_blend_op(vk::BlendOp::ADD);
809 }
810 attachment
811}
812
813fn create_shader_module(
820 device: &ash::Device,
821 code: &[u32],
822) -> Result<vk::ShaderModule, VulkanRendererError> {
823 unsafe { device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(code), None) }
824 .map_err(|result| VulkanRendererError::CreateShaderModule { result })
825}
826
827fn create_framebuffer(
834 device: &ash::Device,
835 render_pass: vk::RenderPass,
836 view: vk::ImageView,
837 extent: vk::Extent2D,
838) -> Result<vk::Framebuffer, VulkanRendererError> {
839 let attachments = [view];
840 let create_info = vk::FramebufferCreateInfo::default()
841 .render_pass(render_pass)
842 .attachments(&attachments)
843 .width(extent.width)
844 .height(extent.height)
845 .layers(1);
846 unsafe { device.create_framebuffer(&create_info, None) }
847 .map_err(|result| VulkanRendererError::CreateFramebuffer { result })
848}
849
850fn submit_one_time_commands<F>(
866 context: &VulkanRenderContext<'_>,
867 record: F,
868) -> Result<(), VulkanRendererError>
869where
870 F: FnOnce(vk::CommandBuffer),
871{
872 let command_buffer = unsafe {
873 context.device.allocate_command_buffers(
874 &vk::CommandBufferAllocateInfo::default()
875 .command_pool(context.command_pool)
876 .level(vk::CommandBufferLevel::PRIMARY)
877 .command_buffer_count(1),
878 )
879 }
880 .map_err(|result| VulkanRendererError::AllocateCommandBuffer { result })?[0];
881
882 let result = (|| {
883 unsafe {
884 context.device.begin_command_buffer(
885 command_buffer,
886 &vk::CommandBufferBeginInfo::default()
887 .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
888 )
889 }
890 .map_err(|result| VulkanRendererError::BeginCommandBuffer { result })?;
891 record(command_buffer);
892 unsafe { context.device.end_command_buffer(command_buffer) }
893 .map_err(|result| VulkanRendererError::EndCommandBuffer { result })?;
894 let command_buffers = [command_buffer];
895 let submit_infos = [vk::SubmitInfo::default().command_buffers(&command_buffers)];
896 unsafe {
897 context
898 .device
899 .queue_submit(context.queue, &submit_infos, vk::Fence::null())
900 }
901 .map_err(|result| VulkanRendererError::QueueSubmit { result })?;
902 unsafe { context.device.queue_wait_idle(context.queue) }
903 .map_err(|result| VulkanRendererError::QueueWaitIdle { result })?;
904 Ok(())
905 })();
906
907 unsafe {
908 context
909 .device
910 .free_command_buffers(context.command_pool, &[command_buffer]);
911 }
912 result
913}
914
915unsafe fn record_render_pass(
928 device: &ash::Device,
929 command_buffer: vk::CommandBuffer,
930 pipelines: PipelineHandles,
931 framebuffer: vk::Framebuffer,
932 target: &VulkanFrameTarget,
933 clear: Color,
934 batches: &[DrawBatch],
935 solid_buffer: Option<vk::Buffer>,
936 rrect_buffer: Option<vk::Buffer>,
937 border_buffer: Option<vk::Buffer>,
938 shadow_buffer: Option<vk::Buffer>,
939 text_buffer: Option<vk::Buffer>,
940 text_atlas: Option<&TextAtlas>,
941) {
942 transition_target(
943 device,
944 command_buffer,
945 target.image,
946 target.initial_layout,
947 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
948 );
949
950 let clear_values = [vk::ClearValue {
951 color: vk::ClearColorValue {
952 float32: clear.to_array(),
953 },
954 }];
955 let render_area = full_scissor(target.extent);
956 let begin_info = vk::RenderPassBeginInfo::default()
957 .render_pass(pipelines.render_pass)
958 .framebuffer(framebuffer)
959 .render_area(render_area)
960 .clear_values(&clear_values);
961 device.cmd_begin_render_pass(command_buffer, &begin_info, vk::SubpassContents::INLINE);
962 let viewport = vk::Viewport {
963 x: 0.0,
964 y: 0.0,
965 width: target.extent.width as f32,
966 height: target.extent.height as f32,
967 min_depth: 0.0,
968 max_depth: 1.0,
969 };
970 device.cmd_set_viewport(command_buffer, 0, &[viewport]);
971
972 for batch in batches {
973 match *batch {
974 DrawBatch::Solid {
975 first_vertex,
976 vertex_count,
977 scissor,
978 } => {
979 let Some(buffer) = solid_buffer else {
980 continue;
981 };
982 device.cmd_bind_pipeline(
983 command_buffer,
984 vk::PipelineBindPoint::GRAPHICS,
985 pipelines.rect_pipeline,
986 );
987 device.cmd_set_scissor(command_buffer, 0, &[scissor]);
988 device.cmd_bind_vertex_buffers(command_buffer, 0, &[buffer], &[0]);
989 device.cmd_draw(command_buffer, vertex_count, 1, first_vertex, 0);
990 }
991 DrawBatch::RRect {
992 first_vertex,
993 vertex_count,
994 scissor,
995 } => {
996 let Some(buffer) = rrect_buffer else {
997 continue;
998 };
999 device.cmd_bind_pipeline(
1000 command_buffer,
1001 vk::PipelineBindPoint::GRAPHICS,
1002 pipelines.rrect_pipeline,
1003 );
1004 device.cmd_set_scissor(command_buffer, 0, &[scissor]);
1005 device.cmd_bind_vertex_buffers(command_buffer, 0, &[buffer], &[0]);
1006 device.cmd_draw(command_buffer, vertex_count, 1, first_vertex, 0);
1007 }
1008 DrawBatch::BorderRRect {
1009 first_vertex,
1010 vertex_count,
1011 scissor,
1012 } => {
1013 let Some(buffer) = border_buffer else {
1014 continue;
1015 };
1016 device.cmd_bind_pipeline(
1017 command_buffer,
1018 vk::PipelineBindPoint::GRAPHICS,
1019 pipelines.border_pipeline,
1020 );
1021 device.cmd_set_scissor(command_buffer, 0, &[scissor]);
1022 device.cmd_bind_vertex_buffers(command_buffer, 0, &[buffer], &[0]);
1023 device.cmd_draw(command_buffer, vertex_count, 1, first_vertex, 0);
1024 }
1025 DrawBatch::BoxShadow {
1026 first_vertex,
1027 vertex_count,
1028 scissor,
1029 } => {
1030 let Some(buffer) = shadow_buffer else {
1031 continue;
1032 };
1033 device.cmd_bind_pipeline(
1034 command_buffer,
1035 vk::PipelineBindPoint::GRAPHICS,
1036 pipelines.shadow_pipeline,
1037 );
1038 device.cmd_set_scissor(command_buffer, 0, &[scissor]);
1039 device.cmd_bind_vertex_buffers(command_buffer, 0, &[buffer], &[0]);
1040 device.cmd_draw(command_buffer, vertex_count, 1, first_vertex, 0);
1041 }
1042 DrawBatch::Text {
1043 page,
1044 first_vertex,
1045 vertex_count,
1046 scissor,
1047 } => {
1048 let (Some(buffer), Some(atlas)) = (text_buffer, text_atlas) else {
1049 continue;
1050 };
1051 let Some(descriptor_set) = atlas.descriptor_set(page) else {
1052 continue;
1053 };
1054 device.cmd_bind_pipeline(
1055 command_buffer,
1056 vk::PipelineBindPoint::GRAPHICS,
1057 pipelines.text_pipeline,
1058 );
1059 device.cmd_set_scissor(command_buffer, 0, &[scissor]);
1060 device.cmd_bind_descriptor_sets(
1061 command_buffer,
1062 vk::PipelineBindPoint::GRAPHICS,
1063 pipelines.text_pipeline_layout,
1064 0,
1065 &[descriptor_set],
1066 &[],
1067 );
1068 device.cmd_bind_vertex_buffers(command_buffer, 0, &[buffer], &[0]);
1069 device.cmd_draw(command_buffer, vertex_count, 1, first_vertex, 0);
1070 }
1071 }
1072 }
1073 device.cmd_end_render_pass(command_buffer);
1074
1075 transition_target(
1076 device,
1077 command_buffer,
1078 target.image,
1079 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
1080 target.final_layout,
1081 );
1082}
1083
1084unsafe fn transition_target(
1091 device: &ash::Device,
1092 command_buffer: vk::CommandBuffer,
1093 image: vk::Image,
1094 old_layout: vk::ImageLayout,
1095 new_layout: vk::ImageLayout,
1096) {
1097 if old_layout == new_layout {
1098 return;
1099 }
1100 let barrier = vk::ImageMemoryBarrier::default()
1101 .image(image)
1102 .old_layout(old_layout)
1103 .new_layout(new_layout)
1104 .src_access_mask(access_mask_for_layout(old_layout))
1105 .dst_access_mask(access_mask_for_layout(new_layout))
1106 .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
1107 .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
1108 .subresource_range(vk::ImageSubresourceRange {
1109 aspect_mask: vk::ImageAspectFlags::COLOR,
1110 base_mip_level: 0,
1111 level_count: 1,
1112 base_array_layer: 0,
1113 layer_count: 1,
1114 });
1115 device.cmd_pipeline_barrier(
1116 command_buffer,
1117 pipeline_stage_for_layout(old_layout),
1118 pipeline_stage_for_layout(new_layout),
1119 vk::DependencyFlags::empty(),
1120 &[],
1121 &[],
1122 &[barrier],
1123 );
1124}
1125
1126fn access_mask_for_layout(layout: vk::ImageLayout) -> vk::AccessFlags {
1128 match layout {
1129 vk::ImageLayout::UNDEFINED => vk::AccessFlags::empty(),
1130 vk::ImageLayout::TRANSFER_DST_OPTIMAL => vk::AccessFlags::TRANSFER_WRITE,
1131 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL => vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
1132 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL => vk::AccessFlags::SHADER_READ,
1133 vk::ImageLayout::GENERAL => vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
1134 _ => vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
1135 }
1136}
1137
1138fn pipeline_stage_for_layout(layout: vk::ImageLayout) -> vk::PipelineStageFlags {
1140 match layout {
1141 vk::ImageLayout::UNDEFINED => vk::PipelineStageFlags::TOP_OF_PIPE,
1142 vk::ImageLayout::TRANSFER_DST_OPTIMAL => vk::PipelineStageFlags::TRANSFER,
1143 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL => {
1144 vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
1145 }
1146 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL => vk::PipelineStageFlags::FRAGMENT_SHADER,
1147 vk::ImageLayout::GENERAL => vk::PipelineStageFlags::ALL_COMMANDS,
1148 _ => vk::PipelineStageFlags::ALL_COMMANDS,
1149 }
1150}