diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index 35924af32..a45ceedc0 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -44,6 +44,8 @@ class GraphicsSystem { ui::Window* target_window); virtual void Shutdown(); + virtual std::unique_ptr Capture() { return nullptr; } + RegisterFile* register_file() { return ®ister_file_; } CommandProcessor* command_processor() const { return command_processor_.get(); diff --git a/src/xenia/gpu/trace_dump.cc b/src/xenia/gpu/trace_dump.cc index 7535e0f01..c45d170cf 100644 --- a/src/xenia/gpu/trace_dump.cc +++ b/src/xenia/gpu/trace_dump.cc @@ -194,7 +194,7 @@ void TraceDump::Run() { loop_->PostDelayed( [&]() { // Capture. - auto raw_image = window_->context()->Capture(); + auto raw_image = graphics_system_->Capture(); if (!raw_image) { // Failed to capture anything. capture_fence.Signal(); diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index 7286adc8c..63477ae8b 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -20,12 +20,16 @@ #include "xenia/gpu/vulkan/vulkan_gpu_flags.h" #include "xenia/ui/vulkan/vulkan_provider.h" #include "xenia/ui/vulkan/vulkan_swap_chain.h" +#include "xenia/ui/vulkan/vulkan_util.h" #include "xenia/ui/window.h" namespace xe { namespace gpu { namespace vulkan { +using xe::ui::vulkan::CheckResult; +using xe::ui::RawImage; + VulkanGraphicsSystem::VulkanGraphicsSystem() {} VulkanGraphicsSystem::~VulkanGraphicsSystem() = default; @@ -42,11 +46,189 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor, display_context_ = reinterpret_cast( target_window->context()); + device_ = display_context_->device(); + + // Create our own command pool we can use for captures. + VkCommandPoolCreateInfo create_info = { + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + device_->queue_family_index(), + }; + auto status = + vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_); + CheckResult(status, "vkCreateCommandPool"); return X_STATUS_SUCCESS; } -void VulkanGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); } +void VulkanGraphicsSystem::Shutdown() { + GraphicsSystem::Shutdown(); + + vkDestroyCommandPool(*device_, command_pool_, nullptr); +} + +std::unique_ptr VulkanGraphicsSystem::Capture() { + auto& swap_state = command_processor_->swap_state(); + std::lock_guard lock(swap_state.mutex); + if (!swap_state.front_buffer_texture) { + return nullptr; + } + + VkResult status = VK_SUCCESS; + + VkCommandBufferAllocateInfo alloc_info = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + nullptr, + command_pool_, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1, + }; + + VkCommandBuffer cmd; + status = vkAllocateCommandBuffers(*device_, &alloc_info, &cmd); + CheckResult(status, "vkAllocateCommandBuffers"); + + VkCommandBufferBeginInfo begin_info = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr, + }; + vkBeginCommandBuffer(cmd, &begin_info); + + auto front_buffer = + reinterpret_cast(swap_state.front_buffer_texture); + + CreateCaptureBuffer(cmd, {swap_state.width, swap_state.height}); + + VkImageMemoryBarrier barrier; + std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier)); + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = front_buffer; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); + + // Copy front buffer into capture image. + VkBufferImageCopy region = { + 0, 0, + 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + {0, 0, 0}, {swap_state.width, swap_state.height, 1}, + }; + + vkCmdCopyImageToBuffer(cmd, front_buffer, VK_IMAGE_LAYOUT_GENERAL, + capture_buffer_, 1, ®ion); + + VkBufferMemoryBarrier memory_barrier = { + VK_STRUCTURE_TYPE_MEMORY_BARRIER, + nullptr, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + capture_buffer_, + 0, + VK_WHOLE_SIZE, + }; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, + &memory_barrier, 0, nullptr); + + vkEndCommandBuffer(cmd); + + // Submit commands and wait. + { + std::lock_guard(device_->primary_queue_mutex()); + VkSubmitInfo submit_info = { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, + 0, + nullptr, + nullptr, + 1, + &cmd, + 0, + nullptr, + }; + status = vkQueueSubmit(device_->primary_queue(), 1, &submit_info, nullptr); + CheckResult(status, "vkQueueSubmit"); + + if (status == VK_SUCCESS) { + status = vkQueueWaitIdle(device_->primary_queue()); + CheckResult(status, "vkQueueWaitIdle"); + } + } + + vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd); + + void* data; + status = + vkMapMemory(*device_, capture_buffer_memory_, 0, VK_WHOLE_SIZE, 0, &data); + CheckResult(status, "vkMapMemory"); + if (status == VK_SUCCESS) { + std::unique_ptr raw_image(new RawImage()); + raw_image->width = swap_state.width; + raw_image->height = swap_state.height; + raw_image->stride = swap_state.width * 4; + raw_image->data.resize(raw_image->stride * raw_image->height); + + std::memcpy(raw_image->data.data(), data, + raw_image->stride * raw_image->height); + + vkUnmapMemory(*device_, capture_buffer_memory_); + DestroyCaptureBuffer(); + return raw_image; + } + + return nullptr; +} + +VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd, + VkExtent2D extents) { + VkBufferCreateInfo buffer_info = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + nullptr, + 0, + extents.width * extents.height * 4, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_SHARING_MODE_EXCLUSIVE, + 0, + nullptr, + }; + auto status = + vkCreateBuffer(*device_, &buffer_info, nullptr, &capture_buffer_); + + capture_buffer_size_ = extents.width * extents.height * 4; + + // Bind memory to buffer. + VkMemoryRequirements mem_requirements; + vkGetBufferMemoryRequirements(*device_, capture_buffer_, &mem_requirements); + capture_buffer_memory_ = + device_->AllocateMemory(mem_requirements, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + assert_not_null(capture_buffer_memory_); + + status = + vkBindBufferMemory(*device_, capture_buffer_, capture_buffer_memory_, 0); + CheckResult(status, "vkBindImageMemory"); + + return VK_SUCCESS; +} + +void VulkanGraphicsSystem::DestroyCaptureBuffer() { + vkDestroyBuffer(*device_, capture_buffer_, nullptr); + vkFreeMemory(*device_, capture_buffer_memory_, nullptr); + capture_buffer_ = nullptr; + capture_buffer_memory_ = nullptr; + capture_buffer_size_ = 0; +} std::unique_ptr VulkanGraphicsSystem::CreateCommandProcessor() { diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.h b/src/xenia/gpu/vulkan/vulkan_graphics_system.h index e486452aa..b9a2341ae 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.h +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.h @@ -28,12 +28,23 @@ class VulkanGraphicsSystem : public GraphicsSystem { ui::Window* target_window) override; void Shutdown() override; - private: - std::unique_ptr CreateCommandProcessor() override; + std::unique_ptr Capture() override; + private: + VkResult CreateCaptureBuffer(VkCommandBuffer cmd, VkExtent2D extents); + void DestroyCaptureBuffer(); + + std::unique_ptr CreateCommandProcessor() override; void Swap(xe::ui::UIEvent* e) override; + xe::ui::vulkan::VulkanDevice* device_ = nullptr; xe::ui::vulkan::VulkanContext* display_context_ = nullptr; + + VkCommandPool command_pool_ = nullptr; + + VkBuffer capture_buffer_ = nullptr; + VkDeviceMemory capture_buffer_memory_ = nullptr; + VkDeviceSize capture_buffer_size_ = 0; }; } // namespace vulkan