[UI] Image post-processing and full presentation/window rework

[GPU] Add FXAA post-processing
[UI] Add FidelityFX FSR and CAS post-processing
[UI] Add blue noise dithering from 10bpc to 8bpc
[GPU] Apply the DC PWL gamma ramp closer to the spec, supporting fully white color
[UI] Allow the GPU CP thread to present on the host directly, bypassing the UI thread OS paint event
[UI] Allow variable refresh rate (or tearing)
[UI] Present the newest frame (restart) on DXGI
[UI] Replace GraphicsContext with a far more advanced Presenter with more coherent surface connection and UI overlay state management
[UI] Connect presentation to windows via the Surface class, not native window handles
[Vulkan] Switch to simpler Vulkan setup with no instance/device separation due to interdependencies and to pass fewer objects around
[Vulkan] Lower the minimum required Vulkan version to 1.0
[UI/GPU] Various cleanup, mainly ComPtr usage
[UI] Support per-monitor DPI awareness v2 on Windows
[UI] DPI-scale Dear ImGui
[UI] Replace the remaining non-detachable window delegates with unified window event and input listeners
[UI] Allow listeners to safely destroy or close the window, and to register/unregister listeners without use-after-free and the ABA problem
[UI] Explicit Z ordering of input listeners and UI overlays, top-down for input, bottom-up for drawing
[UI] Add explicit window lifecycle phases
[UI] Replace Window virtual functions with explicit desired state, its application, actual state, its feedback
[UI] GTK: Apply the initial size to the drawing area
[UI] Limit internal UI frame rate to that of the monitor
[UI] Hide the cursor using a timer instead of polling due to no repeated UI thread paints with GPU CP thread presentation, and only within the window
This commit is contained in:
Triang3l
2022-01-29 13:22:03 +03:00
parent 372bdd3ec9
commit fe3f0f26e4
428 changed files with 75942 additions and 18360 deletions

View File

@@ -2,267 +2,32 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
#include <algorithm>
#include <cstring>
#include "xenia/base/logging.h"
#include "xenia/base/profiling.h"
#include "xenia/cpu/processor.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/gpu/vulkan/vulkan_gpu_flags.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_swap_chain.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/window.h"
#include "xenia/xbox.h"
namespace xe {
namespace gpu {
namespace vulkan {
using xe::ui::RawImage;
using xe::ui::vulkan::CheckResult;
VulkanGraphicsSystem::VulkanGraphicsSystem() {}
VulkanGraphicsSystem::~VulkanGraphicsSystem() = default;
VulkanGraphicsSystem::~VulkanGraphicsSystem() {}
X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
// Must create the provider so we can create contexts.
auto provider = xe::ui::vulkan::VulkanProvider::Create();
device_ = provider->device();
provider_ = std::move(provider);
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
if (result) {
return result;
}
if (target_window) {
display_context_ = reinterpret_cast<xe::ui::vulkan::VulkanContext*>(
target_window->context());
}
// Create our own command pool we can use for captures.
VkCommandPoolCreateInfo create_info = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
nullptr,
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
device_->queue_family_index(),
};
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
auto status =
dfn.vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_);
CheckResult(status, "vkCreateCommandPool");
return X_STATUS_SUCCESS;
}
void VulkanGraphicsSystem::Shutdown() {
GraphicsSystem::Shutdown();
if (command_pool_ != VK_NULL_HANDLE) {
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
dfn.vkDestroyCommandPool(*device_, command_pool_, nullptr);
command_pool_ = VK_NULL_HANDLE;
}
}
std::unique_ptr<RawImage> VulkanGraphicsSystem::Capture() {
auto& swap_state = command_processor_->swap_state();
std::lock_guard<std::mutex> lock(swap_state.mutex);
if (!swap_state.front_buffer_texture) {
return nullptr;
}
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status = VK_SUCCESS;
VkCommandBufferAllocateInfo alloc_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
nullptr,
command_pool_,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1,
};
VkCommandBuffer cmd = nullptr;
status = dfn.vkAllocateCommandBuffers(*device_, &alloc_info, &cmd);
CheckResult(status, "vkAllocateCommandBuffers");
if (status != VK_SUCCESS) {
return nullptr;
}
VkCommandBufferBeginInfo begin_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
nullptr,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
nullptr,
};
dfn.vkBeginCommandBuffer(cmd, &begin_info);
auto front_buffer =
reinterpret_cast<VkImage>(swap_state.front_buffer_texture);
status = CreateCaptureBuffer(cmd, {swap_state.width, swap_state.height});
if (status != VK_SUCCESS) {
dfn.vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd);
return nullptr;
}
VkImageMemoryBarrier barrier;
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = front_buffer;
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
dfn.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
nullptr, 1, &barrier);
// Copy front buffer into capture image.
VkBufferImageCopy region = {
0, 0,
0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{0, 0, 0}, {swap_state.width, swap_state.height, 1},
};
dfn.vkCmdCopyImageToBuffer(cmd, front_buffer, VK_IMAGE_LAYOUT_GENERAL,
capture_buffer_, 1, &region);
VkBufferMemoryBarrier memory_barrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER,
nullptr,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
capture_buffer_,
0,
VK_WHOLE_SIZE,
};
dfn.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr,
1, &memory_barrier, 0, nullptr);
status = dfn.vkEndCommandBuffer(cmd);
// Submit commands and wait.
if (status == VK_SUCCESS) {
std::lock_guard<std::mutex> lock(device_->primary_queue_mutex());
VkSubmitInfo submit_info = {
VK_STRUCTURE_TYPE_SUBMIT_INFO,
nullptr,
0,
nullptr,
nullptr,
1,
&cmd,
0,
nullptr,
};
status =
dfn.vkQueueSubmit(device_->primary_queue(), 1, &submit_info, nullptr);
CheckResult(status, "vkQueueSubmit");
if (status == VK_SUCCESS) {
status = dfn.vkQueueWaitIdle(device_->primary_queue());
CheckResult(status, "vkQueueWaitIdle");
}
}
dfn.vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd);
void* data;
if (status == VK_SUCCESS) {
status = dfn.vkMapMemory(*device_, capture_buffer_memory_, 0, VK_WHOLE_SIZE,
0, &data);
CheckResult(status, "vkMapMemory");
}
if (status == VK_SUCCESS) {
std::unique_ptr<RawImage> raw_image(new RawImage());
raw_image->width = swap_state.width;
raw_image->height = swap_state.height;
raw_image->stride = swap_state.width * 4;
raw_image->data.resize(raw_image->stride * raw_image->height);
std::memcpy(raw_image->data.data(), data,
raw_image->stride * raw_image->height);
dfn.vkUnmapMemory(*device_, capture_buffer_memory_);
DestroyCaptureBuffer();
return raw_image;
}
DestroyCaptureBuffer();
return nullptr;
}
VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd,
VkExtent2D extents) {
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status = VK_SUCCESS;
VkBufferCreateInfo buffer_info = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
nullptr,
0,
extents.width * extents.height * 4,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr,
};
status =
dfn.vkCreateBuffer(*device_, &buffer_info, nullptr, &capture_buffer_);
if (status != VK_SUCCESS) {
return status;
}
capture_buffer_size_ = extents.width * extents.height * 4;
// Bind memory to buffer.
VkMemoryRequirements mem_requirements;
dfn.vkGetBufferMemoryRequirements(*device_, capture_buffer_,
&mem_requirements);
capture_buffer_memory_ = device_->AllocateMemory(
mem_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
assert_not_null(capture_buffer_memory_);
status = dfn.vkBindBufferMemory(*device_, capture_buffer_,
capture_buffer_memory_, 0);
CheckResult(status, "vkBindImageMemory");
if (status != VK_SUCCESS) {
dfn.vkDestroyBuffer(*device_, capture_buffer_, nullptr);
return status;
}
return status;
}
void VulkanGraphicsSystem::DestroyCaptureBuffer() {
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
dfn.vkDestroyBuffer(*device_, capture_buffer_, nullptr);
dfn.vkFreeMemory(*device_, capture_buffer_memory_, nullptr);
capture_buffer_ = nullptr;
capture_buffer_memory_ = nullptr;
capture_buffer_size_ = 0;
ui::WindowedAppContext* app_context,
bool is_surface_required) {
provider_ = xe::ui::vulkan::VulkanProvider::Create(is_surface_required);
return GraphicsSystem::Setup(processor, kernel_state, app_context,
is_surface_required);
}
std::unique_ptr<CommandProcessor>
@@ -270,71 +35,6 @@ VulkanGraphicsSystem::CreateCommandProcessor() {
return std::make_unique<VulkanCommandProcessor>(this, kernel_state_);
}
void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) {
if (!command_processor_ || !display_context_) {
return;
}
// Check for pending swap.
auto& swap_state = command_processor_->swap_state();
if (display_context_->WasLost()) {
// We're crashing. Cheese it.
swap_state.pending = false;
return;
}
{
std::lock_guard<std::mutex> lock(swap_state.mutex);
if (!swap_state.pending) {
// return;
}
swap_state.pending = false;
}
if (!swap_state.front_buffer_texture) {
// Not yet ready.
return;
}
const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn();
auto swap_chain = display_context_->swap_chain();
auto copy_cmd_buffer = swap_chain->copy_cmd_buffer();
auto front_buffer =
reinterpret_cast<VkImage>(swap_state.front_buffer_texture);
VkImageMemoryBarrier barrier;
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = front_buffer;
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
dfn.vkCmdPipelineBarrier(copy_cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &barrier);
VkImageBlit region;
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.srcOffsets[0] = {0, 0, 0};
region.srcOffsets[1] = {static_cast<int32_t>(swap_state.width),
static_cast<int32_t>(swap_state.height), 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstOffsets[0] = {0, 0, 0};
region.dstOffsets[1] = {static_cast<int32_t>(swap_chain->surface_width()),
static_cast<int32_t>(swap_chain->surface_height()),
1};
dfn.vkCmdBlitImage(copy_cmd_buffer, front_buffer, VK_IMAGE_LAYOUT_GENERAL,
swap_chain->surface_image(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region,
VK_FILTER_LINEAR);
}
} // namespace vulkan
} // namespace gpu
} // namespace xe