summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDr. Chat <arkolbed@gmail.com>2018-06-12 12:01:19 -0500
committerDr. Chat <arkolbed@gmail.com>2018-10-12 14:54:57 -0500
commite309e6ce6dc307fe7a366f6b201ffe70e905fbce (patch)
tree8b2ceeb1241392100bc9002c59cdc5e7a9d5274b
parent3fcdbefce8d2d1abfeec9e16b7f14a9f1ee51510 (diff)
[Vulkan] Use a swap chain for the backend
-rw-r--r--src/xenia/app/emulator_window.cc3
-rw-r--r--src/xenia/gpu/graphics_system.h12
-rw-r--r--src/xenia/gpu/vulkan/vulkan_command_processor.cc65
-rw-r--r--src/xenia/gpu/vulkan/vulkan_graphics_system.cc96
-rw-r--r--src/xenia/gpu/vulkan/vulkan_graphics_system.h17
5 files changed, 128 insertions, 65 deletions
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<VkImage>(swap_state->front_buffer_texture);
+ auto src = reinterpret_cast<VkImage>(
+ 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<VulkanGraphicsSystem*>(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<VulkanGraphicsSystem*>(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<VulkanGraphicsSystem*>(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<VkImage>(swap_state->front_buffer_texture);
- if (swap_state->fb_image_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) {
+ auto swap_fb = reinterpret_cast<VkImage>(
+ 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, &current_batch_fence_, VK_TRUE, -1);
if (cache_clear_requested_) {
+ vkWaitForFences(*device_, 1, &current_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<ui::GraphicsContext> context) {
+ VkResult status;
device_ = static_cast<ui::vulkan::VulkanContext*>(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<RawImage> VulkanGraphicsSystem::Capture() {
std::lock_guard<std::mutex> 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<RawImage> VulkanGraphicsSystem::Capture() {
};
vkBeginCommandBuffer(cmd, &begin_info);
- auto front_buffer =
- reinterpret_cast<VkImage>(swap_state_.front_buffer_texture);
+ auto front_buffer = reinterpret_cast<VkImage>(
+ 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<std::mutex> lock(swap_state_.mutex);
- swap_state_.front_buffer_texture = reinterpret_cast<uintptr_t>(image_fb);
+ *image_out = reinterpret_cast<uintptr_t>(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");
+ if (status != VK_SUCCESS) {
+ return status;
+ }
+
+ return VK_SUCCESS;
}
-void VulkanGraphicsSystem::DestroySwapImage() {
- VK_SAFE_DESTROY(vkDestroyFramebuffer, *device_, swap_state_.fb_framebuffer_,
- nullptr);
- VK_SAFE_DESTROY(vkDestroyImageView, *device_, swap_state_.fb_image_view_,
+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);
- std::lock_guard<std::mutex> lock(swap_state_.mutex);
- VK_SAFE_DESTROY(vkFreeMemory, *device_, swap_state_.fb_memory_, nullptr);
- if (swap_state_.front_buffer_texture) {
- vkDestroyImage(*device_,
- reinterpret_cast<VkImage>(swap_state_.front_buffer_texture),
- nullptr);
+ VK_SAFE_DESTROY(vkFreeMemory, *device_, buffer_resources->buf_memory,
+ nullptr);
+ if (image) {
+ vkDestroyImage(*device_, reinterpret_cast<VkImage>(image), nullptr);
}
-
- swap_state_.front_buffer_texture = 0;
}
std::unique_ptr<CommandProcessor>
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<CommandProcessor> CreateCommandProcessor() override;