[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 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();

View File

@ -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;
};

View File

@ -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();

View File

@ -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");
}
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);
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<VkImage>(image), nullptr);
}
}
std::unique_ptr<CommandProcessor>

View File

@ -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;