/ src / video_core / renderer_vulkan / renderer_vulkan.cpp
renderer_vulkan.cpp
   1  // Copyright 2023 Citra Emulator Project
   2  // Licensed under GPLv2 or any later version
   3  // Refer to the license.txt file included.
   4  
   5  #include "common/assert.h"
   6  #include "common/logging/log.h"
   7  #include "common/memory_detect.h"
   8  #include "common/microprofile.h"
   9  #include "common/settings.h"
  10  #include "core/core.h"
  11  #include "core/frontend/emu_window.h"
  12  #include "video_core/gpu.h"
  13  #include "video_core/pica/pica_core.h"
  14  #include "video_core/renderer_vulkan/renderer_vulkan.h"
  15  #include "video_core/renderer_vulkan/vk_memory_util.h"
  16  #include "video_core/renderer_vulkan/vk_shader_util.h"
  17  
  18  #include "video_core/host_shaders/vulkan_present_anaglyph_frag.h"
  19  #include "video_core/host_shaders/vulkan_present_frag.h"
  20  #include "video_core/host_shaders/vulkan_present_interlaced_frag.h"
  21  #include "video_core/host_shaders/vulkan_present_vert.h"
  22  
  23  #include <vk_mem_alloc.h>
  24  
  25  MICROPROFILE_DEFINE(Vulkan_RenderFrame, "Vulkan", "Render Frame", MP_RGB(128, 128, 64));
  26  
  27  namespace Vulkan {
  28  
  29  struct ScreenRectVertex {
  30      ScreenRectVertex() = default;
  31      ScreenRectVertex(float x, float y, float u, float v)
  32          : position{Common::MakeVec(x, y)}, tex_coord{Common::MakeVec(u, v)} {}
  33  
  34      Common::Vec2f position;
  35      Common::Vec2f tex_coord;
  36  };
  37  
  38  constexpr u32 VERTEX_BUFFER_SIZE = sizeof(ScreenRectVertex) * 8192;
  39  
  40  constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
  41      // clang-format off
  42      return { 2.f / width, 0.f,         0.f, -1.f,
  43              0.f,         2.f / height, 0.f, -1.f,
  44              0.f,         0.f,          1.f,  0.f,
  45              0.f,         0.f,          0.f,  1.f};
  46      // clang-format on
  47  }
  48  
  49  constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS = {{
  50      {0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment},
  51  }};
  52  
  53  RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_,
  54                                 Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
  55      : RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_},
  56        instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()},
  57        scheduler{instance}, renderpass_cache{instance, scheduler}, pool{instance},
  58        main_window{window, instance, scheduler},
  59        vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
  60                      VERTEX_BUFFER_SIZE},
  61        rasterizer{memory,
  62                   pica,
  63                   system.CustomTexManager(),
  64                   *this,
  65                   render_window,
  66                   instance,
  67                   scheduler,
  68                   pool,
  69                   renderpass_cache,
  70                   main_window.ImageCount()},
  71        present_set_provider{instance, pool, PRESENT_BINDINGS} {
  72      CompileShaders();
  73      BuildLayouts();
  74      BuildPipelines();
  75      if (secondary_window) {
  76          second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
  77      }
  78  }
  79  
  80  RendererVulkan::~RendererVulkan() {
  81      vk::Device device = instance.GetDevice();
  82      scheduler.Finish();
  83      device.waitIdle();
  84  
  85      device.destroyShaderModule(present_vertex_shader);
  86      for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
  87          device.destroyPipeline(present_pipelines[i]);
  88          device.destroyShaderModule(present_shaders[i]);
  89      }
  90  
  91      for (auto& sampler : present_samplers) {
  92          device.destroySampler(sampler);
  93      }
  94  
  95      for (auto& info : screen_infos) {
  96          device.destroyImageView(info.texture.image_view);
  97          vmaDestroyImage(instance.GetAllocator(), info.texture.image, info.texture.allocation);
  98      }
  99  }
 100  
 101  void RendererVulkan::Sync() {
 102      rasterizer.SyncEntireState();
 103  }
 104  
 105  void RendererVulkan::PrepareRendertarget() {
 106      const auto& framebuffer_config = pica.regs.framebuffer_config;
 107      const auto& regs_lcd = pica.regs_lcd;
 108      for (u32 i = 0; i < 3; i++) {
 109          const u32 fb_id = i == 2 ? 1 : 0;
 110          const auto& framebuffer = framebuffer_config[fb_id];
 111          auto& texture = screen_infos[i].texture;
 112  
 113          const auto color_fill = fb_id == 0 ? regs_lcd.color_fill_top : regs_lcd.color_fill_bottom;
 114          if (color_fill.is_enabled) {
 115              FillScreen(color_fill.AsVector(), texture);
 116              continue;
 117          }
 118  
 119          if (texture.width != framebuffer.width || texture.height != framebuffer.height ||
 120              texture.format != framebuffer.color_format) {
 121              ConfigureFramebufferTexture(texture, framebuffer);
 122          }
 123  
 124          LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
 125      }
 126  }
 127  
 128  void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout) {
 129      const auto sampler = present_samplers[!Settings::values.filter_mode.GetValue()];
 130      std::transform(screen_infos.begin(), screen_infos.end(), present_textures.begin(),
 131                     [&](auto& info) {
 132                         return DescriptorData{vk::DescriptorImageInfo{sampler, info.image_view,
 133                                                                       vk::ImageLayout::eGeneral}};
 134                     });
 135  
 136      const auto descriptor_set = present_set_provider.Acquire(present_textures);
 137  
 138      renderpass_cache.EndRendering();
 139      scheduler.Record([this, layout, frame, descriptor_set, renderpass = main_window.Renderpass(),
 140                        index = current_pipeline](vk::CommandBuffer cmdbuf) {
 141          const vk::Viewport viewport = {
 142              .x = 0.0f,
 143              .y = 0.0f,
 144              .width = static_cast<float>(layout.width),
 145              .height = static_cast<float>(layout.height),
 146              .minDepth = 0.0f,
 147              .maxDepth = 1.0f,
 148          };
 149  
 150          const vk::Rect2D scissor = {
 151              .offset = {0, 0},
 152              .extent = {layout.width, layout.height},
 153          };
 154  
 155          cmdbuf.setViewport(0, viewport);
 156          cmdbuf.setScissor(0, scissor);
 157  
 158          const vk::ClearValue clear{.color = clear_color};
 159          const vk::PipelineLayout layout{*present_pipeline_layout};
 160          const vk::RenderPassBeginInfo renderpass_begin_info = {
 161              .renderPass = renderpass,
 162              .framebuffer = frame->framebuffer,
 163              .renderArea =
 164                  vk::Rect2D{
 165                      .offset = {0, 0},
 166                      .extent = {frame->width, frame->height},
 167                  },
 168              .clearValueCount = 1,
 169              .pClearValues = &clear,
 170          };
 171  
 172          cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
 173          cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, present_pipelines[index]);
 174          cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
 175      });
 176  }
 177  
 178  void RendererVulkan::RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout,
 179                                      bool flipped) {
 180      Frame* frame = window.GetRenderFrame();
 181  
 182      if (layout.width != frame->width || layout.height != frame->height) {
 183          window.WaitPresent();
 184          scheduler.Finish();
 185          window.RecreateFrame(frame, layout.width, layout.height);
 186      }
 187  
 188      DrawScreens(frame, layout, flipped);
 189      scheduler.Flush(frame->render_ready);
 190  
 191      window.Present(frame);
 192  }
 193  
 194  void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuffer,
 195                                          ScreenInfo& screen_info, bool right_eye) {
 196  
 197      if (framebuffer.address_right1 == 0 || framebuffer.address_right2 == 0) {
 198          right_eye = false;
 199      }
 200  
 201      const PAddr framebuffer_addr =
 202          framebuffer.active_fb == 0
 203              ? (right_eye ? framebuffer.address_right1 : framebuffer.address_left1)
 204              : (right_eye ? framebuffer.address_right2 : framebuffer.address_left2);
 205  
 206      LOG_TRACE(Render_Vulkan, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}",
 207                framebuffer.stride * framebuffer.height, framebuffer_addr, framebuffer.width.Value(),
 208                framebuffer.height.Value(), framebuffer.format);
 209  
 210      const u32 bpp = Pica::BytesPerPixel(framebuffer.color_format);
 211      const std::size_t pixel_stride = framebuffer.stride / bpp;
 212  
 213      ASSERT(pixel_stride * bpp == framebuffer.stride);
 214      ASSERT(pixel_stride % 4 == 0);
 215  
 216      if (!rasterizer.AccelerateDisplay(framebuffer, framebuffer_addr, static_cast<u32>(pixel_stride),
 217                                        screen_info)) {
 218          // Reset the screen info's display texture to its own permanent texture
 219          screen_info.image_view = screen_info.texture.image_view;
 220          screen_info.texcoords = {0.f, 0.f, 1.f, 1.f};
 221  
 222          ASSERT(false);
 223      }
 224  }
 225  
 226  void RendererVulkan::CompileShaders() {
 227      vk::Device device = instance.GetDevice();
 228      present_vertex_shader =
 229          Compile(HostShaders::VULKAN_PRESENT_VERT, vk::ShaderStageFlagBits::eVertex, device);
 230      present_shaders[0] =
 231          Compile(HostShaders::VULKAN_PRESENT_FRAG, vk::ShaderStageFlagBits::eFragment, device);
 232      present_shaders[1] = Compile(HostShaders::VULKAN_PRESENT_ANAGLYPH_FRAG,
 233                                   vk::ShaderStageFlagBits::eFragment, device);
 234      present_shaders[2] = Compile(HostShaders::VULKAN_PRESENT_INTERLACED_FRAG,
 235                                   vk::ShaderStageFlagBits::eFragment, device);
 236  
 237      auto properties = instance.GetPhysicalDevice().getProperties();
 238      for (std::size_t i = 0; i < present_samplers.size(); i++) {
 239          const vk::Filter filter_mode = i == 0 ? vk::Filter::eLinear : vk::Filter::eNearest;
 240          const vk::SamplerCreateInfo sampler_info = {
 241              .magFilter = filter_mode,
 242              .minFilter = filter_mode,
 243              .mipmapMode = vk::SamplerMipmapMode::eLinear,
 244              .addressModeU = vk::SamplerAddressMode::eClampToEdge,
 245              .addressModeV = vk::SamplerAddressMode::eClampToEdge,
 246              .anisotropyEnable = instance.IsAnisotropicFilteringSupported(),
 247              .maxAnisotropy = properties.limits.maxSamplerAnisotropy,
 248              .compareEnable = false,
 249              .compareOp = vk::CompareOp::eAlways,
 250              .borderColor = vk::BorderColor::eIntOpaqueBlack,
 251              .unnormalizedCoordinates = false,
 252          };
 253  
 254          present_samplers[i] = device.createSampler(sampler_info);
 255      }
 256  }
 257  
 258  void RendererVulkan::BuildLayouts() {
 259      const vk::PushConstantRange push_range = {
 260          .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
 261          .offset = 0,
 262          .size = sizeof(PresentUniformData),
 263      };
 264  
 265      const auto descriptor_set_layout = present_set_provider.Layout();
 266      const vk::PipelineLayoutCreateInfo layout_info = {
 267          .setLayoutCount = 1,
 268          .pSetLayouts = &descriptor_set_layout,
 269          .pushConstantRangeCount = 1,
 270          .pPushConstantRanges = &push_range,
 271      };
 272      present_pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
 273  }
 274  
 275  void RendererVulkan::BuildPipelines() {
 276      const vk::VertexInputBindingDescription binding = {
 277          .binding = 0,
 278          .stride = sizeof(ScreenRectVertex),
 279          .inputRate = vk::VertexInputRate::eVertex,
 280      };
 281  
 282      const std::array attributes = {
 283          vk::VertexInputAttributeDescription{
 284              .location = 0,
 285              .binding = 0,
 286              .format = vk::Format::eR32G32Sfloat,
 287              .offset = offsetof(ScreenRectVertex, position),
 288          },
 289          vk::VertexInputAttributeDescription{
 290              .location = 1,
 291              .binding = 0,
 292              .format = vk::Format::eR32G32Sfloat,
 293              .offset = offsetof(ScreenRectVertex, tex_coord),
 294          },
 295      };
 296  
 297      const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
 298          .vertexBindingDescriptionCount = 1,
 299          .pVertexBindingDescriptions = &binding,
 300          .vertexAttributeDescriptionCount = static_cast<u32>(attributes.size()),
 301          .pVertexAttributeDescriptions = attributes.data(),
 302      };
 303  
 304      const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
 305          .topology = vk::PrimitiveTopology::eTriangleStrip,
 306          .primitiveRestartEnable = false,
 307      };
 308  
 309      const vk::PipelineRasterizationStateCreateInfo raster_state = {
 310          .depthClampEnable = false,
 311          .rasterizerDiscardEnable = false,
 312          .cullMode = vk::CullModeFlagBits::eNone,
 313          .frontFace = vk::FrontFace::eClockwise,
 314          .depthBiasEnable = false,
 315          .lineWidth = 1.0f,
 316      };
 317  
 318      const vk::PipelineMultisampleStateCreateInfo multisampling = {
 319          .rasterizationSamples = vk::SampleCountFlagBits::e1,
 320          .sampleShadingEnable = false,
 321      };
 322  
 323      const vk::PipelineColorBlendAttachmentState colorblend_attachment = {
 324          .blendEnable = false,
 325          .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
 326                            vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
 327      };
 328  
 329      const vk::PipelineColorBlendStateCreateInfo color_blending = {
 330          .logicOpEnable = false,
 331          .attachmentCount = 1,
 332          .pAttachments = &colorblend_attachment,
 333          .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
 334      };
 335  
 336      const vk::Viewport placeholder_viewport = vk::Viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
 337      const vk::Rect2D placeholder_scissor = vk::Rect2D{{0, 0}, {1, 1}};
 338      const vk::PipelineViewportStateCreateInfo viewport_info = {
 339          .viewportCount = 1,
 340          .pViewports = &placeholder_viewport,
 341          .scissorCount = 1,
 342          .pScissors = &placeholder_scissor,
 343      };
 344  
 345      const std::array dynamic_states = {
 346          vk::DynamicState::eViewport,
 347          vk::DynamicState::eScissor,
 348      };
 349  
 350      const vk::PipelineDynamicStateCreateInfo dynamic_info = {
 351          .dynamicStateCount = static_cast<u32>(dynamic_states.size()),
 352          .pDynamicStates = dynamic_states.data(),
 353      };
 354  
 355      const vk::PipelineDepthStencilStateCreateInfo depth_info = {
 356          .depthTestEnable = false,
 357          .depthWriteEnable = false,
 358          .depthCompareOp = vk::CompareOp::eAlways,
 359          .depthBoundsTestEnable = false,
 360          .stencilTestEnable = false,
 361      };
 362  
 363      for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
 364          const std::array shader_stages = {
 365              vk::PipelineShaderStageCreateInfo{
 366                  .stage = vk::ShaderStageFlagBits::eVertex,
 367                  .module = present_vertex_shader,
 368                  .pName = "main",
 369              },
 370              vk::PipelineShaderStageCreateInfo{
 371                  .stage = vk::ShaderStageFlagBits::eFragment,
 372                  .module = present_shaders[i],
 373                  .pName = "main",
 374              },
 375          };
 376  
 377          const vk::GraphicsPipelineCreateInfo pipeline_info = {
 378              .stageCount = static_cast<u32>(shader_stages.size()),
 379              .pStages = shader_stages.data(),
 380              .pVertexInputState = &vertex_input_info,
 381              .pInputAssemblyState = &input_assembly,
 382              .pViewportState = &viewport_info,
 383              .pRasterizationState = &raster_state,
 384              .pMultisampleState = &multisampling,
 385              .pDepthStencilState = &depth_info,
 386              .pColorBlendState = &color_blending,
 387              .pDynamicState = &dynamic_info,
 388              .layout = *present_pipeline_layout,
 389              .renderPass = main_window.Renderpass(),
 390          };
 391  
 392          const auto [result, pipeline] =
 393              instance.GetDevice().createGraphicsPipeline({}, pipeline_info);
 394          ASSERT_MSG(result == vk::Result::eSuccess, "Unable to build present pipelines");
 395          present_pipelines[i] = pipeline;
 396      }
 397  }
 398  
 399  void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
 400                                                   const Pica::FramebufferConfig& framebuffer) {
 401      vk::Device device = instance.GetDevice();
 402      if (texture.image_view) {
 403          device.destroyImageView(texture.image_view);
 404      }
 405      if (texture.image) {
 406          vmaDestroyImage(instance.GetAllocator(), texture.image, texture.allocation);
 407      }
 408  
 409      const VideoCore::PixelFormat pixel_format =
 410          VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format);
 411      const vk::Format format = instance.GetTraits(pixel_format).native;
 412      const vk::ImageCreateInfo image_info = {
 413          .imageType = vk::ImageType::e2D,
 414          .format = format,
 415          .extent = {framebuffer.width, framebuffer.height, 1},
 416          .mipLevels = 1,
 417          .arrayLayers = 1,
 418          .samples = vk::SampleCountFlagBits::e1,
 419          .usage = vk::ImageUsageFlagBits::eSampled,
 420      };
 421  
 422      const VmaAllocationCreateInfo alloc_info = {
 423          .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
 424          .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
 425          .requiredFlags = 0,
 426          .preferredFlags = 0,
 427          .pool = VK_NULL_HANDLE,
 428          .pUserData = nullptr,
 429      };
 430  
 431      VkImage unsafe_image{};
 432      VkImageCreateInfo unsafe_image_info = static_cast<VkImageCreateInfo>(image_info);
 433  
 434      VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, &alloc_info,
 435                                       &unsafe_image, &texture.allocation, nullptr);
 436      if (result != VK_SUCCESS) [[unlikely]] {
 437          LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result);
 438          UNREACHABLE();
 439      }
 440      texture.image = vk::Image{unsafe_image};
 441  
 442      const vk::ImageViewCreateInfo view_info = {
 443          .image = texture.image,
 444          .viewType = vk::ImageViewType::e2D,
 445          .format = format,
 446          .subresourceRange{
 447              .aspectMask = vk::ImageAspectFlagBits::eColor,
 448              .baseMipLevel = 0,
 449              .levelCount = 1,
 450              .baseArrayLayer = 0,
 451              .layerCount = 1,
 452          },
 453      };
 454      texture.image_view = device.createImageView(view_info);
 455  
 456      texture.width = framebuffer.width;
 457      texture.height = framebuffer.height;
 458      texture.format = framebuffer.color_format;
 459  }
 460  
 461  void RendererVulkan::FillScreen(Common::Vec3<u8> color, const TextureInfo& texture) {
 462      return;
 463      const vk::ClearColorValue clear_color = {
 464          .float32 =
 465              std::array{
 466                  color.r() / 255.0f,
 467                  color.g() / 255.0f,
 468                  color.b() / 255.0f,
 469                  1.0f,
 470              },
 471      };
 472  
 473      renderpass_cache.EndRendering();
 474      scheduler.Record([image = texture.image, clear_color](vk::CommandBuffer cmdbuf) {
 475          const vk::ImageSubresourceRange range = {
 476              .aspectMask = vk::ImageAspectFlagBits::eColor,
 477              .baseMipLevel = 0,
 478              .levelCount = VK_REMAINING_MIP_LEVELS,
 479              .baseArrayLayer = 0,
 480              .layerCount = VK_REMAINING_ARRAY_LAYERS,
 481          };
 482  
 483          const vk::ImageMemoryBarrier pre_barrier = {
 484              .srcAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
 485              .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 486              .oldLayout = vk::ImageLayout::eGeneral,
 487              .newLayout = vk::ImageLayout::eTransferDstOptimal,
 488              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 489              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 490              .image = image,
 491              .subresourceRange = range,
 492          };
 493  
 494          const vk::ImageMemoryBarrier post_barrier = {
 495              .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 496              .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
 497              .oldLayout = vk::ImageLayout::eTransferDstOptimal,
 498              .newLayout = vk::ImageLayout::eGeneral,
 499              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 500              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 501              .image = image,
 502              .subresourceRange = range,
 503          };
 504  
 505          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
 506                                 vk::PipelineStageFlagBits::eTransfer,
 507                                 vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier);
 508  
 509          cmdbuf.clearColorImage(image, vk::ImageLayout::eTransferDstOptimal, clear_color, range);
 510  
 511          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
 512                                 vk::PipelineStageFlagBits::eFragmentShader,
 513                                 vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
 514      });
 515  }
 516  
 517  void RendererVulkan::ReloadPipeline() {
 518      const Settings::StereoRenderOption render_3d = Settings::values.render_3d.GetValue();
 519      switch (render_3d) {
 520      case Settings::StereoRenderOption::Anaglyph:
 521          current_pipeline = 1;
 522          break;
 523      case Settings::StereoRenderOption::Interlaced:
 524      case Settings::StereoRenderOption::ReverseInterlaced:
 525          current_pipeline = 2;
 526          draw_info.reverse_interlaced = render_3d == Settings::StereoRenderOption::ReverseInterlaced;
 527          break;
 528      default:
 529          current_pipeline = 0;
 530          break;
 531      }
 532  }
 533  
 534  void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w, float h,
 535                                        Layout::DisplayOrientation orientation) {
 536      const ScreenInfo& screen_info = screen_infos[screen_id];
 537      const auto& texcoords = screen_info.texcoords;
 538  
 539      std::array<ScreenRectVertex, 4> vertices;
 540      switch (orientation) {
 541      case Layout::DisplayOrientation::Landscape:
 542          vertices = {{
 543              ScreenRectVertex(x, y, texcoords.bottom, texcoords.left),
 544              ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right),
 545              ScreenRectVertex(x, y + h, texcoords.top, texcoords.left),
 546              ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right),
 547          }};
 548          break;
 549      case Layout::DisplayOrientation::Portrait:
 550          vertices = {{
 551              ScreenRectVertex(x, y, texcoords.bottom, texcoords.right),
 552              ScreenRectVertex(x + w, y, texcoords.top, texcoords.right),
 553              ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left),
 554              ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left),
 555          }};
 556          std::swap(h, w);
 557          break;
 558      case Layout::DisplayOrientation::LandscapeFlipped:
 559          vertices = {{
 560              ScreenRectVertex(x, y, texcoords.top, texcoords.right),
 561              ScreenRectVertex(x + w, y, texcoords.top, texcoords.left),
 562              ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.right),
 563              ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.left),
 564          }};
 565          break;
 566      case Layout::DisplayOrientation::PortraitFlipped:
 567          vertices = {{
 568              ScreenRectVertex(x, y, texcoords.top, texcoords.left),
 569              ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.left),
 570              ScreenRectVertex(x, y + h, texcoords.top, texcoords.right),
 571              ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.right),
 572          }};
 573          std::swap(h, w);
 574          break;
 575      default:
 576          LOG_ERROR(Render_Vulkan, "Unknown DisplayOrientation: {}", orientation);
 577          break;
 578      }
 579  
 580      const u64 size = sizeof(ScreenRectVertex) * vertices.size();
 581      auto [data, offset, invalidate] = vertex_buffer.Map(size, 16);
 582      std::memcpy(data, vertices.data(), size);
 583      vertex_buffer.Commit(size);
 584  
 585      const u32 scale_factor = GetResolutionScaleFactor();
 586      draw_info.i_resolution =
 587          Common::MakeVec(static_cast<f32>(screen_info.texture.width * scale_factor),
 588                          static_cast<f32>(screen_info.texture.height * scale_factor),
 589                          1.0f / static_cast<f32>(screen_info.texture.width * scale_factor),
 590                          1.0f / static_cast<f32>(screen_info.texture.height * scale_factor));
 591      draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w);
 592      draw_info.screen_id_l = screen_id;
 593  
 594      scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
 595          const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
 596          cmdbuf.pushConstants(*present_pipeline_layout,
 597                               vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
 598                               0, sizeof(info), &info);
 599  
 600          cmdbuf.bindVertexBuffers(0, vertex_buffer.Handle(), {0});
 601          cmdbuf.draw(4, 1, first_vertex, 0);
 602      });
 603  }
 604  
 605  void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, float x, float y,
 606                                              float w, float h,
 607                                              Layout::DisplayOrientation orientation) {
 608      const ScreenInfo& screen_info_l = screen_infos[screen_id_l];
 609      const auto& texcoords = screen_info_l.texcoords;
 610  
 611      std::array<ScreenRectVertex, 4> vertices;
 612      switch (orientation) {
 613      case Layout::DisplayOrientation::Landscape:
 614          vertices = {{
 615              ScreenRectVertex(x, y, texcoords.bottom, texcoords.left),
 616              ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right),
 617              ScreenRectVertex(x, y + h, texcoords.top, texcoords.left),
 618              ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right),
 619          }};
 620          break;
 621      case Layout::DisplayOrientation::Portrait:
 622          vertices = {{
 623              ScreenRectVertex(x, y, texcoords.bottom, texcoords.right),
 624              ScreenRectVertex(x + w, y, texcoords.top, texcoords.right),
 625              ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left),
 626              ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left),
 627          }};
 628          std::swap(h, w);
 629          break;
 630      case Layout::DisplayOrientation::LandscapeFlipped:
 631          vertices = {{
 632              ScreenRectVertex(x, y, texcoords.top, texcoords.right),
 633              ScreenRectVertex(x + w, y, texcoords.top, texcoords.left),
 634              ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.right),
 635              ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.left),
 636          }};
 637          break;
 638      case Layout::DisplayOrientation::PortraitFlipped:
 639          vertices = {{
 640              ScreenRectVertex(x, y, texcoords.top, texcoords.left),
 641              ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.left),
 642              ScreenRectVertex(x, y + h, texcoords.top, texcoords.right),
 643              ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.right),
 644          }};
 645          std::swap(h, w);
 646          break;
 647      default:
 648          LOG_ERROR(Render_Vulkan, "Unknown DisplayOrientation: {}", orientation);
 649          break;
 650      }
 651  
 652      const u64 size = sizeof(ScreenRectVertex) * vertices.size();
 653      auto [data, offset, invalidate] = vertex_buffer.Map(size, 16);
 654      std::memcpy(data, vertices.data(), size);
 655      vertex_buffer.Commit(size);
 656  
 657      const u32 scale_factor = GetResolutionScaleFactor();
 658      draw_info.i_resolution =
 659          Common::MakeVec(static_cast<f32>(screen_info_l.texture.width * scale_factor),
 660                          static_cast<f32>(screen_info_l.texture.height * scale_factor),
 661                          1.0f / static_cast<f32>(screen_info_l.texture.width * scale_factor),
 662                          1.0f / static_cast<f32>(screen_info_l.texture.height * scale_factor));
 663      draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w);
 664      draw_info.screen_id_l = screen_id_l;
 665      draw_info.screen_id_r = screen_id_r;
 666  
 667      scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
 668          const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
 669          cmdbuf.pushConstants(*present_pipeline_layout,
 670                               vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
 671                               0, sizeof(info), &info);
 672  
 673          cmdbuf.bindVertexBuffers(0, vertex_buffer.Handle(), {0});
 674          cmdbuf.draw(4, 1, first_vertex, 0);
 675      });
 676  }
 677  
 678  void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
 679                                     const Common::Rectangle<u32>& top_screen) {
 680      if (!layout.top_screen_enabled) {
 681          return;
 682      }
 683  
 684      const float top_screen_left = static_cast<float>(top_screen.left);
 685      const float top_screen_top = static_cast<float>(top_screen.top);
 686      const float top_screen_width = static_cast<float>(top_screen.GetWidth());
 687      const float top_screen_height = static_cast<float>(top_screen.GetHeight());
 688  
 689      const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
 690                                                 : Layout::DisplayOrientation::Portrait;
 691      switch (Settings::values.render_3d.GetValue()) {
 692      case Settings::StereoRenderOption::Off: {
 693          const int eye = static_cast<int>(Settings::values.mono_render_option.GetValue());
 694          DrawSingleScreen(eye, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
 695                           orientation);
 696          break;
 697      }
 698      case Settings::StereoRenderOption::SideBySide: {
 699          DrawSingleScreen(0, top_screen_left / 2, top_screen_top, top_screen_width / 2,
 700                           top_screen_height, orientation);
 701          draw_info.layer = 1;
 702          DrawSingleScreen(1, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
 703                           top_screen_top, top_screen_width / 2, top_screen_height, orientation);
 704          break;
 705      }
 706      case Settings::StereoRenderOption::CardboardVR: {
 707          DrawSingleScreen(0, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
 708                           orientation);
 709          draw_info.layer = 1;
 710          DrawSingleScreen(
 711              1, static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
 712              top_screen_top, top_screen_width, top_screen_height, orientation);
 713          break;
 714      }
 715      case Settings::StereoRenderOption::Anaglyph:
 716      case Settings::StereoRenderOption::Interlaced:
 717      case Settings::StereoRenderOption::ReverseInterlaced: {
 718          DrawSingleScreenStereo(0, 1, top_screen_left, top_screen_top, top_screen_width,
 719                                 top_screen_height, orientation);
 720          break;
 721      }
 722      }
 723  }
 724  
 725  void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout,
 726                                        const Common::Rectangle<u32>& bottom_screen) {
 727      if (!layout.bottom_screen_enabled) {
 728          return;
 729      }
 730  
 731      const float bottom_screen_left = static_cast<float>(bottom_screen.left);
 732      const float bottom_screen_top = static_cast<float>(bottom_screen.top);
 733      const float bottom_screen_width = static_cast<float>(bottom_screen.GetWidth());
 734      const float bottom_screen_height = static_cast<float>(bottom_screen.GetHeight());
 735  
 736      const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
 737                                                 : Layout::DisplayOrientation::Portrait;
 738  
 739      switch (Settings::values.render_3d.GetValue()) {
 740      case Settings::StereoRenderOption::Off: {
 741          DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
 742                           bottom_screen_height, orientation);
 743          break;
 744      }
 745      case Settings::StereoRenderOption::SideBySide: {
 746          DrawSingleScreen(2, bottom_screen_left / 2, bottom_screen_top, bottom_screen_width / 2,
 747                           bottom_screen_height, orientation);
 748          draw_info.layer = 1;
 749          DrawSingleScreen(2, static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
 750                           bottom_screen_top, bottom_screen_width / 2, bottom_screen_height,
 751                           orientation);
 752          break;
 753      }
 754      case Settings::StereoRenderOption::CardboardVR: {
 755          DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
 756                           bottom_screen_height, orientation);
 757          draw_info.layer = 1;
 758          DrawSingleScreen(
 759              2, static_cast<float>(layout.cardboard.bottom_screen_right_eye + (layout.width / 2)),
 760              bottom_screen_top, bottom_screen_width, bottom_screen_height, orientation);
 761          break;
 762      }
 763      case Settings::StereoRenderOption::Anaglyph:
 764      case Settings::StereoRenderOption::Interlaced:
 765      case Settings::StereoRenderOption::ReverseInterlaced: {
 766          DrawSingleScreenStereo(2, 2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
 767                                 bottom_screen_height, orientation);
 768          break;
 769      }
 770      }
 771  }
 772  
 773  void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout& layout,
 774                                   bool flipped) {
 775      if (settings.bg_color_update_requested.exchange(false)) {
 776          clear_color.float32[0] = Settings::values.bg_red.GetValue();
 777          clear_color.float32[1] = Settings::values.bg_green.GetValue();
 778          clear_color.float32[2] = Settings::values.bg_blue.GetValue();
 779      }
 780      if (settings.shader_update_requested.exchange(false)) {
 781          ReloadPipeline();
 782      }
 783  
 784      PrepareDraw(frame, layout);
 785  
 786      const auto& top_screen = layout.top_screen;
 787      const auto& bottom_screen = layout.bottom_screen;
 788      draw_info.modelview = MakeOrthographicMatrix(layout.width, layout.height);
 789  
 790      draw_info.layer = 0;
 791      if (!Settings::values.swap_screen.GetValue()) {
 792          DrawTopScreen(layout, top_screen);
 793          draw_info.layer = 0;
 794          DrawBottomScreen(layout, bottom_screen);
 795      } else {
 796          DrawBottomScreen(layout, bottom_screen);
 797          draw_info.layer = 0;
 798          DrawTopScreen(layout, top_screen);
 799      }
 800  
 801      if (layout.additional_screen_enabled) {
 802          const auto& additional_screen = layout.additional_screen;
 803          if (!Settings::values.swap_screen.GetValue()) {
 804              DrawTopScreen(layout, additional_screen);
 805          } else {
 806              DrawBottomScreen(layout, additional_screen);
 807          }
 808      }
 809  
 810      scheduler.Record([image = frame->image](vk::CommandBuffer cmdbuf) {
 811          const vk::ImageMemoryBarrier render_barrier = {
 812              .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
 813              .dstAccessMask = vk::AccessFlagBits::eTransferRead,
 814              .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
 815              .newLayout = vk::ImageLayout::eTransferSrcOptimal,
 816              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 817              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 818              .image = image,
 819              .subresourceRange{
 820                  .aspectMask = vk::ImageAspectFlagBits::eColor,
 821                  .baseMipLevel = 0,
 822                  .levelCount = 1,
 823                  .baseArrayLayer = 0,
 824                  .layerCount = VK_REMAINING_ARRAY_LAYERS,
 825              },
 826          };
 827  
 828          cmdbuf.endRenderPass();
 829          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
 830                                 vk::PipelineStageFlagBits::eTransfer,
 831                                 vk::DependencyFlagBits::eByRegion, {}, {}, render_barrier);
 832      });
 833  }
 834  
 835  void RendererVulkan::SwapBuffers() {
 836      const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
 837      PrepareRendertarget();
 838      RenderScreenshot();
 839      RenderToWindow(main_window, layout, false);
 840  #ifndef ANDROID
 841      if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
 842          ASSERT(secondary_window);
 843          const auto& secondary_layout = secondary_window->GetFramebufferLayout();
 844          if (!second_window) {
 845              second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
 846          }
 847          RenderToWindow(*second_window, secondary_layout, false);
 848          secondary_window->PollEvents();
 849      }
 850  #endif
 851      rasterizer.TickFrame();
 852      EndFrame();
 853  }
 854  
 855  void RendererVulkan::RenderScreenshot() {
 856      if (!settings.screenshot_requested.exchange(false)) {
 857          return;
 858      }
 859  
 860      if (!TryRenderScreenshotWithHostMemory()) {
 861          RenderScreenshotWithStagingCopy();
 862      }
 863  
 864      settings.screenshot_complete_callback(false);
 865  }
 866  
 867  void RendererVulkan::RenderScreenshotWithStagingCopy() {
 868      const vk::Device device = instance.GetDevice();
 869  
 870      const Layout::FramebufferLayout layout{settings.screenshot_framebuffer_layout};
 871      const u32 width = layout.width;
 872      const u32 height = layout.height;
 873  
 874      const vk::BufferCreateInfo staging_buffer_info = {
 875          .size = width * height * 4,
 876          .usage = vk::BufferUsageFlagBits::eTransferDst,
 877      };
 878  
 879      const VmaAllocationCreateInfo alloc_create_info = {
 880          .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
 881                   VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
 882          .usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
 883          .requiredFlags = 0,
 884          .preferredFlags = 0,
 885          .pool = VK_NULL_HANDLE,
 886          .pUserData = nullptr,
 887      };
 888  
 889      VkBuffer unsafe_buffer{};
 890      VmaAllocation allocation{};
 891      VmaAllocationInfo alloc_info;
 892      VkBufferCreateInfo unsafe_buffer_info = static_cast<VkBufferCreateInfo>(staging_buffer_info);
 893  
 894      VkResult result = vmaCreateBuffer(instance.GetAllocator(), &unsafe_buffer_info,
 895                                        &alloc_create_info, &unsafe_buffer, &allocation, &alloc_info);
 896      if (result != VK_SUCCESS) [[unlikely]] {
 897          LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result);
 898          UNREACHABLE();
 899      }
 900  
 901      vk::Buffer staging_buffer{unsafe_buffer};
 902  
 903      Frame frame{};
 904      main_window.RecreateFrame(&frame, width, height);
 905  
 906      DrawScreens(&frame, layout, false);
 907  
 908      scheduler.Record(
 909          [width, height, source_image = frame.image, staging_buffer](vk::CommandBuffer cmdbuf) {
 910              const vk::ImageMemoryBarrier read_barrier = {
 911                  .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
 912                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
 913                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
 914                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
 915                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 916                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 917                  .image = source_image,
 918                  .subresourceRange{
 919                      .aspectMask = vk::ImageAspectFlagBits::eColor,
 920                      .baseMipLevel = 0,
 921                      .levelCount = VK_REMAINING_MIP_LEVELS,
 922                      .baseArrayLayer = 0,
 923                      .layerCount = VK_REMAINING_ARRAY_LAYERS,
 924                  },
 925              };
 926              const vk::ImageMemoryBarrier write_barrier = {
 927                  .srcAccessMask = vk::AccessFlagBits::eTransferRead,
 928                  .dstAccessMask = vk::AccessFlagBits::eMemoryWrite,
 929                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
 930                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
 931                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 932                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 933                  .image = source_image,
 934                  .subresourceRange{
 935                      .aspectMask = vk::ImageAspectFlagBits::eColor,
 936                      .baseMipLevel = 0,
 937                      .levelCount = VK_REMAINING_MIP_LEVELS,
 938                      .baseArrayLayer = 0,
 939                      .layerCount = VK_REMAINING_ARRAY_LAYERS,
 940                  },
 941              };
 942              static constexpr vk::MemoryBarrier memory_write_barrier = {
 943                  .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
 944                  .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
 945              };
 946  
 947              const vk::BufferImageCopy image_copy = {
 948                  .bufferOffset = 0,
 949                  .bufferRowLength = 0,
 950                  .bufferImageHeight = 0,
 951                  .imageSubresource =
 952                      {
 953                          .aspectMask = vk::ImageAspectFlagBits::eColor,
 954                          .mipLevel = 0,
 955                          .baseArrayLayer = 0,
 956                          .layerCount = 1,
 957                      },
 958                  .imageOffset = {0, 0, 0},
 959                  .imageExtent = {width, height, 1},
 960              };
 961  
 962              cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
 963                                     vk::PipelineStageFlagBits::eTransfer,
 964                                     vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
 965              cmdbuf.copyImageToBuffer(source_image, vk::ImageLayout::eTransferSrcOptimal,
 966                                       staging_buffer, image_copy);
 967              cmdbuf.pipelineBarrier(
 968                  vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands,
 969                  vk::DependencyFlagBits::eByRegion, memory_write_barrier, {}, write_barrier);
 970          });
 971  
 972      // Ensure the copy is fully completed before saving the screenshot
 973      scheduler.Finish();
 974  
 975      // Copy backing image data to the QImage screenshot buffer
 976      std::memcpy(settings.screenshot_bits, alloc_info.pMappedData, staging_buffer_info.size);
 977  
 978      // Destroy allocated resources
 979      vmaDestroyBuffer(instance.GetAllocator(), staging_buffer, allocation);
 980      vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation);
 981      device.destroyFramebuffer(frame.framebuffer);
 982      device.destroyImageView(frame.image_view);
 983  }
 984  
 985  bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
 986      // If the host-memory import alignment matches the allocation granularity of the platform, then
 987      // the entire span of memory can be trivially imported
 988      const bool trivial_import =
 989          instance.IsExternalMemoryHostSupported() &&
 990          instance.GetMinImportedHostPointerAlignment() == Common::GetPageSize();
 991      if (!trivial_import) {
 992          return false;
 993      }
 994  
 995      const vk::Device device = instance.GetDevice();
 996  
 997      const Layout::FramebufferLayout layout{settings.screenshot_framebuffer_layout};
 998      const u32 width = layout.width;
 999      const u32 height = layout.height;
1000  
1001      // For a span of memory [x, x + s], import [AlignDown(x, alignment), AlignUp(x + s, alignment)]
1002      // and maintain an offset to the start of the data
1003      const u64 import_alignment = instance.GetMinImportedHostPointerAlignment();
1004      const uintptr_t address = reinterpret_cast<uintptr_t>(settings.screenshot_bits);
1005      void* aligned_pointer = reinterpret_cast<void*>(Common::AlignDown(address, import_alignment));
1006      const u64 offset = address % import_alignment;
1007      const u64 aligned_size = Common::AlignUp(offset + width * height * 4ull, import_alignment);
1008  
1009      // Buffer<->Image mapping for the imported imported buffer
1010      const vk::BufferImageCopy buffer_image_copy = {
1011          .bufferOffset = offset,
1012          .bufferRowLength = 0,
1013          .bufferImageHeight = 0,
1014          .imageSubresource =
1015              {
1016                  .aspectMask = vk::ImageAspectFlagBits::eColor,
1017                  .mipLevel = 0,
1018                  .baseArrayLayer = 0,
1019                  .layerCount = 1,
1020              },
1021          .imageOffset = {0, 0, 0},
1022          .imageExtent = {width, height, 1},
1023      };
1024  
1025      const vk::MemoryHostPointerPropertiesEXT import_properties =
1026          device.getMemoryHostPointerPropertiesEXT(
1027              vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, aligned_pointer);
1028  
1029      if (!import_properties.memoryTypeBits) {
1030          // Could not import memory
1031          return false;
1032      }
1033  
1034      const std::optional<u32> memory_type_index = FindMemoryType(
1035          instance.GetPhysicalDevice().getMemoryProperties(),
1036          vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
1037          import_properties.memoryTypeBits);
1038  
1039      if (!memory_type_index.has_value()) {
1040          // Could not find memory type index
1041          return false;
1042      }
1043  
1044      const vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryHostPointerInfoEXT>
1045          allocation_chain = {
1046              vk::MemoryAllocateInfo{
1047                  .allocationSize = aligned_size,
1048                  .memoryTypeIndex = memory_type_index.value(),
1049              },
1050              vk::ImportMemoryHostPointerInfoEXT{
1051                  .handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
1052                  .pHostPointer = aligned_pointer,
1053              },
1054          };
1055  
1056      // Import host memory
1057      const vk::UniqueDeviceMemory imported_memory =
1058          device.allocateMemoryUnique(allocation_chain.get());
1059  
1060      const vk::StructureChain<vk::BufferCreateInfo, vk::ExternalMemoryBufferCreateInfo> buffer_info =
1061          {
1062              vk::BufferCreateInfo{
1063                  .size = aligned_size,
1064                  .usage = vk::BufferUsageFlagBits::eTransferDst,
1065                  .sharingMode = vk::SharingMode::eExclusive,
1066              },
1067              vk::ExternalMemoryBufferCreateInfo{
1068                  .handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
1069              },
1070          };
1071  
1072      // Bind imported memory to buffer
1073      const vk::UniqueBuffer imported_buffer = device.createBufferUnique(buffer_info.get());
1074      device.bindBufferMemory(imported_buffer.get(), imported_memory.get(), 0);
1075  
1076      Frame frame{};
1077      main_window.RecreateFrame(&frame, width, height);
1078  
1079      DrawScreens(&frame, layout, false);
1080  
1081      scheduler.Record([buffer_image_copy, source_image = frame.image,
1082                        imported_buffer = imported_buffer.get()](vk::CommandBuffer cmdbuf) {
1083          const vk::ImageMemoryBarrier read_barrier = {
1084              .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
1085              .dstAccessMask = vk::AccessFlagBits::eTransferRead,
1086              .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
1087              .newLayout = vk::ImageLayout::eTransferSrcOptimal,
1088              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1089              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1090              .image = source_image,
1091              .subresourceRange{
1092                  .aspectMask = vk::ImageAspectFlagBits::eColor,
1093                  .baseMipLevel = 0,
1094                  .levelCount = VK_REMAINING_MIP_LEVELS,
1095                  .baseArrayLayer = 0,
1096                  .layerCount = VK_REMAINING_ARRAY_LAYERS,
1097              },
1098          };
1099          const vk::ImageMemoryBarrier write_barrier = {
1100              .srcAccessMask = vk::AccessFlagBits::eTransferRead,
1101              .dstAccessMask = vk::AccessFlagBits::eMemoryWrite,
1102              .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
1103              .newLayout = vk::ImageLayout::eTransferSrcOptimal,
1104              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1105              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1106              .image = source_image,
1107              .subresourceRange{
1108                  .aspectMask = vk::ImageAspectFlagBits::eColor,
1109                  .baseMipLevel = 0,
1110                  .levelCount = VK_REMAINING_MIP_LEVELS,
1111                  .baseArrayLayer = 0,
1112                  .layerCount = VK_REMAINING_ARRAY_LAYERS,
1113              },
1114          };
1115          static constexpr vk::MemoryBarrier memory_write_barrier = {
1116              .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
1117              .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
1118          };
1119  
1120          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
1121                                 vk::PipelineStageFlagBits::eTransfer,
1122                                 vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
1123          cmdbuf.copyImageToBuffer(source_image, vk::ImageLayout::eTransferSrcOptimal,
1124                                   imported_buffer, buffer_image_copy);
1125          cmdbuf.pipelineBarrier(
1126              vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands,
1127              vk::DependencyFlagBits::eByRegion, memory_write_barrier, {}, write_barrier);
1128      });
1129  
1130      // Ensure the copy is fully completed before saving the screenshot
1131      scheduler.Finish();
1132  
1133      // Image data has been copied directly to host memory
1134      device.destroyFramebuffer(frame.framebuffer);
1135      device.destroyImageView(frame.image_view);
1136  
1137      return true;
1138  }
1139  
1140  } // namespace Vulkan