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