[Vulkan] Use a swap chain for the backend
This commit is contained in:
parent
3fcdbefce8
commit
e309e6ce6d
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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, ¤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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue