[Vulkan] Use a swap chain for the backend

This commit is contained in:
Dr. Chat 2018-06-12 12:01:19 -05:00
parent 3fcdbefce8
commit e309e6ce6d
5 changed files with 131 additions and 68 deletions

View File

@ -49,7 +49,8 @@ class VulkanRenderer : public QVulkanWindowRenderer {
auto swap_state = graphics_system_->swap_state(); auto swap_state = graphics_system_->swap_state();
auto cmd = window_->currentCommandBuffer(); 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 = window_->swapChainImage(window_->currentSwapChainImageIndex());
auto dest_size = window_->swapChainImageSize(); auto dest_size = window_->swapChainImageSize();

View File

@ -28,18 +28,18 @@ class Emulator;
namespace xe { namespace xe {
namespace gpu { namespace gpu {
const uint32_t kNumSwapBuffers = 2;
struct SwapState { struct SwapState {
// Lock must be held when changing data in this structure. // Lock must be held when changing data in this structure.
std::mutex mutex; std::mutex mutex;
// Dimensions of the framebuffer textures. Should match window size. // Dimensions of the framebuffer textures. Should match window size.
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
// Current front buffer, being drawn to the screen. // Array of swap textures.
uintptr_t front_buffer_texture = 0; uintptr_t buffer_textures[kNumSwapBuffers];
// Current back buffer, being updated by the CP. // Current swap buffer index
uintptr_t back_buffer_texture = 0; size_t current_buffer = 0;
// Backend data
void* backend_data = nullptr;
// Whether the back buffer is dirty and a swap is pending. // Whether the back buffer is dirty and a swap is pending.
bool pending = false; bool pending = false;
}; };

View File

@ -226,12 +226,25 @@ void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) {
void VulkanCommandProcessor::BeginFrame() { void VulkanCommandProcessor::BeginFrame() {
assert_false(frame_open_); 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(benvanik): bigger batches.
// TODO(DrChat): Decouple setup buffer from current batch. // TODO(DrChat): Decouple setup buffer from current batch.
// Begin a new batch, and allocate and begin a command buffer and setup // Begin a new batch, and allocate and begin a command buffer and setup
// buffer. // 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_command_buffer_ = command_buffer_pool_->AcquireEntry();
current_setup_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.pNext = nullptr;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
command_buffer_begin_info.pInheritanceInfo = nullptr; command_buffer_begin_info.pInheritanceInfo = nullptr;
auto status = status =
vkBeginCommandBuffer(current_command_buffer_, &command_buffer_begin_info); vkBeginCommandBuffer(current_command_buffer_, &command_buffer_begin_info);
CheckResult(status, "vkBeginCommandBuffer"); CheckResult(status, "vkBeginCommandBuffer");
@ -293,19 +306,33 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
uint32_t frontbuffer_height) { uint32_t frontbuffer_height) {
SCOPE_profile_cpu_f("gpu"); 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 // Build a final command buffer that copies the game's frontbuffer texture
// into our backbuffer texture. // into our backbuffer texture.
VkCommandBuffer copy_commands = nullptr; VkCommandBuffer copy_commands = nullptr;
bool opened_batch; bool opened_batch = false;
if (command_buffer_pool_->has_open_batch()) { if (!command_buffer_pool_->has_open_batch()) {
copy_commands = command_buffer_pool_->AcquireEntry(); // Wait for the fence, in-case it's still in-use by the GPU.
opened_batch = false; auto status =
} else { vkWaitForFences(*device_, 1, &swap_resources.buf_fence, VK_TRUE, -1);
current_batch_fence_ = command_buffer_pool_->BeginBatch(); vkResetFences(*device_, 1, &swap_resources.buf_fence);
copy_commands = command_buffer_pool_->AcquireEntry();
// 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; opened_batch = true;
} }
copy_commands = command_buffer_pool_->AcquireEntry();
VkCommandBufferBeginInfo begin_info; VkCommandBufferBeginInfo begin_info;
std::memset(&begin_info, 0, sizeof(begin_info)); std::memset(&begin_info, 0, sizeof(begin_info));
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_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_; frontbuffer_ptr = last_copy_base_;
} }
auto swap_state =
reinterpret_cast<VulkanGraphicsSystem*>(graphics_system_)->swap_state();
// Create a framebuffer if it isn't already created. // Create a framebuffer if it isn't already created.
if (!swap_state->fb_framebuffer_) { if (!swap_resources.buf_framebuffer) {
VkFramebufferCreateInfo framebuffer_create_info = { VkFramebufferCreateInfo framebuffer_create_info = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
nullptr, nullptr,
0, 0,
blitter_->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, true), blitter_->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, true),
1, 1,
&swap_state->fb_image_view_, &swap_resources.buf_image_view,
swap_state->width, swap_state->width,
swap_state->height, swap_state->height,
1, 1,
}; };
status = vkCreateFramebuffer(*device_, &framebuffer_create_info, nullptr, status = vkCreateFramebuffer(*device_, &framebuffer_create_info, nullptr,
&swap_state->fb_framebuffer_); &swap_resources.buf_framebuffer);
CheckResult(status, "vkCreateFramebuffer"); CheckResult(status, "vkCreateFramebuffer");
} }
auto swap_fb = reinterpret_cast<VkImage>(swap_state->front_buffer_texture); auto swap_fb = reinterpret_cast<VkImage>(
if (swap_state->fb_image_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) { swap_state->buffer_textures[swap_state->current_buffer]);
if (swap_resources.buf_image_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
// Transition image to general layout. // Transition image to general layout.
VkImageMemoryBarrier barrier; VkImageMemoryBarrier barrier;
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier)); std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
@ -359,6 +384,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
nullptr, 0, nullptr, 1, &barrier); nullptr, 0, nullptr, 1, &barrier);
} }
// Grab the game's frontbuffer from a fetch constant populated by VdSwap.
auto& regs = *register_file_; auto& regs = *register_file_;
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0;
auto group = auto group =
@ -395,6 +421,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier); 0, nullptr, 1, &barrier);
// Notify the GPU we're gonna write to the swap image.
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 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_cache_->DemandView(texture, 0x688)->view, src_rect,
{texture->texture_info.width + 1, texture->texture_info.height + 1}, {texture->texture_info.width + 1, texture->texture_info.height + 1},
VK_FORMAT_R8G8B8A8_UNORM, dst_rect, 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); viewport, scissor, VK_FILTER_LINEAR, true, true);
std::swap(barrier.oldLayout, barrier.newLayout); 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_) { if (cache_clear_requested_) {
vkWaitForFences(*device_, 1, &current_batch_fence_, VK_TRUE, -1);
cache_clear_requested_ = false; cache_clear_requested_ = false;
buffer_cache_->ClearCache(); buffer_cache_->ClearCache();

View File

@ -33,6 +33,7 @@ VulkanGraphicsSystem::~VulkanGraphicsSystem() = default;
X_STATUS VulkanGraphicsSystem::Setup( X_STATUS VulkanGraphicsSystem::Setup(
cpu::Processor* processor, kernel::KernelState* kernel_state, cpu::Processor* processor, kernel::KernelState* kernel_state,
std::unique_ptr<ui::GraphicsContext> context) { std::unique_ptr<ui::GraphicsContext> context) {
VkResult status;
device_ = static_cast<ui::vulkan::VulkanContext*>(context.get())->device(); device_ = static_cast<ui::vulkan::VulkanContext*>(context.get())->device();
auto result = auto result =
@ -49,28 +50,33 @@ X_STATUS VulkanGraphicsSystem::Setup(
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
device_->queue_family_index(), device_->queue_family_index(),
}; };
auto status = status = vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_);
vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_);
CheckResult(status, "vkCreateCommandPool"); CheckResult(status, "vkCreateCommandPool");
// TODO(DrChat): Don't hardcode this resolution. for (uint32_t i = 0; i < kNumSwapBuffers; i++) {
CreateSwapImage({1280, 720}); // 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; return X_STATUS_SUCCESS;
} }
void VulkanGraphicsSystem::Shutdown() { void VulkanGraphicsSystem::Shutdown() {
GraphicsSystem::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); VK_SAFE_DESTROY(vkDestroyCommandPool, *device_, command_pool_, nullptr);
} }
std::unique_ptr<RawImage> VulkanGraphicsSystem::Capture() { std::unique_ptr<RawImage> VulkanGraphicsSystem::Capture() {
std::lock_guard<std::mutex> lock(swap_state_.mutex); std::lock_guard<std::mutex> lock(swap_state_.mutex);
if (!swap_state_.front_buffer_texture) {
return nullptr;
}
VkResult status = VK_SUCCESS; VkResult status = VK_SUCCESS;
VkCommandBufferAllocateInfo alloc_info = { VkCommandBufferAllocateInfo alloc_info = {
@ -96,8 +102,8 @@ std::unique_ptr<RawImage> VulkanGraphicsSystem::Capture() {
}; };
vkBeginCommandBuffer(cmd, &begin_info); vkBeginCommandBuffer(cmd, &begin_info);
auto front_buffer = auto front_buffer = reinterpret_cast<VkImage>(
reinterpret_cast<VkImage>(swap_state_.front_buffer_texture); swap_state_.buffer_textures[swap_state_.current_buffer]);
status = CreateCaptureBuffer(cmd, {swap_state_.width, swap_state_.height}); status = CreateCaptureBuffer(cmd, {swap_state_.width, swap_state_.height});
if (status != VK_SUCCESS) { if (status != VK_SUCCESS) {
@ -246,7 +252,19 @@ void VulkanGraphicsSystem::DestroyCaptureBuffer() {
capture_buffer_size_ = 0; 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; VkImageCreateInfo image_info;
std::memset(&image_info, 0, sizeof(VkImageCreateInfo)); std::memset(&image_info, 0, sizeof(VkImageCreateInfo));
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
@ -264,29 +282,37 @@ void VulkanGraphicsSystem::CreateSwapImage(VkExtent2D extents) {
image_info.pQueueFamilyIndices = nullptr; image_info.pQueueFamilyIndices = nullptr;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImage image_fb; VkImage image_buf;
auto status = vkCreateImage(*device_, &image_info, nullptr, &image_fb); status = vkCreateImage(*device_, &image_info, nullptr, &image_buf);
CheckResult(status, "vkCreateImage"); CheckResult(status, "vkCreateImage");
if (status != VK_SUCCESS) {
return status;
}
// Bind memory to image. // Bind memory to image.
VkMemoryRequirements mem_requirements; VkMemoryRequirements mem_requirements;
vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements); vkGetImageMemoryRequirements(*device_, image_buf, &mem_requirements);
swap_state_.fb_memory_ = device_->AllocateMemory(mem_requirements, 0); buffer_resources->buf_memory = device_->AllocateMemory(mem_requirements, 0);
assert_not_null(swap_state_.fb_memory_); 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"); CheckResult(status, "vkBindImageMemory");
if (status != VK_SUCCESS) {
return status;
}
std::lock_guard<std::mutex> lock(swap_state_.mutex); 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_.width = extents.width;
swap_state_.height = extents.height; swap_state_.height = extents.height;
buffer_resources->buf_image_layout = image_info.initialLayout;
VkImageViewCreateInfo view_create_info = { VkImageViewCreateInfo view_create_info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
nullptr, nullptr,
0, 0,
image_fb, image_buf,
VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_VIEW_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM,
{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, {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}, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1},
}; };
status = vkCreateImageView(*device_, &view_create_info, nullptr, status = vkCreateImageView(*device_, &view_create_info, nullptr,
&swap_state_.fb_image_view_); &buffer_resources->buf_image_view);
CheckResult(status, "vkCreateImageView"); CheckResult(status, "vkCreateImageView");
} if (status != VK_SUCCESS) {
return status;
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<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);
} }
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<VkImage>(image), nullptr);
}
} }
std::unique_ptr<CommandProcessor> std::unique_ptr<CommandProcessor>

View File

@ -21,10 +21,13 @@ namespace vulkan {
struct SwapState : public gpu::SwapState { struct SwapState : public gpu::SwapState {
// front buffer / back buffer memory // front buffer / back buffer memory
VkDeviceMemory fb_memory_ = nullptr; struct BufferResources {
VkImageView fb_image_view_ = nullptr; VkDeviceMemory buf_memory = nullptr;
VkImageLayout fb_image_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; VkImageView buf_image_view = nullptr;
VkFramebuffer fb_framebuffer_ = nullptr; // Used and created by CP. 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 { class VulkanGraphicsSystem : public GraphicsSystem {
@ -45,8 +48,10 @@ class VulkanGraphicsSystem : public GraphicsSystem {
VkResult CreateCaptureBuffer(VkCommandBuffer cmd, VkExtent2D extents); VkResult CreateCaptureBuffer(VkCommandBuffer cmd, VkExtent2D extents);
void DestroyCaptureBuffer(); void DestroyCaptureBuffer();
void CreateSwapImage(VkExtent2D extents); VkResult CreateSwapImage(VkExtent2D extents, uintptr_t* image_out,
void DestroySwapImage(); SwapState::BufferResources* buffer_resources);
void DestroySwapImage(uintptr_t* image,
SwapState::BufferResources* buffer_resources);
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override; std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;