diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index ca4217b80..f5e1acb01 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -49,7 +49,8 @@ class VulkanRenderer : public QVulkanWindowRenderer { auto swap_state = graphics_system_->swap_state(); auto cmd = window_->currentCommandBuffer(); - auto src = reinterpret_cast(swap_state->front_buffer_texture); + auto src = reinterpret_cast( + swap_state->buffer_textures[swap_state->current_buffer]); auto dest = window_->swapChainImage(window_->currentSwapChainImageIndex()); auto dest_size = window_->swapChainImageSize(); diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index 05d245fd3..56aa8178b 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -28,18 +28,18 @@ class Emulator; namespace xe { namespace gpu { +const uint32_t kNumSwapBuffers = 2; + struct SwapState { // Lock must be held when changing data in this structure. std::mutex mutex; // Dimensions of the framebuffer textures. Should match window size. uint32_t width = 0; uint32_t height = 0; - // Current front buffer, being drawn to the screen. - uintptr_t front_buffer_texture = 0; - // Current back buffer, being updated by the CP. - uintptr_t back_buffer_texture = 0; - // Backend data - void* backend_data = nullptr; + // Array of swap textures. + uintptr_t buffer_textures[kNumSwapBuffers]; + // Current swap buffer index + size_t current_buffer = 0; // Whether the back buffer is dirty and a swap is pending. bool pending = false; }; diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index ec64bc743..9b25a0f8a 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -226,12 +226,25 @@ void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { void VulkanCommandProcessor::BeginFrame() { assert_false(frame_open_); + VkResult status = VK_SUCCESS; + + // Grab a fence from the backbuffer. + auto swap_state = + reinterpret_cast(graphics_system_)->swap_state(); + auto& swap_resources = + swap_state->buffer_resources[(swap_state->current_buffer + 1) % + kNumSwapBuffers]; + + // Wait for the fence, in-case it's still in-use by the GPU. + status = vkWaitForFences(*device_, 1, &swap_resources.buf_fence, VK_TRUE, -1); + vkResetFences(*device_, 1, &swap_resources.buf_fence); // TODO(benvanik): bigger batches. // TODO(DrChat): Decouple setup buffer from current batch. // Begin a new batch, and allocate and begin a command buffer and setup // buffer. - current_batch_fence_ = command_buffer_pool_->BeginBatch(); + current_batch_fence_ = + command_buffer_pool_->BeginBatch(swap_resources.buf_fence); current_command_buffer_ = command_buffer_pool_->AcquireEntry(); current_setup_buffer_ = command_buffer_pool_->AcquireEntry(); @@ -240,7 +253,7 @@ void VulkanCommandProcessor::BeginFrame() { command_buffer_begin_info.pNext = nullptr; command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; command_buffer_begin_info.pInheritanceInfo = nullptr; - auto status = + status = vkBeginCommandBuffer(current_command_buffer_, &command_buffer_begin_info); CheckResult(status, "vkBeginCommandBuffer"); @@ -293,19 +306,33 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_height) { SCOPE_profile_cpu_f("gpu"); + auto swap_state = + reinterpret_cast(graphics_system_)->swap_state(); + // Increment the current buffer. + swap_state->current_buffer = + (swap_state->current_buffer + 1) % kNumSwapBuffers; + + auto& swap_resources = + swap_state->buffer_resources[swap_state->current_buffer]; + // Build a final command buffer that copies the game's frontbuffer texture // into our backbuffer texture. VkCommandBuffer copy_commands = nullptr; - bool opened_batch; - if (command_buffer_pool_->has_open_batch()) { - copy_commands = command_buffer_pool_->AcquireEntry(); - opened_batch = false; - } else { - current_batch_fence_ = command_buffer_pool_->BeginBatch(); - copy_commands = command_buffer_pool_->AcquireEntry(); + bool opened_batch = false; + if (!command_buffer_pool_->has_open_batch()) { + // Wait for the fence, in-case it's still in-use by the GPU. + auto status = + vkWaitForFences(*device_, 1, &swap_resources.buf_fence, VK_TRUE, -1); + vkResetFences(*device_, 1, &swap_resources.buf_fence); + + // We'll have to open a batch if there isn't one open. + current_batch_fence_ = + command_buffer_pool_->BeginBatch(swap_resources.buf_fence); opened_batch = true; } + copy_commands = command_buffer_pool_->AcquireEntry(); + VkCommandBufferBeginInfo begin_info; std::memset(&begin_info, 0, sizeof(begin_info)); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; @@ -318,29 +345,27 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, frontbuffer_ptr = last_copy_base_; } - auto swap_state = - reinterpret_cast(graphics_system_)->swap_state(); - // Create a framebuffer if it isn't already created. - if (!swap_state->fb_framebuffer_) { + if (!swap_resources.buf_framebuffer) { VkFramebufferCreateInfo framebuffer_create_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, blitter_->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, true), 1, - &swap_state->fb_image_view_, + &swap_resources.buf_image_view, swap_state->width, swap_state->height, 1, }; status = vkCreateFramebuffer(*device_, &framebuffer_create_info, nullptr, - &swap_state->fb_framebuffer_); + &swap_resources.buf_framebuffer); CheckResult(status, "vkCreateFramebuffer"); } - auto swap_fb = reinterpret_cast(swap_state->front_buffer_texture); - if (swap_state->fb_image_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) { + auto swap_fb = reinterpret_cast( + swap_state->buffer_textures[swap_state->current_buffer]); + if (swap_resources.buf_image_layout == VK_IMAGE_LAYOUT_UNDEFINED) { // Transition image to general layout. VkImageMemoryBarrier barrier; std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier)); @@ -359,6 +384,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, nullptr, 0, nullptr, 1, &barrier); } + // Grab the game's frontbuffer from a fetch constant populated by VdSwap. auto& regs = *register_file_; int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; auto group = @@ -395,6 +421,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + // Notify the GPU we're gonna write to the swap image. barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; @@ -422,7 +449,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, texture_cache_->DemandView(texture, 0x688)->view, src_rect, {texture->texture_info.width + 1, texture->texture_info.height + 1}, VK_FORMAT_R8G8B8A8_UNORM, dst_rect, - {frontbuffer_width, frontbuffer_height}, swap_state->fb_framebuffer_, + {frontbuffer_width, frontbuffer_height}, swap_resources.buf_framebuffer, viewport, scissor, VK_FILTER_LINEAR, true, true); std::swap(barrier.oldLayout, barrier.newLayout); @@ -485,8 +512,8 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, } } - vkWaitForFences(*device_, 1, ¤t_batch_fence_, VK_TRUE, -1); if (cache_clear_requested_) { + vkWaitForFences(*device_, 1, ¤t_batch_fence_, VK_TRUE, -1); cache_clear_requested_ = false; buffer_cache_->ClearCache(); diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index 91c1979fd..b2392153d 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -33,6 +33,7 @@ VulkanGraphicsSystem::~VulkanGraphicsSystem() = default; X_STATUS VulkanGraphicsSystem::Setup( cpu::Processor* processor, kernel::KernelState* kernel_state, std::unique_ptr context) { + VkResult status; device_ = static_cast(context.get())->device(); auto result = @@ -49,28 +50,33 @@ X_STATUS VulkanGraphicsSystem::Setup( VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, device_->queue_family_index(), }; - auto status = - vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_); + status = vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_); CheckResult(status, "vkCreateCommandPool"); - // TODO(DrChat): Don't hardcode this resolution. - CreateSwapImage({1280, 720}); + for (uint32_t i = 0; i < kNumSwapBuffers; i++) { + // TODO(DrChat): Don't hardcode this resolution. + status = CreateSwapImage({1280, 720}, &swap_state_.buffer_textures[i], + &swap_state_.buffer_resources[i]); + if (status != VK_SUCCESS) { + return X_STATUS_UNSUCCESSFUL; + } + } + return X_STATUS_SUCCESS; } void VulkanGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); - DestroySwapImage(); + for (uint32_t i = 0; i < kNumSwapBuffers; i++) { + DestroySwapImage(&swap_state_.buffer_textures[i], + &swap_state_.buffer_resources[i]); + } VK_SAFE_DESTROY(vkDestroyCommandPool, *device_, command_pool_, nullptr); } std::unique_ptr VulkanGraphicsSystem::Capture() { std::lock_guard lock(swap_state_.mutex); - if (!swap_state_.front_buffer_texture) { - return nullptr; - } - VkResult status = VK_SUCCESS; VkCommandBufferAllocateInfo alloc_info = { @@ -96,8 +102,8 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { }; vkBeginCommandBuffer(cmd, &begin_info); - auto front_buffer = - reinterpret_cast(swap_state_.front_buffer_texture); + auto front_buffer = reinterpret_cast( + swap_state_.buffer_textures[swap_state_.current_buffer]); status = CreateCaptureBuffer(cmd, {swap_state_.width, swap_state_.height}); if (status != VK_SUCCESS) { @@ -246,7 +252,19 @@ void VulkanGraphicsSystem::DestroyCaptureBuffer() { capture_buffer_size_ = 0; } -void VulkanGraphicsSystem::CreateSwapImage(VkExtent2D extents) { +VkResult VulkanGraphicsSystem::CreateSwapImage( + VkExtent2D extents, uintptr_t* image_out, + SwapState::BufferResources* buffer_resources) { + VkResult status; + + VkFenceCreateInfo fence_info = { + VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + nullptr, + VK_FENCE_CREATE_SIGNALED_BIT, + }; + status = vkCreateFence(*device_, &fence_info, nullptr, + &buffer_resources->buf_fence); + VkImageCreateInfo image_info; std::memset(&image_info, 0, sizeof(VkImageCreateInfo)); image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; @@ -264,29 +282,37 @@ void VulkanGraphicsSystem::CreateSwapImage(VkExtent2D extents) { image_info.pQueueFamilyIndices = nullptr; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VkImage image_fb; - auto status = vkCreateImage(*device_, &image_info, nullptr, &image_fb); + VkImage image_buf; + status = vkCreateImage(*device_, &image_info, nullptr, &image_buf); CheckResult(status, "vkCreateImage"); + if (status != VK_SUCCESS) { + return status; + } // Bind memory to image. VkMemoryRequirements mem_requirements; - vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements); - swap_state_.fb_memory_ = device_->AllocateMemory(mem_requirements, 0); - assert_not_null(swap_state_.fb_memory_); + vkGetImageMemoryRequirements(*device_, image_buf, &mem_requirements); + buffer_resources->buf_memory = device_->AllocateMemory(mem_requirements, 0); + assert_not_null(buffer_resources->buf_memory); - status = vkBindImageMemory(*device_, image_fb, swap_state_.fb_memory_, 0); + status = + vkBindImageMemory(*device_, image_buf, buffer_resources->buf_memory, 0); CheckResult(status, "vkBindImageMemory"); + if (status != VK_SUCCESS) { + return status; + } std::lock_guard lock(swap_state_.mutex); - swap_state_.front_buffer_texture = reinterpret_cast(image_fb); + *image_out = reinterpret_cast(image_buf); swap_state_.width = extents.width; swap_state_.height = extents.height; + buffer_resources->buf_image_layout = image_info.initialLayout; VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, nullptr, 0, - image_fb, + image_buf, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, @@ -294,25 +320,29 @@ void VulkanGraphicsSystem::CreateSwapImage(VkExtent2D extents) { {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, }; status = vkCreateImageView(*device_, &view_create_info, nullptr, - &swap_state_.fb_image_view_); + &buffer_resources->buf_image_view); CheckResult(status, "vkCreateImageView"); -} - -void VulkanGraphicsSystem::DestroySwapImage() { - VK_SAFE_DESTROY(vkDestroyFramebuffer, *device_, swap_state_.fb_framebuffer_, - nullptr); - VK_SAFE_DESTROY(vkDestroyImageView, *device_, swap_state_.fb_image_view_, - nullptr); - - std::lock_guard lock(swap_state_.mutex); - VK_SAFE_DESTROY(vkFreeMemory, *device_, swap_state_.fb_memory_, nullptr); - if (swap_state_.front_buffer_texture) { - vkDestroyImage(*device_, - reinterpret_cast(swap_state_.front_buffer_texture), - nullptr); + if (status != VK_SUCCESS) { + return status; } - swap_state_.front_buffer_texture = 0; + return VK_SUCCESS; +} + +void VulkanGraphicsSystem::DestroySwapImage( + uintptr_t* image, SwapState::BufferResources* buffer_resources) { + VK_SAFE_DESTROY(vkDestroyFence, *device_, buffer_resources->buf_fence, + nullptr); + VK_SAFE_DESTROY(vkDestroyFramebuffer, *device_, + buffer_resources->buf_framebuffer, nullptr); + VK_SAFE_DESTROY(vkDestroyImageView, *device_, + buffer_resources->buf_image_view, nullptr); + + VK_SAFE_DESTROY(vkFreeMemory, *device_, buffer_resources->buf_memory, + nullptr); + if (image) { + vkDestroyImage(*device_, reinterpret_cast(image), nullptr); + } } std::unique_ptr diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.h b/src/xenia/gpu/vulkan/vulkan_graphics_system.h index 0462be017..8c6ae7a82 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.h +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.h @@ -21,10 +21,13 @@ namespace vulkan { struct SwapState : public gpu::SwapState { // front buffer / back buffer memory - VkDeviceMemory fb_memory_ = nullptr; - VkImageView fb_image_view_ = nullptr; - VkImageLayout fb_image_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; - VkFramebuffer fb_framebuffer_ = nullptr; // Used and created by CP. + struct BufferResources { + VkDeviceMemory buf_memory = nullptr; + VkImageView buf_image_view = nullptr; + VkImageLayout buf_image_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkFramebuffer buf_framebuffer = nullptr; // Used and created by CP. + VkFence buf_fence = nullptr; // Completion fence. Used by CP. + } buffer_resources[kNumSwapBuffers]; }; class VulkanGraphicsSystem : public GraphicsSystem { @@ -45,8 +48,10 @@ class VulkanGraphicsSystem : public GraphicsSystem { VkResult CreateCaptureBuffer(VkCommandBuffer cmd, VkExtent2D extents); void DestroyCaptureBuffer(); - void CreateSwapImage(VkExtent2D extents); - void DestroySwapImage(); + VkResult CreateSwapImage(VkExtent2D extents, uintptr_t* image_out, + SwapState::BufferResources* buffer_resources); + void DestroySwapImage(uintptr_t* image, + SwapState::BufferResources* buffer_resources); std::unique_ptr CreateCommandProcessor() override;