[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

@@ -10,23 +10,25 @@
#include "xenia/ui/vulkan/blitter.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/fenced_pools.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
// Generated with `xenia-build genspirv`.
#include "xenia/ui/vulkan/shaders/bin/blit_color_frag.h"
#include "xenia/ui/vulkan/shaders/bin/blit_depth_frag.h"
#include "xenia/ui/vulkan/shaders/bin/blit_vert.h"
using util::CheckResult;
Blitter::Blitter() {}
// Generated with `xenia-build genspirv`.
#include "xenia/ui/vulkan/shaders/bytecode/vulkan_spirv/blit_color_frag.h"
#include "xenia/ui/vulkan/shaders/bytecode/vulkan_spirv/blit_depth_frag.h"
#include "xenia/ui/vulkan/shaders/bytecode/vulkan_spirv/blit_vert.h"
Blitter::Blitter(const VulkanProvider& provider) : provider_(provider) {}
Blitter::~Blitter() { Shutdown(); }
VkResult Blitter::Initialize(VulkanDevice* device) {
device_ = device;
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult Blitter::Initialize() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
// Shaders
@@ -35,39 +37,36 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
shader_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_create_info.codeSize = sizeof(blit_vert);
shader_create_info.pCode = reinterpret_cast<const uint32_t*>(blit_vert);
status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr,
status = dfn.vkCreateShaderModule(device, &shader_create_info, nullptr,
&blit_vertex_);
CheckResult(status, "vkCreateShaderModule");
if (status != VK_SUCCESS) {
return status;
}
device_->DbgSetObjectName(uint64_t(blit_vertex_),
VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT,
"S(B): Vertex");
provider_.SetDeviceObjectName(VK_OBJECT_TYPE_SHADER_MODULE,
uint64_t(blit_vertex_), "S(B): Vertex");
shader_create_info.codeSize = sizeof(blit_color_frag);
shader_create_info.pCode = reinterpret_cast<const uint32_t*>(blit_color_frag);
status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr,
status = dfn.vkCreateShaderModule(device, &shader_create_info, nullptr,
&blit_color_);
CheckResult(status, "vkCreateShaderModule");
if (status != VK_SUCCESS) {
return status;
}
device_->DbgSetObjectName(uint64_t(blit_color_),
VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT,
"S(B): Color");
provider_.SetDeviceObjectName(VK_OBJECT_TYPE_SHADER_MODULE,
uint64_t(blit_color_), "S(B): Color");
shader_create_info.codeSize = sizeof(blit_depth_frag);
shader_create_info.pCode = reinterpret_cast<const uint32_t*>(blit_depth_frag);
status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr,
status = dfn.vkCreateShaderModule(device, &shader_create_info, nullptr,
&blit_depth_);
CheckResult(status, "vkCreateShaderModule");
if (status != VK_SUCCESS) {
return status;
}
device_->DbgSetObjectName(uint64_t(blit_depth_),
VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT,
"S(B): Depth");
provider_.SetDeviceObjectName(VK_OBJECT_TYPE_SHADER_MODULE,
uint64_t(blit_depth_), "S(B): Depth");
// Create the descriptor set layout used for our texture sampler.
// As it changes almost every draw we cache it per texture.
@@ -84,7 +83,7 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
texture_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
texture_binding.pImmutableSamplers = nullptr;
texture_set_layout_info.pBindings = &texture_binding;
status = dfn.vkCreateDescriptorSetLayout(*device_, &texture_set_layout_info,
status = dfn.vkCreateDescriptorSetLayout(device, &texture_set_layout_info,
nullptr, &descriptor_set_layout_);
CheckResult(status, "vkCreateDescriptorSetLayout");
if (status != VK_SUCCESS) {
@@ -96,7 +95,7 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
pool_sizes[0].descriptorCount = 4096;
pool_sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptor_pool_ = std::make_unique<DescriptorPool>(
*device_, 4096,
provider_, 4096,
std::vector<VkDescriptorPoolSize>(pool_sizes, std::end(pool_sizes)));
// Create the pipeline layout used for our pipeline.
@@ -120,7 +119,7 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
pipeline_layout_info.pushConstantRangeCount =
static_cast<uint32_t>(xe::countof(push_constant_ranges));
pipeline_layout_info.pPushConstantRanges = push_constant_ranges;
status = dfn.vkCreatePipelineLayout(*device_, &pipeline_layout_info, nullptr,
status = dfn.vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr,
&pipeline_layout_);
CheckResult(status, "vkCreatePipelineLayout");
if (status != VK_SUCCESS) {
@@ -148,7 +147,7 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
VK_BORDER_COLOR_INT_TRANSPARENT_BLACK,
VK_FALSE,
};
status = dfn.vkCreateSampler(*device_, &sampler_create_info, nullptr,
status = dfn.vkCreateSampler(device, &sampler_create_info, nullptr,
&samp_nearest_);
CheckResult(status, "vkCreateSampler");
if (status != VK_SUCCESS) {
@@ -158,8 +157,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
sampler_create_info.minFilter = VK_FILTER_LINEAR;
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
status = dfn.vkCreateSampler(*device_, &sampler_create_info, nullptr,
&samp_linear_);
status =
dfn.vkCreateSampler(device, &sampler_create_info, nullptr, &samp_linear_);
CheckResult(status, "vkCreateSampler");
if (status != VK_SUCCESS) {
return status;
@@ -169,50 +168,26 @@ VkResult Blitter::Initialize(VulkanDevice* device) {
}
void Blitter::Shutdown() {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
if (samp_nearest_) {
dfn.vkDestroySampler(*device_, samp_nearest_, nullptr);
samp_nearest_ = nullptr;
}
if (samp_linear_) {
dfn.vkDestroySampler(*device_, samp_linear_, nullptr);
samp_linear_ = nullptr;
}
if (blit_vertex_) {
dfn.vkDestroyShaderModule(*device_, blit_vertex_, nullptr);
blit_vertex_ = nullptr;
}
if (blit_color_) {
dfn.vkDestroyShaderModule(*device_, blit_color_, nullptr);
blit_color_ = nullptr;
}
if (blit_depth_) {
dfn.vkDestroyShaderModule(*device_, blit_depth_, nullptr);
blit_depth_ = nullptr;
}
if (pipeline_color_) {
dfn.vkDestroyPipeline(*device_, pipeline_color_, nullptr);
pipeline_color_ = nullptr;
}
if (pipeline_depth_) {
dfn.vkDestroyPipeline(*device_, pipeline_depth_, nullptr);
pipeline_depth_ = nullptr;
}
if (pipeline_layout_) {
dfn.vkDestroyPipelineLayout(*device_, pipeline_layout_, nullptr);
pipeline_layout_ = nullptr;
}
if (descriptor_set_layout_) {
dfn.vkDestroyDescriptorSetLayout(*device_, descriptor_set_layout_, nullptr);
descriptor_set_layout_ = nullptr;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
util::DestroyAndNullHandle(dfn.vkDestroySampler, device, samp_nearest_);
util::DestroyAndNullHandle(dfn.vkDestroySampler, device, samp_linear_);
util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, blit_vertex_);
util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, blit_color_);
util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, blit_depth_);
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_color_);
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_depth_);
util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device,
pipeline_layout_);
util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, device,
descriptor_set_layout_);
for (auto& pipeline : pipelines_) {
dfn.vkDestroyPipeline(*device_, pipeline.second, nullptr);
dfn.vkDestroyPipeline(device, pipeline.second, nullptr);
}
pipelines_.clear();
for (auto& pass : render_passes_) {
dfn.vkDestroyRenderPass(*device_, pass.second, nullptr);
dfn.vkDestroyRenderPass(device, pass.second, nullptr);
}
render_passes_.clear();
}
@@ -232,7 +207,8 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence,
VkFramebuffer dst_framebuffer, VkViewport viewport,
VkRect2D scissor, VkFilter filter,
bool color_or_depth, bool swap_channels) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
// Do we need a full draw, or can we cheap out with a blit command?
bool full_draw = swap_channels || true;
@@ -291,7 +267,7 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence,
write.pImageInfo = &image;
write.pBufferInfo = nullptr;
write.pTexelBufferView = nullptr;
dfn.vkUpdateDescriptorSets(*device_, 1, &write, 0, nullptr);
dfn.vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
dfn.vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline_layout_, 0, 1, &set, 0, nullptr);
@@ -425,9 +401,10 @@ VkRenderPass Blitter::CreateRenderPass(VkFormat output_format,
nullptr,
};
VkRenderPass renderpass = nullptr;
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult result =
dfn.vkCreateRenderPass(*device_, &renderpass_info, nullptr, &renderpass);
dfn.vkCreateRenderPass(device, &renderpass_info, nullptr, &renderpass);
CheckResult(result, "vkCreateRenderPass");
return renderpass;
@@ -436,7 +413,8 @@ VkRenderPass Blitter::CreateRenderPass(VkFormat output_format,
VkPipeline Blitter::CreatePipeline(VkRenderPass render_pass,
VkShaderModule frag_shader,
bool color_or_depth) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult result = VK_SUCCESS;
// Pipeline
@@ -582,7 +560,7 @@ VkPipeline Blitter::CreatePipeline(VkRenderPass render_pass,
pipeline_info.basePipelineIndex = -1;
VkPipeline pipeline = nullptr;
result = dfn.vkCreateGraphicsPipelines(*device_, nullptr, 1, &pipeline_info,
result = dfn.vkCreateGraphicsPipelines(device, nullptr, 1, &pipeline_info,
nullptr, &pipeline);
CheckResult(result, "vkCreateGraphicsPipelines");

View File

@@ -13,8 +13,7 @@
#include <map>
#include <memory>
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
@@ -24,10 +23,10 @@ class DescriptorPool;
class Blitter {
public:
Blitter();
Blitter(const VulkanProvider& provider);
~Blitter();
VkResult Initialize(VulkanDevice* device);
VkResult Initialize();
void Scavenge();
void Shutdown();
@@ -79,7 +78,7 @@ class Blitter {
VkShaderModule frag_shader, bool color_or_depth);
std::unique_ptr<DescriptorPool> descriptor_pool_ = nullptr;
VulkanDevice* device_ = nullptr;
const VulkanProvider& provider_;
VkPipeline pipeline_color_ = nullptr;
VkPipeline pipeline_depth_ = nullptr;
VkPipelineLayout pipeline_layout_ = nullptr;

View File

@@ -7,22 +7,27 @@
******************************************************************************
*/
#include "xenia/ui/vulkan/circular_buffer.h"
#include <algorithm>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/circular_buffer.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
CircularBuffer::CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage,
VkDeviceSize capacity, VkDeviceSize alignment)
: device_(device), capacity_(capacity) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
using util::CheckResult;
CircularBuffer::CircularBuffer(const VulkanProvider& provider,
VkBufferUsageFlags usage, VkDeviceSize capacity,
VkDeviceSize alignment)
: provider_(provider), capacity_(capacity) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
// Create our internal buffer.
@@ -35,14 +40,14 @@ CircularBuffer::CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage,
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_info.queueFamilyIndexCount = 0;
buffer_info.pQueueFamilyIndices = nullptr;
status = dfn.vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_);
status = dfn.vkCreateBuffer(device, &buffer_info, nullptr, &gpu_buffer_);
CheckResult(status, "vkCreateBuffer");
if (status != VK_SUCCESS) {
assert_always();
}
VkMemoryRequirements reqs;
dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs);
dfn.vkGetBufferMemoryRequirements(device, gpu_buffer_, &reqs);
alignment_ = xe::round_up(alignment, reqs.alignment);
}
CircularBuffer::~CircularBuffer() { Shutdown(); }
@@ -53,12 +58,12 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory,
gpu_memory_ = memory;
gpu_base_ = offset;
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
// Bind the buffer to its backing memory.
status =
dfn.vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_);
status = dfn.vkBindBufferMemory(device, gpu_buffer_, gpu_memory_, gpu_base_);
CheckResult(status, "vkBindBufferMemory");
if (status != VK_SUCCESS) {
XELOGE("CircularBuffer::Initialize - Failed to bind memory!");
@@ -67,7 +72,7 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory,
}
// Map the memory so we can access it.
status = dfn.vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0,
status = dfn.vkMapMemory(device, gpu_memory_, gpu_base_, capacity_, 0,
reinterpret_cast<void**>(&host_base_));
CheckResult(status, "vkMapMemory");
if (status != VK_SUCCESS) {
@@ -80,27 +85,39 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory,
}
VkResult CircularBuffer::Initialize() {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
VkMemoryRequirements reqs;
dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs);
dfn.vkGetBufferMemoryRequirements(device, gpu_buffer_, &reqs);
// Allocate memory from the device to back the buffer.
owns_gpu_memory_ = true;
gpu_memory_ = device_->AllocateMemory(reqs);
if (!gpu_memory_) {
XELOGE("CircularBuffer::Initialize - Failed to allocate memory!");
VkMemoryAllocateInfo memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = reqs.size;
memory_allocate_info.memoryTypeIndex = ui::vulkan::util::ChooseHostMemoryType(
provider_, reqs.memoryTypeBits, false);
if (memory_allocate_info.memoryTypeIndex == UINT32_MAX) {
XELOGE("CircularBuffer::Initialize - Failed to get memory type!");
Shutdown();
return VK_ERROR_INITIALIZATION_FAILED;
}
status = dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr,
&gpu_memory_);
if (status != VK_SUCCESS) {
XELOGE("CircularBuffer::Initialize - Failed to allocate memory!");
Shutdown();
return status;
}
capacity_ = reqs.size;
gpu_base_ = 0;
// Bind the buffer to its backing memory.
status =
dfn.vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_);
status = dfn.vkBindBufferMemory(device, gpu_buffer_, gpu_memory_, gpu_base_);
CheckResult(status, "vkBindBufferMemory");
if (status != VK_SUCCESS) {
XELOGE("CircularBuffer::Initialize - Failed to bind memory!");
@@ -109,7 +126,7 @@ VkResult CircularBuffer::Initialize() {
}
// Map the memory so we can access it.
status = dfn.vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0,
status = dfn.vkMapMemory(device, gpu_memory_, gpu_base_, capacity_, 0,
reinterpret_cast<void**>(&host_base_));
CheckResult(status, "vkMapMemory");
if (status != VK_SUCCESS) {
@@ -123,24 +140,26 @@ VkResult CircularBuffer::Initialize() {
void CircularBuffer::Shutdown() {
Clear();
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
if (host_base_) {
dfn.vkUnmapMemory(*device_, gpu_memory_);
dfn.vkUnmapMemory(device, gpu_memory_);
host_base_ = nullptr;
}
if (gpu_buffer_) {
dfn.vkDestroyBuffer(*device_, gpu_buffer_, nullptr);
dfn.vkDestroyBuffer(device, gpu_buffer_, nullptr);
gpu_buffer_ = nullptr;
}
if (gpu_memory_ && owns_gpu_memory_) {
dfn.vkFreeMemory(*device_, gpu_memory_, nullptr);
dfn.vkFreeMemory(device, gpu_memory_, nullptr);
gpu_memory_ = nullptr;
}
}
void CircularBuffer::GetBufferMemoryRequirements(VkMemoryRequirements* reqs) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, reqs);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkGetBufferMemoryRequirements(device, gpu_buffer_, reqs);
}
bool CircularBuffer::CanAcquire(VkDeviceSize length) {
@@ -231,25 +250,27 @@ CircularBuffer::Allocation* CircularBuffer::Acquire(VkDeviceSize length,
}
void CircularBuffer::Flush(Allocation* allocation) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkMappedMemoryRange range;
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.pNext = nullptr;
range.memory = gpu_memory_;
range.offset = gpu_base_ + allocation->offset;
range.size = allocation->length;
dfn.vkFlushMappedMemoryRanges(*device_, 1, &range);
dfn.vkFlushMappedMemoryRanges(device, 1, &range);
}
void CircularBuffer::Flush(VkDeviceSize offset, VkDeviceSize length) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkMappedMemoryRange range;
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.pNext = nullptr;
range.memory = gpu_memory_;
range.offset = gpu_base_ + offset;
range.size = length;
dfn.vkFlushMappedMemoryRanges(*device_, 1, &range);
dfn.vkFlushMappedMemoryRanges(device, 1, &range);
}
void CircularBuffer::Clear() {
@@ -258,14 +279,15 @@ void CircularBuffer::Clear() {
}
void CircularBuffer::Scavenge() {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
// Stash the last signalled fence
VkFence fence = nullptr;
while (!allocations_.empty()) {
Allocation& alloc = allocations_.front();
if (fence != alloc.fence &&
dfn.vkGetFenceStatus(*device_, alloc.fence) != VK_SUCCESS) {
dfn.vkGetFenceStatus(device, alloc.fence) != VK_SUCCESS) {
// Don't bother freeing following allocations to ensure proper ordering.
break;
}

View File

@@ -12,8 +12,7 @@
#include <queue>
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
@@ -27,7 +26,7 @@ namespace vulkan {
// ends of the buffer), where trailing older allocations are freed after use.
class CircularBuffer {
public:
CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage,
CircularBuffer(const VulkanProvider& provider, VkBufferUsageFlags usage,
VkDeviceSize capacity, VkDeviceSize alignment = 256);
~CircularBuffer();
@@ -76,7 +75,7 @@ class CircularBuffer {
VkDeviceSize write_head_ = 0;
VkDeviceSize read_head_ = 0;
VulkanDevice* device_;
const VulkanProvider& provider_;
bool owns_gpu_memory_ = false;
VkBuffer gpu_buffer_ = nullptr;
VkDeviceMemory gpu_memory_ = nullptr;

View File

@@ -17,12 +17,13 @@ namespace xe {
namespace ui {
namespace vulkan {
using xe::ui::vulkan::CheckResult;
using util::CheckResult;
CommandBufferPool::CommandBufferPool(const VulkanDevice& device,
CommandBufferPool::CommandBufferPool(const VulkanProvider& provider,
uint32_t queue_family_index)
: BaseFencedPool(device) {
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
: BaseFencedPool(provider) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
// Create the pool used for allocating buffers.
// They are marked as transient (short-lived) and cycled frequently.
@@ -33,7 +34,7 @@ CommandBufferPool::CommandBufferPool(const VulkanDevice& device,
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
cmd_pool_info.queueFamilyIndex = queue_family_index;
auto err =
dfn.vkCreateCommandPool(device_, &cmd_pool_info, nullptr, &command_pool_);
dfn.vkCreateCommandPool(device, &cmd_pool_info, nullptr, &command_pool_);
CheckResult(err, "vkCreateCommandPool");
// Allocate a bunch of command buffers to start.
@@ -45,7 +46,7 @@ CommandBufferPool::CommandBufferPool(const VulkanDevice& device,
command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
command_buffer_info.commandBufferCount = kDefaultCount;
VkCommandBuffer command_buffers[kDefaultCount];
err = dfn.vkAllocateCommandBuffers(device_, &command_buffer_info,
err = dfn.vkAllocateCommandBuffers(device, &command_buffer_info,
command_buffers);
CheckResult(err, "vkCreateCommandBuffer");
for (size_t i = 0; i < xe::countof(command_buffers); ++i) {
@@ -55,8 +56,9 @@ CommandBufferPool::CommandBufferPool(const VulkanDevice& device,
CommandBufferPool::~CommandBufferPool() {
FreeAllEntries();
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
dfn.vkDestroyCommandPool(device_, command_pool_, nullptr);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkDestroyCommandPool(device, command_pool_, nullptr);
command_pool_ = nullptr;
}
@@ -70,21 +72,24 @@ VkCommandBuffer CommandBufferPool::AllocateEntry(void* data) {
VkCommandBufferLevel(reinterpret_cast<uintptr_t>(data));
command_buffer_info.commandBufferCount = 1;
VkCommandBuffer command_buffer;
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
auto err = dfn.vkAllocateCommandBuffers(device_, &command_buffer_info,
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
auto err = dfn.vkAllocateCommandBuffers(device, &command_buffer_info,
&command_buffer);
CheckResult(err, "vkCreateCommandBuffer");
return command_buffer;
}
void CommandBufferPool::FreeEntry(VkCommandBuffer handle) {
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
dfn.vkFreeCommandBuffers(device_, command_pool_, 1, &handle);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkFreeCommandBuffers(device, command_pool_, 1, &handle);
}
DescriptorPool::DescriptorPool(const VulkanDevice& device, uint32_t max_count,
DescriptorPool::DescriptorPool(const VulkanProvider& provider,
uint32_t max_count,
std::vector<VkDescriptorPoolSize> pool_sizes)
: BaseFencedPool(device) {
: BaseFencedPool(provider) {
VkDescriptorPoolCreateInfo descriptor_pool_info;
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_info.pNext = nullptr;
@@ -93,15 +98,17 @@ DescriptorPool::DescriptorPool(const VulkanDevice& device, uint32_t max_count,
descriptor_pool_info.maxSets = max_count;
descriptor_pool_info.poolSizeCount = uint32_t(pool_sizes.size());
descriptor_pool_info.pPoolSizes = pool_sizes.data();
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
auto err = dfn.vkCreateDescriptorPool(device, &descriptor_pool_info, nullptr,
&descriptor_pool_);
CheckResult(err, "vkCreateDescriptorPool");
}
DescriptorPool::~DescriptorPool() {
FreeAllEntries();
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
dfn.vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkDestroyDescriptorPool(device, descriptor_pool_, nullptr);
descriptor_pool_ = nullptr;
}
@@ -115,17 +122,19 @@ VkDescriptorSet DescriptorPool::AllocateEntry(void* data) {
set_alloc_info.descriptorPool = descriptor_pool_;
set_alloc_info.descriptorSetCount = 1;
set_alloc_info.pSetLayouts = &layout;
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
auto err =
dfn.vkAllocateDescriptorSets(device_, &set_alloc_info, &descriptor_set);
dfn.vkAllocateDescriptorSets(device, &set_alloc_info, &descriptor_set);
CheckResult(err, "vkAllocateDescriptorSets");
return descriptor_set;
}
void DescriptorPool::FreeEntry(VkDescriptorSet handle) {
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
dfn.vkFreeDescriptorSets(device_, descriptor_pool_, 1, &handle);
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkFreeDescriptorSets(device, descriptor_pool_, 1, &handle);
}
} // namespace vulkan

View File

@@ -13,8 +13,7 @@
#include <memory>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
@@ -29,7 +28,7 @@ namespace vulkan {
template <typename T, typename HANDLE>
class BaseFencedPool {
public:
BaseFencedPool(const VulkanDevice& device) : device_(device) {}
BaseFencedPool(const VulkanProvider& provider) : provider_(provider) {}
virtual ~BaseFencedPool() {
// TODO(benvanik): wait on fence until done.
@@ -48,12 +47,13 @@ class BaseFencedPool {
// Checks all pending batches for completion and scavenges their entries.
// This should be called as frequently as reasonable.
void Scavenge() {
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
while (pending_batch_list_head_) {
auto batch = pending_batch_list_head_;
assert_not_null(batch->fence);
VkResult status = dfn.vkGetFenceStatus(device_, batch->fence);
VkResult status = dfn.vkGetFenceStatus(device, batch->fence);
if (status == VK_SUCCESS || status == VK_ERROR_DEVICE_LOST) {
// Batch has completed. Reclaim.
pending_batch_list_head_ = batch->next;
@@ -82,7 +82,8 @@ class BaseFencedPool {
VkFence BeginBatch(VkFence fence = nullptr) {
assert_null(open_batch_);
Batch* batch = nullptr;
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
if (free_batch_list_head_) {
// Reuse a batch.
batch = free_batch_list_head_;
@@ -91,10 +92,10 @@ class BaseFencedPool {
if (batch->flags & kBatchOwnsFence && !fence) {
// Reset owned fence.
dfn.vkResetFences(device_, 1, &batch->fence);
dfn.vkResetFences(device, 1, &batch->fence);
} else if ((batch->flags & kBatchOwnsFence) && fence) {
// Transfer owned -> external
dfn.vkDestroyFence(device_, batch->fence, nullptr);
dfn.vkDestroyFence(device, batch->fence, nullptr);
batch->fence = fence;
batch->flags &= ~kBatchOwnsFence;
} else if (!(batch->flags & kBatchOwnsFence) && !fence) {
@@ -103,8 +104,7 @@ class BaseFencedPool {
info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
info.pNext = nullptr;
info.flags = 0;
VkResult res =
dfn.vkCreateFence(device_, &info, nullptr, &batch->fence);
VkResult res = dfn.vkCreateFence(device, &info, nullptr, &batch->fence);
if (res != VK_SUCCESS) {
assert_always();
}
@@ -125,8 +125,7 @@ class BaseFencedPool {
info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
info.pNext = nullptr;
info.flags = 0;
VkResult res =
dfn.vkCreateFence(device_, &info, nullptr, &batch->fence);
VkResult res = dfn.vkCreateFence(device, &info, nullptr, &batch->fence);
if (res != VK_SUCCESS) {
assert_always();
}
@@ -244,14 +243,15 @@ class BaseFencedPool {
}
void FreeAllEntries() {
const VulkanDevice::DeviceFunctions& dfn = device_.dfn();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
// Run down free lists.
while (free_batch_list_head_) {
auto batch = free_batch_list_head_;
free_batch_list_head_ = batch->next;
if (batch->flags & kBatchOwnsFence) {
dfn.vkDestroyFence(device_, batch->fence, nullptr);
dfn.vkDestroyFence(device, batch->fence, nullptr);
batch->fence = nullptr;
}
delete batch;
@@ -264,7 +264,7 @@ class BaseFencedPool {
}
}
const VulkanDevice& device_;
const VulkanProvider& provider_;
private:
struct Entry {
@@ -294,7 +294,8 @@ class CommandBufferPool
public:
typedef BaseFencedPool<CommandBufferPool, VkCommandBuffer> Base;
CommandBufferPool(const VulkanDevice& device, uint32_t queue_family_index);
CommandBufferPool(const VulkanProvider& provider,
uint32_t queue_family_index);
~CommandBufferPool() override;
VkCommandBuffer AcquireEntry(
@@ -314,7 +315,7 @@ class DescriptorPool : public BaseFencedPool<DescriptorPool, VkDescriptorSet> {
public:
typedef BaseFencedPool<DescriptorPool, VkDescriptorSet> Base;
DescriptorPool(const VulkanDevice& device, uint32_t max_count,
DescriptorPool(const VulkanProvider& provider, uint32_t max_count,
std::vector<VkDescriptorPoolSize> pool_sizes);
~DescriptorPool() override;

View File

@@ -3,16 +3,18 @@ XE_UI_VULKAN_FUNCTION(vkAllocateCommandBuffers)
XE_UI_VULKAN_FUNCTION(vkAllocateDescriptorSets)
XE_UI_VULKAN_FUNCTION(vkAllocateMemory)
XE_UI_VULKAN_FUNCTION(vkBeginCommandBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdBeginRenderPass)
XE_UI_VULKAN_FUNCTION(vkCmdBindDescriptorSets)
XE_UI_VULKAN_FUNCTION(vkBindBufferMemory)
XE_UI_VULKAN_FUNCTION(vkBindImageMemory)
XE_UI_VULKAN_FUNCTION(vkCmdBeginRenderPass)
XE_UI_VULKAN_FUNCTION(vkCmdBindDescriptorSets)
XE_UI_VULKAN_FUNCTION(vkCmdBindIndexBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdBindPipeline)
XE_UI_VULKAN_FUNCTION(vkCmdBindVertexBuffers)
XE_UI_VULKAN_FUNCTION(vkCmdBlitImage)
XE_UI_VULKAN_FUNCTION(vkCmdClearAttachments)
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
XE_UI_VULKAN_FUNCTION(vkCmdClearDepthStencilImage)
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage)
XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdDraw)
@@ -67,17 +69,20 @@ XE_UI_VULKAN_FUNCTION(vkFlushMappedMemoryRanges)
XE_UI_VULKAN_FUNCTION(vkFreeCommandBuffers)
XE_UI_VULKAN_FUNCTION(vkFreeDescriptorSets)
XE_UI_VULKAN_FUNCTION(vkFreeMemory)
XE_UI_VULKAN_FUNCTION(vkGetDeviceQueue)
XE_UI_VULKAN_FUNCTION(vkGetBufferMemoryRequirements)
XE_UI_VULKAN_FUNCTION(vkGetDeviceQueue)
XE_UI_VULKAN_FUNCTION(vkGetFenceStatus)
XE_UI_VULKAN_FUNCTION(vkGetImageMemoryRequirements)
XE_UI_VULKAN_FUNCTION(vkGetImageSubresourceLayout)
XE_UI_VULKAN_FUNCTION(vkGetPipelineCacheData)
XE_UI_VULKAN_FUNCTION(vkInvalidateMappedMemoryRanges)
XE_UI_VULKAN_FUNCTION(vkMapMemory)
XE_UI_VULKAN_FUNCTION(vkResetCommandBuffer)
XE_UI_VULKAN_FUNCTION(vkResetCommandPool)
XE_UI_VULKAN_FUNCTION(vkResetDescriptorPool)
XE_UI_VULKAN_FUNCTION(vkResetFences)
XE_UI_VULKAN_FUNCTION(vkQueueSubmit)
XE_UI_VULKAN_FUNCTION(vkQueueWaitIdle)
XE_UI_VULKAN_FUNCTION(vkResetCommandBuffer)
XE_UI_VULKAN_FUNCTION(vkResetFences)
XE_UI_VULKAN_FUNCTION(vkUnmapMemory)
XE_UI_VULKAN_FUNCTION(vkUpdateDescriptorSets)
XE_UI_VULKAN_FUNCTION(vkWaitForFences)

View File

@@ -1,5 +0,0 @@
// VK_EXT_debug_marker functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkDebugMarkerSetObjectNameEXT)
XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerBeginEXT)
XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerEndEXT)
XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerInsertEXT)

View File

@@ -2,7 +2,6 @@
XE_UI_VULKAN_FUNCTION(vkCreateDevice)
XE_UI_VULKAN_FUNCTION(vkDestroyDevice)
XE_UI_VULKAN_FUNCTION(vkEnumerateDeviceExtensionProperties)
XE_UI_VULKAN_FUNCTION(vkEnumerateDeviceLayerProperties)
XE_UI_VULKAN_FUNCTION(vkEnumeratePhysicalDevices)
XE_UI_VULKAN_FUNCTION(vkGetDeviceProcAddr)
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceFeatures)

View File

@@ -1,3 +0,0 @@
// VK_EXT_debug_report functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateDebugReportCallbackEXT)
XE_UI_VULKAN_FUNCTION(vkDestroyDebugReportCallbackEXT)

View File

@@ -0,0 +1,4 @@
// VK_EXT_debug_utils functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateDebugUtilsMessengerEXT)
XE_UI_VULKAN_FUNCTION(vkDestroyDebugUtilsMessengerEXT)
XE_UI_VULKAN_FUNCTION(vkSetDebugUtilsObjectNameEXT)

View File

@@ -0,0 +1,4 @@
// VK_KHR_get_physical_device_properties2 functions used in Xenia.
// Promoted to Vulkan 1.1 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceProperties2KHR,
vkGetPhysicalDeviceProperties2)

View File

@@ -1,2 +1,3 @@
// VK_KHR_win32_surface functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateWin32SurfaceKHR)
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceWin32PresentationSupportKHR)

View File

@@ -1,109 +0,0 @@
// generated from `xb genspirv`
// source: immediate.frag
const uint8_t immediate_frag[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, 0x00,
0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C,
0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00,
0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F,
0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x76, 0x74, 0x78, 0x5F, 0x63, 0x6F, 0x6C, 0x6F,
0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x50, 0x75, 0x73, 0x68, 0x43, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74,
0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74,
0x5F, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5F, 0x73, 0x61, 0x6D,
0x70, 0x6C, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
0x11, 0x00, 0x00, 0x00, 0x70, 0x75, 0x73, 0x68, 0x5F, 0x63, 0x6F, 0x6E,
0x73, 0x74, 0x61, 0x6E, 0x74, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x1C, 0x00, 0x00, 0x00, 0x76, 0x74, 0x78, 0x5F, 0x75, 0x76, 0x00, 0x00,
0x05, 0x00, 0x06, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74,
0x75, 0x72, 0x65, 0x5F, 0x73, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72, 0x00,
0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x13, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x1B, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x15, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x1F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3F, 0x19, 0x00, 0x09, 0x00, 0x29, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, 0x2A, 0x00, 0x00, 0x00,
0x29, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x2B, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x15, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x05, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0xF7, 0x00, 0x03, 0x00,
0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x18, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
0x1F, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x05, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x19, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x19, 0x00, 0x00, 0x00, 0xF5, 0x00, 0x07, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0xF7, 0x00, 0x03, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFA, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
0x26, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x25, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x04, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00,
0x2E, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00,
0x2E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x31, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
0x2F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
0x32, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x26, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x26, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00,
0x38, 0x00, 0x01, 0x00,
};

View File

@@ -1,83 +0,0 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 6
; Bound: 51
; Schema: 0
OpCapability Shader
OpCapability Sampled1D
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %out_color %vtx_color %vtx_uv
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %out_color "out_color"
OpName %vtx_color "vtx_color"
OpName %PushConstants "PushConstants"
OpMemberName %PushConstants 0 "restrict_texture_samples"
OpName %push_constants "push_constants"
OpName %vtx_uv "vtx_uv"
OpName %texture_sampler "texture_sampler"
OpDecorate %out_color Location 0
OpDecorate %vtx_color Location 1
OpMemberDecorate %PushConstants 0 Offset 64
OpDecorate %PushConstants Block
OpDecorate %vtx_uv Location 0
OpDecorate %texture_sampler DescriptorSet 0
OpDecorate %texture_sampler Binding 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%out_color = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%vtx_color = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%int = OpTypeInt 32 1
%PushConstants = OpTypeStruct %int
%_ptr_PushConstant_PushConstants = OpTypePointer PushConstant %PushConstants
%push_constants = OpVariable %_ptr_PushConstant_PushConstants PushConstant
%int_0 = OpConstant %int 0
%_ptr_PushConstant_int = OpTypePointer PushConstant %int
%v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
%vtx_uv = OpVariable %_ptr_Input_v2float Input
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_ptr_Input_float = OpTypePointer Input %float
%float_1 = OpConstant %float 1
%41 = OpTypeImage %float 2D 0 0 0 1 Unknown
%42 = OpTypeSampledImage %41
%_ptr_UniformConstant_42 = OpTypePointer UniformConstant %42
%texture_sampler = OpVariable %_ptr_UniformConstant_42 UniformConstant
%main = OpFunction %void None %3
%5 = OpLabel
%12 = OpLoad %v4float %vtx_color
OpStore %out_color %12
%20 = OpAccessChain %_ptr_PushConstant_int %push_constants %int_0
%21 = OpLoad %int %20
%22 = OpIEqual %bool %21 %int_0
%23 = OpLogicalNot %bool %22
OpSelectionMerge %25 None
OpBranchConditional %23 %24 %25
%24 = OpLabel
%32 = OpAccessChain %_ptr_Input_float %vtx_uv %uint_0
%33 = OpLoad %float %32
%35 = OpFOrdLessThanEqual %bool %33 %float_1
OpBranch %25
%25 = OpLabel
%36 = OpPhi %bool %22 %5 %35 %24
OpSelectionMerge %38 None
OpBranchConditional %36 %37 %38
%37 = OpLabel
%45 = OpLoad %42 %texture_sampler
%46 = OpLoad %v2float %vtx_uv
%47 = OpImageSampleImplicitLod %v4float %45 %46
%49 = OpLoad %v4float %out_color
%50 = OpFMul %v4float %49 %47
OpStore %out_color %50
OpBranch %38
%38 = OpLabel
OpReturn
OpFunctionEnd

View File

@@ -1,128 +0,0 @@
// generated from `xb genspirv`
// source: immediate.vert
const uint8_t immediate_vert[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
0x2A, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x67, 0x6C, 0x5F, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74,
0x69, 0x6F, 0x6E, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x69, 0x6E, 0x74,
0x53, 0x69, 0x7A, 0x65, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43,
0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00,
0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x67, 0x6C, 0x5F, 0x43, 0x75, 0x6C, 0x6C, 0x44, 0x69, 0x73, 0x74, 0x61,
0x6E, 0x63, 0x65, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x11, 0x00, 0x00, 0x00,
0x50, 0x75, 0x73, 0x68, 0x43, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74,
0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x11, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x6F, 0x6A, 0x65, 0x63, 0x74, 0x69,
0x6F, 0x6E, 0x5F, 0x6D, 0x61, 0x74, 0x72, 0x69, 0x78, 0x00, 0x00, 0x00,
0x05, 0x00, 0x06, 0x00, 0x13, 0x00, 0x00, 0x00, 0x70, 0x75, 0x73, 0x68,
0x5F, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74, 0x73, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x70,
0x6F, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00,
0x76, 0x74, 0x78, 0x5F, 0x75, 0x76, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x2A, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x75, 0x76, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x76, 0x74, 0x78, 0x5F,
0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
0x2E, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x72,
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x47, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x48, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x19, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x2A, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1C, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x15, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00,
0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x20, 0x00, 0x04, 0x00,
0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x3B, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x2D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3B, 0x00, 0x04, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00,
0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00,
0x1F, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
0x1B, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x1F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00,
0x2E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x2C, 0x00, 0x00, 0x00,
0x2F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
};

View File

@@ -1,90 +0,0 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 6
; Bound: 48
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %_ %in_pos %vtx_uv %in_uv %vtx_color %in_color
OpSource GLSL 450
OpName %main "main"
OpName %gl_PerVertex "gl_PerVertex"
OpMemberName %gl_PerVertex 0 "gl_Position"
OpMemberName %gl_PerVertex 1 "gl_PointSize"
OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
OpMemberName %gl_PerVertex 3 "gl_CullDistance"
OpName %_ ""
OpName %PushConstants "PushConstants"
OpMemberName %PushConstants 0 "projection_matrix"
OpName %push_constants "push_constants"
OpName %in_pos "in_pos"
OpName %vtx_uv "vtx_uv"
OpName %in_uv "in_uv"
OpName %vtx_color "vtx_color"
OpName %in_color "in_color"
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
OpDecorate %gl_PerVertex Block
OpMemberDecorate %PushConstants 0 ColMajor
OpMemberDecorate %PushConstants 0 Offset 0
OpMemberDecorate %PushConstants 0 MatrixStride 16
OpDecorate %PushConstants Block
OpDecorate %in_pos Location 0
OpDecorate %vtx_uv Location 0
OpDecorate %in_uv Location 1
OpDecorate %vtx_color Location 1
OpDecorate %in_color Location 2
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr_float_uint_1 = OpTypeArray %float %uint_1
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
%_ = OpVariable %_ptr_Output_gl_PerVertex Output
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%mat4v4float = OpTypeMatrix %v4float 4
%PushConstants = OpTypeStruct %mat4v4float
%_ptr_PushConstant_PushConstants = OpTypePointer PushConstant %PushConstants
%push_constants = OpVariable %_ptr_PushConstant_PushConstants PushConstant
%_ptr_PushConstant_mat4v4float = OpTypePointer PushConstant %mat4v4float
%v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
%in_pos = OpVariable %_ptr_Input_v2float Input
%float_0 = OpConstant %float 0
%float_1 = OpConstant %float 1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_ptr_Output_float = OpTypePointer Output %float
%_ptr_Output_v2float = OpTypePointer Output %v2float
%vtx_uv = OpVariable %_ptr_Output_v2float Output
%in_uv = OpVariable %_ptr_Input_v2float Input
%vtx_color = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%in_color = OpVariable %_ptr_Input_v4float Input
%main = OpFunction %void None %3
%5 = OpLabel
%21 = OpAccessChain %_ptr_PushConstant_mat4v4float %push_constants %int_0
%22 = OpLoad %mat4v4float %21
%26 = OpLoad %v2float %in_pos
%29 = OpCompositeExtract %float %26 0
%30 = OpCompositeExtract %float %26 1
%31 = OpCompositeConstruct %v4float %29 %30 %float_0 %float_1
%32 = OpMatrixTimesVector %v4float %22 %31
%34 = OpAccessChain %_ptr_Output_v4float %_ %int_0
OpStore %34 %32
%36 = OpAccessChain %_ptr_Output_float %_ %int_0 %uint_1
%37 = OpLoad %float %36
%38 = OpFNegate %float %37
OpStore %36 %38
%43 = OpLoad %v2float %in_uv
OpStore %vtx_uv %43
%47 = OpLoad %v4float %in_color
OpStore %vtx_color %47
OpReturn
OpFunctionEnd

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: false

View File

@@ -1,7 +1,7 @@
// generated from `xb genspirv`
// source: blit_color.frag
const uint8_t blit_color_frag[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, 0x00,
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00,
0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,

View File

@@ -1,6 +1,6 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 6
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 36
; Schema: 0
OpCapability Shader

View File

@@ -1,7 +1,7 @@
// generated from `xb genspirv`
// source: blit_depth.frag
const uint8_t blit_depth_frag[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, 0x00,
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00,
0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,

Binary file not shown.

View File

@@ -1,6 +1,6 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 6
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 30
; Schema: 0
OpCapability Shader

View File

@@ -1,7 +1,7 @@
// generated from `xb genspirv`
// source: blit.vert
const uint8_t blit_vert[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x08, 0x00,
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00,
0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,

View File

@@ -1,6 +1,6 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 6
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 76
; Schema: 0
OpCapability Shader

View File

@@ -1,28 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
precision highp float;
layout(push_constant) uniform PushConstants {
layout(offset = 64) int restrict_texture_samples;
} push_constants;
layout(set = 0, binding = 0) uniform sampler2D texture_sampler;
layout(set = 0, binding = 1) uniform sampler1D tex1D;
layout(set = 0, binding = 1) uniform sampler2D tex2D;
layout(location = 0) in vec2 vtx_uv;
layout(location = 1) in vec4 vtx_color;
layout(location = 0) out vec4 out_color;
void main() {
out_color = vtx_color;
if (push_constants.restrict_texture_samples == 0 || vtx_uv.x <= 1.0) {
vec4 tex_color = texture(texture_sampler, vtx_uv);
out_color *= tex_color;
// TODO(benvanik): microprofiler shadows.
}
}

View File

@@ -1,23 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
precision highp float;
layout(push_constant) uniform PushConstants {
layout(offset = 0) mat4 projection_matrix;
} push_constants;
layout(location = 0) in vec2 in_pos;
layout(location = 1) in vec2 in_uv;
layout(location = 2) in vec4 in_color;
layout(location = 0) out vec2 vtx_uv;
layout(location = 1) out vec4 vtx_color;
void main() {
gl_Position = push_constants.projection_matrix * vec4(in_pos.xy, 0.0, 1.0);
gl_Position.y = -gl_Position.y;
vtx_uv = in_uv;
vtx_color = in_color;
}

View File

@@ -1,18 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan.h"
DEFINE_bool(vulkan_validation, false, "Enable Vulkan validation layers.",
"Vulkan");
DEFINE_bool(vulkan_primary_queue_only, false,
"Force the use of the primary queue, ignoring any additional that "
"may be present.",
"Vulkan");

View File

@@ -1,43 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_H_
#define XENIA_UI_VULKAN_VULKAN_H_
#include "xenia/base/cvar.h"
#include "xenia/base/platform.h"
#if XE_PLATFORM_ANDROID
#ifndef VK_USE_PLATFORM_ANDROID_KHR
#define VK_USE_PLATFORM_ANDROID_KHR 1
#endif
#elif XE_PLATFORM_GNU_LINUX
#ifndef VK_USE_PLATFORM_XCB_KHR
#define VK_USE_PLATFORM_XCB_KHR 1
#endif
#elif XE_PLATFORM_WIN32
// Must be included before vulkan.h with VK_USE_PLATFORM_WIN32_KHR because it
// includes Windows.h too.
#include "xenia/base/platform_win.h"
#ifndef VK_USE_PLATFORM_WIN32_KHR
#define VK_USE_PLATFORM_WIN32_KHR 1
#endif
#endif
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES 1
#endif
#include "third_party/vulkan/vulkan.h"
#define XELOGVK XELOGI
DECLARE_bool(vulkan_validation);
DECLARE_bool(vulkan_primary_queue_only);
#endif // XENIA_UI_VULKAN_VULKAN_H_

View File

@@ -1,206 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_context.h"
#include <mutex>
#include <string>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
#include "xenia/ui/vulkan/vulkan_instance.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"
#if XE_PLATFORM_GNU_LINUX
#include "xenia/ui/window_gtk.h"
#include <X11/Xlib-xcb.h>
#endif
namespace xe {
namespace ui {
namespace vulkan {
VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window)
: GraphicsContext(provider, target_window) {}
VulkanContext::~VulkanContext() {
VkResult status;
auto provider = static_cast<VulkanProvider*>(provider_);
VulkanDevice* device = provider->device();
const VulkanDevice::DeviceFunctions& dfn = device->dfn();
{
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
status = dfn.vkQueueWaitIdle(device->primary_queue());
}
immediate_drawer_.reset();
swap_chain_.reset();
}
bool VulkanContext::Initialize() {
auto provider = static_cast<VulkanProvider*>(provider_);
VulkanInstance* instance = provider->instance();
const VulkanInstance::InstanceFunctions& ifn = instance->ifn();
if (target_window_) {
// Create swap chain used to present to the window.
VkResult status = VK_ERROR_FEATURE_NOT_PRESENT;
VkSurfaceKHR surface = nullptr;
#if XE_PLATFORM_WIN32
VkWin32SurfaceCreateInfoKHR create_info;
create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.hinstance =
static_cast<HINSTANCE>(target_window_->native_platform_handle());
create_info.hwnd = static_cast<HWND>(target_window_->native_handle());
status = ifn.vkCreateWin32SurfaceKHR(*provider->instance(), &create_info,
nullptr, &surface);
CheckResult(status, "vkCreateWin32SurfaceKHR");
#elif XE_PLATFORM_GNU_LINUX
#ifdef GDK_WINDOWING_X11
GtkWidget* window_handle =
dynamic_cast<GTKWindow*>(target_window_)->native_window_handle();
xcb_window_t window =
gdk_x11_window_get_xid(gtk_widget_get_window(window_handle));
VkXcbSurfaceCreateInfoKHR create_info;
create_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.connection = static_cast<xcb_connection_t*>(
target_window_->native_platform_handle());
create_info.window = static_cast<xcb_window_t>(window);
status = ifn.vkCreateXcbSurfaceKHR(*provider->instance(), &create_info,
nullptr, &surface);
CheckResult(status, "vkCreateXcbSurfaceKHR");
#else
#error Unsupported GDK Backend on Linux.
#endif // GDK_WINDOWING_X11
#else
#error Platform not yet implemented.
#endif // XE_PLATFORM_WIN32
if (status != VK_SUCCESS) {
XELOGE("Failed to create presentation surface");
return false;
}
swap_chain_ =
std::make_unique<VulkanSwapChain>(instance, provider->device());
if (swap_chain_->Initialize(surface) != VK_SUCCESS) {
XELOGE("Unable to initialize swap chain");
return false;
}
// Only initialize immediate mode drawer if we are not an offscreen context.
immediate_drawer_ = std::make_unique<VulkanImmediateDrawer>(this);
status = immediate_drawer_->Initialize();
if (status != VK_SUCCESS) {
XELOGE("Failed to initialize the immediate mode drawer");
immediate_drawer_.reset();
return false;
}
}
return true;
}
ImmediateDrawer* VulkanContext::immediate_drawer() {
return immediate_drawer_.get();
}
VulkanInstance* VulkanContext::instance() const {
return static_cast<VulkanProvider*>(provider_)->instance();
}
VulkanDevice* VulkanContext::device() const {
return static_cast<VulkanProvider*>(provider_)->device();
}
bool VulkanContext::is_current() { return false; }
bool VulkanContext::MakeCurrent() {
SCOPE_profile_cpu_f("gpu");
return true;
}
void VulkanContext::ClearCurrent() {}
bool VulkanContext::BeginSwap() {
SCOPE_profile_cpu_f("gpu");
auto provider = static_cast<VulkanProvider*>(provider_);
VulkanDevice* device = provider->device();
const VulkanDevice::DeviceFunctions& dfn = device->dfn();
VkResult status;
// If we have a window see if it's been resized since we last swapped.
// If it has been, we'll need to reinitialize the swap chain before we
// start touching it.
if (target_window_) {
if (target_window_->scaled_width() != swap_chain_->surface_width() ||
target_window_->scaled_height() != swap_chain_->surface_height()) {
// Resized!
swap_chain_->Reinitialize();
}
}
if (!context_lost_) {
// Acquire the next image and set it up for use.
status = swap_chain_->Begin();
if (status == VK_ERROR_DEVICE_LOST) {
context_lost_ = true;
}
}
// TODO(benvanik): use a fence instead? May not be possible with target image.
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
status = dfn.vkQueueWaitIdle(device->primary_queue());
return true;
}
void VulkanContext::EndSwap() {
SCOPE_profile_cpu_f("gpu");
auto provider = static_cast<const VulkanProvider*>(provider_);
VulkanDevice* device = provider->device();
const VulkanDevice::DeviceFunctions& dfn = device->dfn();
VkResult status;
if (!context_lost_) {
// Notify the presentation engine the image is ready.
// The contents must be in a coherent state.
status = swap_chain_->End();
if (status == VK_ERROR_DEVICE_LOST) {
context_lost_ = true;
}
}
// Wait until the queue is idle.
// TODO(benvanik): is this required?
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
status = dfn.vkQueueWaitIdle(device->primary_queue());
}
std::unique_ptr<RawImage> VulkanContext::Capture() {
// TODO(benvanik): read back swap chain front buffer.
return nullptr;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,67 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
#define XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
#include <memory>
#include "xenia/ui/graphics_context.h"
#include "xenia/ui/vulkan/vulkan.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanDevice;
class VulkanImmediateDrawer;
class VulkanInstance;
class VulkanProvider;
class VulkanSwapChain;
class VulkanContext : public GraphicsContext {
public:
~VulkanContext() override;
ImmediateDrawer* immediate_drawer() override;
VulkanSwapChain* swap_chain() const { return swap_chain_.get(); }
VulkanInstance* instance() const;
VulkanDevice* device() const;
bool is_current() override;
bool MakeCurrent() override;
void ClearCurrent() override;
bool WasLost() override { return context_lost_; }
bool BeginSwap() override;
void EndSwap() override;
std::unique_ptr<RawImage> Capture() override;
protected:
bool context_lost_ = false;
private:
friend class VulkanProvider;
explicit VulkanContext(VulkanProvider* provider, Window* target_window);
private:
bool Initialize();
std::unique_ptr<VulkanSwapChain> swap_chain_;
std::unique_ptr<VulkanImmediateDrawer> immediate_drawer_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_CONTEXT_H_

View File

@@ -1,427 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2017 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_device.h"
#include <cinttypes>
#include <climits>
#include <cstring>
#include <mutex>
#include <string>
#include "third_party/renderdoc/renderdoc_app.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
#include "xenia/ui/vulkan/vulkan_instance.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/window.h"
namespace xe {
namespace ui {
namespace vulkan {
VulkanDevice::VulkanDevice(VulkanInstance* instance) : instance_(instance) {
if (cvars::vulkan_validation) {
DeclareRequiredLayer("VK_LAYER_LUNARG_standard_validation",
Version::Make(0, 0, 0), true);
// DeclareRequiredLayer("VK_LAYER_GOOGLE_unique_objects", Version::Make(0,
// 0, 0), true);
/*
DeclareRequiredLayer("VK_LAYER_GOOGLE_threading", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_core_validation",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_object_tracker",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_draw_state", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_parameter_validation",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_swapchain", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_device_limits",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_image", Version::Make(0, 0, 0), true);
*/
}
// AMD shader info (optional)
DeclareRequiredExtension(VK_AMD_SHADER_INFO_EXTENSION_NAME,
Version::Make(0, 0, 0), true);
// Debug markers (optional)
DeclareRequiredExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
Version::Make(0, 0, 0), true);
DeclareRequiredExtension(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
Version::Make(0, 0, 0), false);
}
VulkanDevice::~VulkanDevice() {
if (handle) {
const VulkanInstance::InstanceFunctions& ifn = instance_->ifn();
ifn.vkDestroyDevice(handle, nullptr);
handle = nullptr;
}
}
bool VulkanDevice::Initialize(DeviceInfo device_info) {
// Gather list of enabled layer names.
auto layers_result = CheckRequirements(required_layers_, device_info.layers);
auto& enabled_layers = layers_result.second;
// Gather list of enabled extension names.
auto extensions_result =
CheckRequirements(required_extensions_, device_info.extensions);
enabled_extensions_ = extensions_result.second;
// We wait until both extensions and layers are checked before failing out so
// that the user gets a complete list of what they have/don't.
if (!extensions_result.first || !layers_result.first) {
FatalVulkanError(
"Layer and extension verification failed; aborting initialization");
return false;
}
const VulkanInstance::InstanceFunctions& ifn = instance_->ifn();
// Query supported features so we can make sure we have what we need.
VkPhysicalDeviceFeatures supported_features;
ifn.vkGetPhysicalDeviceFeatures(device_info.handle, &supported_features);
VkPhysicalDeviceFeatures enabled_features = {0};
bool any_features_missing = false;
#define ENABLE_AND_EXPECT(name) \
if (!supported_features.name) { \
any_features_missing = true; \
FatalVulkanError("Vulkan device is missing feature " #name); \
} else { \
enabled_features.name = VK_TRUE; \
}
ENABLE_AND_EXPECT(shaderClipDistance);
ENABLE_AND_EXPECT(shaderCullDistance);
ENABLE_AND_EXPECT(shaderStorageImageExtendedFormats);
ENABLE_AND_EXPECT(shaderTessellationAndGeometryPointSize);
ENABLE_AND_EXPECT(samplerAnisotropy);
ENABLE_AND_EXPECT(geometryShader);
ENABLE_AND_EXPECT(depthClamp);
ENABLE_AND_EXPECT(multiViewport);
ENABLE_AND_EXPECT(independentBlend);
ENABLE_AND_EXPECT(textureCompressionBC);
// TODO(benvanik): add other features.
if (any_features_missing) {
XELOGE(
"One or more required device features are missing; aborting "
"initialization");
return false;
}
// Pick a queue.
// Any queue we use must support both graphics and presentation.
// TODO(benvanik): use multiple queues (DMA-only, compute-only, etc).
if (device_info.queue_family_properties.empty()) {
FatalVulkanError("No queue families available");
return false;
}
uint32_t ideal_queue_family_index = UINT_MAX;
uint32_t queue_count = 1;
for (size_t i = 0; i < device_info.queue_family_properties.size(); ++i) {
auto queue_flags = device_info.queue_family_properties[i].queueFlags;
if (queue_flags & VK_QUEUE_GRAPHICS_BIT &&
queue_flags & VK_QUEUE_TRANSFER_BIT) {
// Can do graphics and transfer - good!
ideal_queue_family_index = static_cast<uint32_t>(i);
// Grab all the queues we can.
queue_count = device_info.queue_family_properties[i].queueCount;
break;
}
}
if (ideal_queue_family_index == UINT_MAX) {
FatalVulkanError(
"No queue families available that can both do graphics and transfer");
return false;
}
// Some tools *cough* renderdoc *cough* can't handle multiple queues.
if (cvars::vulkan_primary_queue_only) {
queue_count = 1;
}
std::vector<VkDeviceQueueCreateInfo> queue_infos;
std::vector<std::vector<float>> queue_priorities;
queue_infos.resize(device_info.queue_family_properties.size());
queue_priorities.resize(queue_infos.size());
for (int i = 0; i < queue_infos.size(); i++) {
VkDeviceQueueCreateInfo& queue_info = queue_infos[i];
VkQueueFamilyProperties& family_props =
device_info.queue_family_properties[i];
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = nullptr;
queue_info.flags = 0;
queue_info.queueFamilyIndex = i;
queue_info.queueCount = family_props.queueCount;
queue_priorities[i].resize(family_props.queueCount, 0.f);
if (i == ideal_queue_family_index) {
// Prioritize the first queue on the primary queue family.
queue_priorities[i][0] = 1.0f;
}
queue_info.pQueuePriorities = queue_priorities[i].data();
}
VkDeviceCreateInfo create_info;
create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.queueCreateInfoCount = static_cast<uint32_t>(queue_infos.size());
create_info.pQueueCreateInfos = queue_infos.data();
create_info.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size());
create_info.ppEnabledLayerNames = enabled_layers.data();
create_info.enabledExtensionCount =
static_cast<uint32_t>(enabled_extensions_.size());
create_info.ppEnabledExtensionNames = enabled_extensions_.data();
create_info.pEnabledFeatures = &enabled_features;
auto err =
ifn.vkCreateDevice(device_info.handle, &create_info, nullptr, &handle);
switch (err) {
case VK_SUCCESS:
// Ok!
break;
case VK_ERROR_INITIALIZATION_FAILED:
FatalVulkanError("Device initialization failed; generic");
return false;
case VK_ERROR_EXTENSION_NOT_PRESENT:
FatalVulkanError(
"Device initialization failed; requested extension not present");
return false;
case VK_ERROR_LAYER_NOT_PRESENT:
FatalVulkanError(
"Device initialization failed; requested layer not present");
return false;
default:
FatalVulkanError(std::string("Device initialization failed; unknown: ") +
to_string(err));
return false;
}
// Get device functions.
std::memset(&dfn_, 0, sizeof(dfn_));
bool device_functions_loaded = true;
debug_marker_ena_ = false;
#define XE_UI_VULKAN_FUNCTION(name) \
device_functions_loaded &= \
(dfn_.name = PFN_##name(ifn.vkGetDeviceProcAddr(handle, #name))) != \
nullptr;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
if (HasEnabledExtension(VK_AMD_SHADER_INFO_EXTENSION_NAME)) {
#include "xenia/ui/vulkan/functions/device_amd_shader_info.inc"
}
debug_marker_ena_ = HasEnabledExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
if (debug_marker_ena_) {
#include "xenia/ui/vulkan/functions/device_ext_debug_marker.inc"
}
#undef XE_UI_VULKAN_FUNCTION
if (!device_functions_loaded) {
XELOGE("Failed to get Vulkan device function pointers");
return false;
}
device_info_ = std::move(device_info);
queue_family_index_ = ideal_queue_family_index;
// Get the primary queue used for most submissions/etc.
dfn_.vkGetDeviceQueue(handle, queue_family_index_, 0, &primary_queue_);
if (!primary_queue_) {
XELOGE("vkGetDeviceQueue returned nullptr!");
return false;
}
// Get all additional queues, if we got any.
free_queues_.resize(device_info_.queue_family_properties.size());
for (uint32_t i = 0; i < device_info_.queue_family_properties.size(); i++) {
VkQueueFamilyProperties& family_props =
device_info_.queue_family_properties[i];
for (uint32_t j = 0; j < family_props.queueCount; j++) {
VkQueue queue = nullptr;
if (i == queue_family_index_ && j == 0) {
// Already retrieved the primary queue index.
continue;
}
dfn_.vkGetDeviceQueue(handle, i, j, &queue);
if (queue) {
free_queues_[i].push_back(queue);
}
}
}
XELOGVK("Device initialized successfully!");
return true;
}
bool VulkanDevice::HasEnabledExtension(const char* name) {
for (auto extension : enabled_extensions_) {
if (!std::strcmp(extension, name)) {
return true;
}
}
return false;
}
VkQueue VulkanDevice::AcquireQueue(uint32_t queue_family_index) {
std::lock_guard<std::mutex> lock(queue_mutex_);
if (free_queues_[queue_family_index].empty()) {
return nullptr;
}
auto queue = free_queues_[queue_family_index].back();
free_queues_[queue_family_index].pop_back();
return queue;
}
void VulkanDevice::ReleaseQueue(VkQueue queue, uint32_t queue_family_index) {
std::lock_guard<std::mutex> lock(queue_mutex_);
free_queues_[queue_family_index].push_back(queue);
}
void VulkanDevice::DbgSetObjectName(uint64_t object,
VkDebugReportObjectTypeEXT object_type,
const std::string& name) const {
if (!debug_marker_ena_) {
// Extension disabled.
return;
}
VkDebugMarkerObjectNameInfoEXT info;
info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
info.pNext = nullptr;
info.objectType = object_type;
info.object = object;
info.pObjectName = name.c_str();
dfn_.vkDebugMarkerSetObjectNameEXT(*this, &info);
}
void VulkanDevice::DbgMarkerBegin(VkCommandBuffer command_buffer,
std::string name, float r, float g, float b,
float a) const {
if (!debug_marker_ena_) {
// Extension disabled.
return;
}
VkDebugMarkerMarkerInfoEXT info;
info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
info.pNext = nullptr;
info.pMarkerName = name.c_str();
info.color[0] = r;
info.color[1] = g;
info.color[2] = b;
info.color[3] = a;
dfn_.vkCmdDebugMarkerBeginEXT(command_buffer, &info);
}
void VulkanDevice::DbgMarkerEnd(VkCommandBuffer command_buffer) const {
if (!debug_marker_ena_) {
// Extension disabled.
return;
}
dfn_.vkCmdDebugMarkerEndEXT(command_buffer);
}
void VulkanDevice::DbgMarkerInsert(VkCommandBuffer command_buffer,
std::string name, float r, float g, float b,
float a) const {
if (!debug_marker_ena_) {
// Extension disabled.
return;
}
VkDebugMarkerMarkerInfoEXT info;
info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
info.pNext = nullptr;
info.pMarkerName = name.c_str();
info.color[0] = r;
info.color[1] = g;
info.color[2] = g;
info.color[3] = b;
dfn_.vkCmdDebugMarkerInsertEXT(command_buffer, &info);
}
bool VulkanDevice::is_renderdoc_attached() const {
return instance_->is_renderdoc_attached();
}
void VulkanDevice::BeginRenderDocFrameCapture() {
auto api = reinterpret_cast<RENDERDOC_API_1_0_1*>(instance_->renderdoc_api());
if (!api) {
return;
}
assert_true(api->IsFrameCapturing() == 0);
api->StartFrameCapture(nullptr, nullptr);
}
void VulkanDevice::EndRenderDocFrameCapture() {
auto api = reinterpret_cast<RENDERDOC_API_1_0_1*>(instance_->renderdoc_api());
if (!api) {
return;
}
assert_true(api->IsFrameCapturing() == 1);
api->EndFrameCapture(nullptr, nullptr);
}
VkDeviceMemory VulkanDevice::AllocateMemory(
const VkMemoryRequirements& requirements,
VkFlags required_properties) const {
// Search memory types to find one matching our requirements and our
// properties.
uint32_t type_index = UINT_MAX;
for (uint32_t i = 0; i < device_info_.memory_properties.memoryTypeCount;
++i) {
const auto& memory_type = device_info_.memory_properties.memoryTypes[i];
if (((requirements.memoryTypeBits >> i) & 1) == 1) {
// Type is available for use; check for a match on properties.
if ((memory_type.propertyFlags & required_properties) ==
required_properties) {
type_index = i;
break;
}
}
}
if (type_index == UINT_MAX) {
XELOGE("Unable to find a matching memory type");
return nullptr;
}
// Allocate the memory.
VkMemoryAllocateInfo memory_info;
memory_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_info.pNext = nullptr;
memory_info.allocationSize = requirements.size;
memory_info.memoryTypeIndex = type_index;
VkDeviceMemory memory = nullptr;
auto err = dfn_.vkAllocateMemory(handle, &memory_info, nullptr, &memory);
CheckResult(err, "vkAllocateMemory");
return memory;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,145 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_DEVICE_H_
#define XENIA_UI_VULKAN_VULKAN_DEVICE_H_
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanInstance;
// Wrapper and utilities for VkDevice.
// Prefer passing this around over a VkDevice and casting as needed to call
// APIs.
class VulkanDevice {
public:
VulkanDevice(VulkanInstance* instance);
~VulkanDevice();
VulkanInstance* instance() const { return instance_; }
VkDevice handle = nullptr;
operator VkDevice() const { return handle; }
operator VkPhysicalDevice() const { return device_info_.handle; }
struct DeviceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
#include "xenia/ui/vulkan/functions/device_amd_shader_info.inc"
#include "xenia/ui/vulkan/functions/device_ext_debug_marker.inc"
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
#undef XE_UI_VULKAN_FUNCTION
};
const DeviceFunctions& dfn() const { return dfn_; }
// Declares a layer to verify and enable upon initialization.
// Must be called before Initialize.
void DeclareRequiredLayer(std::string name, uint32_t min_version,
bool is_optional) {
required_layers_.push_back({name, min_version, is_optional});
}
// Declares an extension to verify and enable upon initialization.
// Must be called before Initialize.
void DeclareRequiredExtension(std::string name, uint32_t min_version,
bool is_optional) {
required_extensions_.push_back({name, min_version, is_optional});
}
// Initializes the device, querying and enabling extensions and layers and
// preparing the device for general use.
// If initialization succeeds it's likely that no more failures beyond runtime
// issues will occur.
bool Initialize(DeviceInfo device_info);
bool HasEnabledExtension(const char* name);
uint32_t queue_family_index() const { return queue_family_index_; }
std::mutex& primary_queue_mutex() { return queue_mutex_; }
// Access to the primary queue must be synchronized with primary_queue_mutex.
VkQueue primary_queue() const { return primary_queue_; }
const DeviceInfo& device_info() const { return device_info_; }
// Acquires a queue for exclusive use by the caller.
// The queue will not be touched by any other code until it's returned with
// ReleaseQueue.
// Not all devices support queues or only support a limited number. If this
// returns null the primary_queue should be used with the
// primary_queue_mutex.
// This method is thread safe.
VkQueue AcquireQueue(uint32_t queue_family_index);
// Releases a queue back to the device pool.
// This method is thread safe.
void ReleaseQueue(VkQueue queue, uint32_t queue_family_index);
void DbgSetObjectName(uint64_t object, VkDebugReportObjectTypeEXT object_type,
const std::string& name) const;
void DbgMarkerBegin(VkCommandBuffer command_buffer, std::string name,
float r = 0.0f, float g = 0.0f, float b = 0.0f,
float a = 0.0f) const;
void DbgMarkerEnd(VkCommandBuffer command_buffer) const;
void DbgMarkerInsert(VkCommandBuffer command_buffer, std::string name,
float r = 0.0f, float g = 0.0f, float b = 0.0f,
float a = 0.0f) const;
// True if RenderDoc is attached and available for use.
bool is_renderdoc_attached() const;
// Begins capturing the current frame in RenderDoc, if it is attached.
// Must be paired with EndRenderDocCapture. Multiple frames cannot be
// captured at the same time.
void BeginRenderDocFrameCapture();
// Ends a capture.
void EndRenderDocFrameCapture();
// Allocates memory of the given size matching the required properties.
VkDeviceMemory AllocateMemory(
const VkMemoryRequirements& requirements,
VkFlags required_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) const;
private:
VulkanInstance* instance_ = nullptr;
std::vector<Requirement> required_layers_;
std::vector<Requirement> required_extensions_;
std::vector<const char*> enabled_extensions_;
DeviceFunctions dfn_ = {};
bool debug_marker_ena_ = false;
PFN_vkDebugMarkerSetObjectNameEXT pfn_vkDebugMarkerSetObjectNameEXT_ =
nullptr;
PFN_vkCmdDebugMarkerBeginEXT pfn_vkCmdDebugMarkerBeginEXT_ = nullptr;
PFN_vkCmdDebugMarkerEndEXT pfn_vkCmdDebugMarkerEndEXT_ = nullptr;
PFN_vkCmdDebugMarkerInsertEXT pfn_vkCmdDebugMarkerInsertEXT_ = nullptr;
DeviceInfo device_info_;
uint32_t queue_family_index_ = 0;
std::mutex queue_mutex_;
VkQueue primary_queue_ = nullptr;
std::vector<std::vector<VkQueue>> free_queues_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_DEVICE_H_

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,66 +10,166 @@
#ifndef XENIA_UI_VULKAN_VULKAN_IMMEDIATE_DRAWER_H_
#define XENIA_UI_VULKAN_VULKAN_IMMEDIATE_DRAWER_H_
#include <cstddef>
#include <deque>
#include <memory>
#include <utility>
#include <vector>
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
namespace ui {
namespace vulkan {
class LightweightCircularBuffer;
class VulkanContext;
class VulkanImmediateDrawer : public ImmediateDrawer {
public:
VulkanImmediateDrawer(VulkanContext* graphics_context);
~VulkanImmediateDrawer() override;
static std::unique_ptr<VulkanImmediateDrawer> Create(
const VulkanProvider& provider) {
auto immediate_drawer = std::unique_ptr<VulkanImmediateDrawer>(
new VulkanImmediateDrawer(provider));
if (!immediate_drawer->Initialize()) {
return nullptr;
}
return std::move(immediate_drawer);
}
VkResult Initialize();
void Shutdown();
~VulkanImmediateDrawer();
std::unique_ptr<ImmediateTexture> CreateTexture(uint32_t width,
uint32_t height,
ImmediateTextureFilter filter,
bool repeat,
bool is_repeated,
const uint8_t* data) override;
std::unique_ptr<ImmediateTexture> WrapTexture(VkImageView image_view,
VkSampler sampler,
uint32_t width,
uint32_t height);
void Begin(int render_target_width, int render_target_height) override;
void Begin(UIDrawContext& ui_draw_context, float coordinate_space_width,
float coordinate_space_height) override;
void BeginDrawBatch(const ImmediateDrawBatch& batch) override;
void Draw(const ImmediateDraw& draw) override;
void EndDrawBatch() override;
void End() override;
VkSampler GetSampler(ImmediateTextureFilter filter, bool repeat);
protected:
void OnLeavePresenter() override;
private:
VulkanContext* context_ = nullptr;
struct PushConstants {
struct Vertex {
float coordinate_space_size_inv[2];
} vertex;
};
struct {
VkSampler nearest_clamp = nullptr;
VkSampler nearest_repeat = nullptr;
VkSampler linear_clamp = nullptr;
VkSampler linear_repeat = nullptr;
} samplers_;
class VulkanImmediateTexture : public ImmediateTexture {
public:
struct Resource {
VkImage image;
VkDeviceMemory memory;
VkImageView image_view;
uint32_t descriptor_index;
};
VkDescriptorSetLayout texture_set_layout_ = nullptr;
VkDescriptorPool descriptor_pool_ = nullptr;
VkPipelineLayout pipeline_layout_ = nullptr;
VkPipeline triangle_pipeline_ = nullptr;
VkPipeline line_pipeline_ = nullptr;
VulkanImmediateTexture(uint32_t width, uint32_t height)
: ImmediateTexture(width, height), immediate_drawer_(nullptr) {}
~VulkanImmediateTexture() override;
std::unique_ptr<LightweightCircularBuffer> circular_buffer_;
// If null, this is either a blank texture, or the immediate drawer has been
// destroyed.
VulkanImmediateDrawer* immediate_drawer_;
size_t immediate_drawer_index_;
// Invalid if immediate_drawer_ is null, since it's managed by the immediate
// drawer.
Resource resource_;
size_t pending_upload_index_;
uint64_t last_usage_submission_ = 0;
};
bool batch_has_index_buffer_ = false;
VkCommandBuffer current_cmd_buffer_ = nullptr;
int current_render_target_width_ = 0;
int current_render_target_height_ = 0;
struct TextureDescriptorPool {
// Using uint64_t for recycled bits.
static constexpr uint32_t kDescriptorCount = 64;
VkDescriptorPool pool;
VkDescriptorSet sets[kDescriptorCount];
uint32_t index;
uint32_t unallocated_count;
uint64_t recycled_bits;
TextureDescriptorPool* unallocated_next;
TextureDescriptorPool* recycled_next;
};
VulkanImmediateDrawer(const VulkanProvider& provider) : provider_(provider) {}
bool Initialize();
bool EnsurePipelinesCreatedForCurrentRenderPass();
// Allocates a combined image sampler in a pool and returns its index, or
// UINT32_MAX in case of failure.
uint32_t AllocateTextureDescriptor();
VkDescriptorSet GetTextureDescriptor(uint32_t descriptor_index) const;
void FreeTextureDescriptor(uint32_t descriptor_index);
// If data is null, a (1, 1, 1, 1) image will be created, which can be used as
// a replacement when drawing without a real texture.
bool CreateTextureResource(uint32_t width, uint32_t height,
ImmediateTextureFilter filter, bool is_repeated,
const uint8_t* data,
VulkanImmediateTexture::Resource& resource_out,
size_t& pending_upload_index_out);
void DestroyTextureResource(VulkanImmediateTexture::Resource& resource);
void OnImmediateTextureDestroyed(VulkanImmediateTexture& texture);
const VulkanProvider& provider_;
// Combined image sampler pools for textures.
VkDescriptorSetLayout texture_descriptor_set_layout_;
std::vector<TextureDescriptorPool*> texture_descriptor_pools_;
TextureDescriptorPool* texture_descriptor_pool_unallocated_first_ = nullptr;
TextureDescriptorPool* texture_descriptor_pool_recycled_first_ = nullptr;
VulkanImmediateTexture::Resource white_texture_ = {};
std::vector<VulkanImmediateTexture*> textures_;
struct PendingTextureUpload {
// Null for internal resources such as the white texture.
VulkanImmediateTexture* texture;
// VK_NULL_HANDLE if need to clear rather than to copy.
VkBuffer buffer;
VkDeviceMemory buffer_memory;
VkImage image;
uint32_t width;
uint32_t height;
};
std::vector<PendingTextureUpload> texture_uploads_pending_;
struct SubmittedTextureUploadBuffer {
VkBuffer buffer;
VkDeviceMemory buffer_memory;
uint64_t submission_index;
};
std::deque<SubmittedTextureUploadBuffer> texture_upload_buffers_submitted_;
// Resource and last usage submission pairs.
std::vector<std::pair<VulkanImmediateTexture::Resource, uint64_t>>
textures_deleted_;
std::unique_ptr<VulkanUploadBufferPool> vertex_buffer_pool_;
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
VkFormat pipeline_framebuffer_format_ = VK_FORMAT_UNDEFINED;
VkPipeline pipeline_triangle_ = VK_NULL_HANDLE;
VkPipeline pipeline_line_ = VK_NULL_HANDLE;
// The submission index within the current Begin (or the last, if outside
// one).
uint64_t last_paint_submission_index_ = 0;
// Completed submission index as of the latest Begin, to coarsely skip delayed
// texture deletion.
uint64_t last_completed_submission_index_ = 0;
VkCommandBuffer current_command_buffer_ = VK_NULL_HANDLE;
VkExtent2D current_render_target_extent_;
VkRect2D current_scissor_;
VkPipeline current_pipeline_;
uint32_t current_texture_descriptor_index_;
bool batch_open_ = false;
bool batch_has_index_buffer_;
};
} // namespace vulkan

View File

@@ -1,634 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_instance.h"
#include <cinttypes>
#include <cstring>
#include <mutex>
#include <string>
#include "third_party/renderdoc/renderdoc_app.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/window.h"
#if XE_PLATFORM_LINUX
#include <dlfcn.h>
#elif XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#endif
#if XE_PLATFORM_GNU_LINUX
#include "xenia/ui/window_gtk.h"
#endif
#define VK_API_VERSION VK_API_VERSION_1_1
namespace xe {
namespace ui {
namespace vulkan {
VulkanInstance::VulkanInstance() {
if (cvars::vulkan_validation) {
DeclareRequiredLayer("VK_LAYER_LUNARG_standard_validation",
Version::Make(0, 0, 0), true);
// DeclareRequiredLayer("VK_LAYER_GOOGLE_unique_objects", Version::Make(0,
// 0, 0), true);
/*
DeclareRequiredLayer("VK_LAYER_GOOGLE_threading", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_core_validation",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_object_tracker",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_draw_state", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_parameter_validation",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_swapchain", Version::Make(0, 0, 0),
true);
DeclareRequiredLayer("VK_LAYER_LUNARG_device_limits",
Version::Make(0, 0, 0), true);
DeclareRequiredLayer("VK_LAYER_LUNARG_image", Version::Make(0, 0, 0), true);
*/
DeclareRequiredExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
Version::Make(0, 0, 0), true);
}
DeclareRequiredExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
Version::Make(0, 0, 0), true);
}
VulkanInstance::~VulkanInstance() { DestroyInstance(); }
bool VulkanInstance::Initialize() {
auto version = Version::Parse(VK_API_VERSION);
XELOGVK("Initializing Vulkan {}...", version.pretty_string);
// Load the library.
bool library_functions_loaded = true;
#if XE_PLATFORM_LINUX
#if XE_PLATFORM_ANDROID
const char* libvulkan_name = "libvulkan.so";
#else
const char* libvulkan_name = "libvulkan.so.1";
#endif
// http://developer.download.nvidia.com/mobile/shield/assets/Vulkan/UsingtheVulkanAPI.pdf
library_ = dlopen(libvulkan_name, RTLD_NOW | RTLD_LOCAL);
if (!library_) {
XELOGE("Failed to load {}", libvulkan_name);
return false;
}
#define XE_VULKAN_LOAD_MODULE_LFN(name) \
library_functions_loaded &= \
(lfn_.name = PFN_##name(dlsym(library_, #name))) != nullptr;
#elif XE_PLATFORM_WIN32
library_ = LoadLibraryA("vulkan-1.dll");
if (!library_) {
XELOGE("Failed to load vulkan-1.dll");
return false;
}
#define XE_VULKAN_LOAD_MODULE_LFN(name) \
library_functions_loaded &= \
(lfn_.name = PFN_##name(GetProcAddress(library_, #name))) != nullptr;
#else
#error No Vulkan library loading provided for the target platform.
#endif
XE_VULKAN_LOAD_MODULE_LFN(vkGetInstanceProcAddr);
XE_VULKAN_LOAD_MODULE_LFN(vkDestroyInstance);
#undef XE_VULKAN_LOAD_MODULE_LFN
if (!library_functions_loaded) {
XELOGE("Failed to get Vulkan library function pointers");
return false;
}
library_functions_loaded &=
(lfn_.vkCreateInstance = PFN_vkCreateInstance(lfn_.vkGetInstanceProcAddr(
VK_NULL_HANDLE, "vkCreateInstance"))) != nullptr;
library_functions_loaded &=
(lfn_.vkEnumerateInstanceExtensionProperties =
PFN_vkEnumerateInstanceExtensionProperties(
lfn_.vkGetInstanceProcAddr(
VK_NULL_HANDLE,
"vkEnumerateInstanceExtensionProperties"))) != nullptr;
library_functions_loaded &=
(lfn_.vkEnumerateInstanceLayerProperties =
PFN_vkEnumerateInstanceLayerProperties(lfn_.vkGetInstanceProcAddr(
VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"))) !=
nullptr;
if (!library_functions_loaded) {
XELOGE(
"Failed to get Vulkan library function pointers via "
"vkGetInstanceProcAddr");
return false;
}
// Get all of the global layers and extensions provided by the system.
if (!QueryGlobals()) {
XELOGE("Failed to query instance globals");
return false;
}
// Create the vulkan instance used by the application with our required
// extensions and layers.
if (!CreateInstance()) {
XELOGE("Failed to create instance");
return false;
}
// Query available devices so that we can pick one.
if (!QueryDevices()) {
XELOGE("Failed to query devices");
return false;
}
// Hook into renderdoc, if it's available.
EnableRenderDoc();
XELOGVK("Instance initialized successfully!");
return true;
}
bool VulkanInstance::EnableRenderDoc() {
// RenderDoc injects itself into our process, so we should be able to get it.
pRENDERDOC_GetAPI get_api = nullptr;
#if XE_PLATFORM_WIN32
auto module_handle = GetModuleHandleW(L"renderdoc.dll");
if (!module_handle) {
XELOGI("RenderDoc support requested but it is not attached");
return false;
}
get_api = reinterpret_cast<pRENDERDOC_GetAPI>(
GetProcAddress(module_handle, "RENDERDOC_GetAPI"));
#else
// TODO(benvanik): dlsym/etc - abstracted in base/.
#endif // XE_PLATFORM_32
if (!get_api) {
XELOGI("RenderDoc support requested but it is not attached");
return false;
}
// Request all API function pointers.
if (!get_api(eRENDERDOC_API_Version_1_0_1,
reinterpret_cast<void**>(&renderdoc_api_))) {
XELOGE("RenderDoc found but was unable to get API - version mismatch?");
return false;
}
auto api = reinterpret_cast<RENDERDOC_API_1_0_1*>(renderdoc_api_);
// Query version.
int major;
int minor;
int patch;
api->GetAPIVersion(&major, &minor, &patch);
XELOGI("RenderDoc attached; {}.{}.{}", major, minor, patch);
is_renderdoc_attached_ = true;
return true;
}
bool VulkanInstance::QueryGlobals() {
// Scan global layers and accumulate properties.
// We do this in a loop so that we can allocate the required amount of
// memory and handle race conditions while querying.
uint32_t count = 0;
std::vector<VkLayerProperties> global_layer_properties;
VkResult err;
do {
err = lfn_.vkEnumerateInstanceLayerProperties(&count, nullptr);
CheckResult(err, "vkEnumerateInstanceLayerProperties");
global_layer_properties.resize(count);
err = lfn_.vkEnumerateInstanceLayerProperties(
&count, global_layer_properties.data());
} while (err == VK_INCOMPLETE);
CheckResult(err, "vkEnumerateInstanceLayerProperties");
global_layers_.resize(count);
for (size_t i = 0; i < global_layers_.size(); ++i) {
auto& global_layer = global_layers_[i];
global_layer.properties = global_layer_properties[i];
// Get all extensions available for the layer.
do {
err = lfn_.vkEnumerateInstanceExtensionProperties(
global_layer.properties.layerName, &count, nullptr);
CheckResult(err, "vkEnumerateInstanceExtensionProperties");
global_layer.extensions.resize(count);
err = lfn_.vkEnumerateInstanceExtensionProperties(
global_layer.properties.layerName, &count,
global_layer.extensions.data());
} while (err == VK_INCOMPLETE);
CheckResult(err, "vkEnumerateInstanceExtensionProperties");
}
XELOGVK("Found {} global layers:", global_layers_.size());
for (size_t i = 0; i < global_layers_.size(); ++i) {
auto& global_layer = global_layers_[i];
auto spec_version = Version::Parse(global_layer.properties.specVersion);
auto impl_version =
Version::Parse(global_layer.properties.implementationVersion);
XELOGVK("- {} (spec: {}, impl: {})", global_layer.properties.layerName,
spec_version.pretty_string, impl_version.pretty_string);
XELOGVK(" {}", global_layer.properties.description);
if (!global_layer.extensions.empty()) {
XELOGVK(" {} extensions:", global_layer.extensions.size());
DumpExtensions(global_layer.extensions, " ");
}
}
// Scan global extensions.
do {
err = lfn_.vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
CheckResult(err, "vkEnumerateInstanceExtensionProperties");
global_extensions_.resize(count);
err = lfn_.vkEnumerateInstanceExtensionProperties(
nullptr, &count, global_extensions_.data());
} while (err == VK_INCOMPLETE);
CheckResult(err, "vkEnumerateInstanceExtensionProperties");
XELOGVK("Found {} global extensions:", global_extensions_.size());
DumpExtensions(global_extensions_, "");
return true;
}
bool VulkanInstance::CreateInstance() {
XELOGVK("Verifying layers and extensions...");
// Gather list of enabled layer names.
auto layers_result = CheckRequirements(required_layers_, global_layers_);
auto& enabled_layers = layers_result.second;
// Gather list of enabled extension names.
auto extensions_result =
CheckRequirements(required_extensions_, global_extensions_);
auto& enabled_extensions = extensions_result.second;
// We wait until both extensions and layers are checked before failing out so
// that the user gets a complete list of what they have/don't.
if (!extensions_result.first || !layers_result.first) {
XELOGE("Layer and extension verification failed; aborting initialization");
return false;
}
XELOGVK("Initializing application instance...");
// TODO(benvanik): use GetEntryInfo?
VkApplicationInfo application_info;
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pNext = nullptr;
application_info.pApplicationName = "xenia";
application_info.applicationVersion = 1;
application_info.pEngineName = "xenia";
application_info.engineVersion = 1;
application_info.apiVersion = VK_API_VERSION;
VkInstanceCreateInfo instance_info;
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_info.pNext = nullptr;
instance_info.flags = 0;
instance_info.pApplicationInfo = &application_info;
instance_info.enabledLayerCount =
static_cast<uint32_t>(enabled_layers.size());
instance_info.ppEnabledLayerNames = enabled_layers.data();
instance_info.enabledExtensionCount =
static_cast<uint32_t>(enabled_extensions.size());
instance_info.ppEnabledExtensionNames = enabled_extensions.data();
auto err = lfn_.vkCreateInstance(&instance_info, nullptr, &handle);
if (err != VK_SUCCESS) {
XELOGE("vkCreateInstance returned {}", to_string(err));
}
switch (err) {
case VK_SUCCESS:
// Ok!
break;
case VK_ERROR_INITIALIZATION_FAILED:
XELOGE("Instance initialization failed; generic");
return false;
case VK_ERROR_INCOMPATIBLE_DRIVER:
XELOGE(
"Instance initialization failed; cannot find a compatible Vulkan "
"installable client driver (ICD)");
return false;
case VK_ERROR_EXTENSION_NOT_PRESENT:
XELOGE("Instance initialization failed; requested extension not present");
return false;
case VK_ERROR_LAYER_NOT_PRESENT:
XELOGE("Instance initialization failed; requested layer not present");
return false;
default:
XELOGE("Instance initialization failed; unknown: {}", to_string(err));
return false;
}
// Check if extensions are enabled.
dbg_report_ena_ = false;
for (const char* enabled_extension : enabled_extensions) {
if (!dbg_report_ena_ &&
!std::strcmp(enabled_extension, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) {
dbg_report_ena_ = true;
}
}
// Get instance functions.
std::memset(&ifn_, 0, sizeof(ifn_));
bool instance_functions_loaded = true;
#define XE_UI_VULKAN_FUNCTION(name) \
instance_functions_loaded &= \
(ifn_.name = PFN_##name(lfn_.vkGetInstanceProcAddr(handle, #name))) != \
nullptr;
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
#if XE_PLATFORM_ANDROID
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
#elif XE_PLATFORM_GNU_LINUX
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
#elif XE_PLATFORM_WIN32
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
#endif
if (dbg_report_ena_) {
#include "xenia/ui/vulkan/functions/instance_ext_debug_report.inc"
}
#undef XE_VULKAN_LOAD_IFN
if (!instance_functions_loaded) {
XELOGE("Failed to get Vulkan instance function pointers");
return false;
}
// Enable debug validation, if needed.
EnableDebugValidation();
return true;
}
void VulkanInstance::DestroyInstance() {
if (handle) {
DisableDebugValidation();
lfn_.vkDestroyInstance(handle, nullptr);
handle = nullptr;
}
#if XE_PLATFORM_LINUX
if (library_) {
dlclose(library_);
library_ = nullptr;
}
#elif XE_PLATFORM_WIN32
if (library_) {
FreeLibrary(library_);
library_ = nullptr;
}
#endif
}
VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object, size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage, void* pUserData) {
if (strcmp(pLayerPrefix, "Validation") == 0) {
const char* blacklist[] = {
"bound but it was never updated. You may want to either update it or "
"not bind it.",
"is being used in draw but has not been updated.",
};
for (uint32_t i = 0; i < xe::countof(blacklist); ++i) {
if (strstr(pMessage, blacklist[i]) != nullptr) {
return false;
}
}
}
auto instance = reinterpret_cast<VulkanInstance*>(pUserData);
const char* message_type = "UNKNOWN";
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
message_type = "ERROR";
} else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
message_type = "WARN";
} else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
message_type = "PERF WARN";
} else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
message_type = "INFO";
} else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
message_type = "DEBUG";
}
XELOGVK("[{}/{}:{}] {}", pLayerPrefix, message_type, messageCode, pMessage);
return false;
}
void VulkanInstance::EnableDebugValidation() {
if (!dbg_report_ena_) {
XELOGVK("Debug validation layer not installed; ignoring");
return;
}
if (dbg_report_callback_) {
DisableDebugValidation();
}
VkDebugReportCallbackCreateInfoEXT create_info;
create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
create_info.pNext = nullptr;
// TODO(benvanik): flags to set these.
create_info.flags =
VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT |
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT;
create_info.pfnCallback = &DebugMessageCallback;
create_info.pUserData = this;
auto status = ifn_.vkCreateDebugReportCallbackEXT(
handle, &create_info, nullptr, &dbg_report_callback_);
if (status == VK_SUCCESS) {
XELOGVK("Debug validation layer enabled");
} else {
XELOGVK("Debug validation layer failed to install; error {}",
to_string(status));
}
}
void VulkanInstance::DisableDebugValidation() {
if (!dbg_report_ena_ || !dbg_report_callback_) {
return;
}
ifn_.vkDestroyDebugReportCallbackEXT(handle, dbg_report_callback_, nullptr);
dbg_report_callback_ = nullptr;
}
bool VulkanInstance::QueryDevices() {
// Get handles to all devices.
uint32_t count = 0;
std::vector<VkPhysicalDevice> device_handles;
auto err = ifn_.vkEnumeratePhysicalDevices(handle, &count, nullptr);
CheckResult(err, "vkEnumeratePhysicalDevices");
device_handles.resize(count);
err = ifn_.vkEnumeratePhysicalDevices(handle, &count, device_handles.data());
CheckResult(err, "vkEnumeratePhysicalDevices");
// Query device info.
for (size_t i = 0; i < device_handles.size(); ++i) {
auto device_handle = device_handles[i];
DeviceInfo device_info;
device_info.handle = device_handle;
// Query general attributes.
ifn_.vkGetPhysicalDeviceProperties(device_handle, &device_info.properties);
ifn_.vkGetPhysicalDeviceFeatures(device_handle, &device_info.features);
ifn_.vkGetPhysicalDeviceMemoryProperties(device_handle,
&device_info.memory_properties);
// Gather queue family properties.
ifn_.vkGetPhysicalDeviceQueueFamilyProperties(device_handle, &count,
nullptr);
device_info.queue_family_properties.resize(count);
ifn_.vkGetPhysicalDeviceQueueFamilyProperties(
device_handle, &count, device_info.queue_family_properties.data());
// Gather layers.
std::vector<VkLayerProperties> layer_properties;
err = ifn_.vkEnumerateDeviceLayerProperties(device_handle, &count, nullptr);
CheckResult(err, "vkEnumerateDeviceLayerProperties");
layer_properties.resize(count);
err = ifn_.vkEnumerateDeviceLayerProperties(device_handle, &count,
layer_properties.data());
CheckResult(err, "vkEnumerateDeviceLayerProperties");
for (size_t j = 0; j < layer_properties.size(); ++j) {
LayerInfo layer_info;
layer_info.properties = layer_properties[j];
err = ifn_.vkEnumerateDeviceExtensionProperties(
device_handle, layer_info.properties.layerName, &count, nullptr);
CheckResult(err, "vkEnumerateDeviceExtensionProperties");
layer_info.extensions.resize(count);
err = ifn_.vkEnumerateDeviceExtensionProperties(
device_handle, layer_info.properties.layerName, &count,
layer_info.extensions.data());
CheckResult(err, "vkEnumerateDeviceExtensionProperties");
device_info.layers.push_back(std::move(layer_info));
}
// Gather extensions.
err = ifn_.vkEnumerateDeviceExtensionProperties(device_handle, nullptr,
&count, nullptr);
CheckResult(err, "vkEnumerateDeviceExtensionProperties");
device_info.extensions.resize(count);
err = ifn_.vkEnumerateDeviceExtensionProperties(
device_handle, nullptr, &count, device_info.extensions.data());
CheckResult(err, "vkEnumerateDeviceExtensionProperties");
available_devices_.push_back(std::move(device_info));
}
XELOGVK("Found {} physical devices:", available_devices_.size());
for (size_t i = 0; i < available_devices_.size(); ++i) {
auto& device_info = available_devices_[i];
XELOGVK("- Device {}:", i);
DumpDeviceInfo(device_info);
}
return true;
}
void VulkanInstance::DumpLayers(const std::vector<LayerInfo>& layers,
const char* indent) {
for (size_t i = 0; i < layers.size(); ++i) {
auto& layer = layers[i];
auto spec_version = Version::Parse(layer.properties.specVersion);
auto impl_version = Version::Parse(layer.properties.implementationVersion);
XELOGVK("{}- {} (spec: {}, impl: {})", indent, layer.properties.layerName,
spec_version.pretty_string, impl_version.pretty_string);
XELOGVK("{} {}", indent, layer.properties.description);
if (!layer.extensions.empty()) {
XELOGVK("{} {} extensions:", indent, layer.extensions.size());
DumpExtensions(layer.extensions, std::strlen(indent) ? " " : " ");
}
}
}
void VulkanInstance::DumpExtensions(
const std::vector<VkExtensionProperties>& extensions, const char* indent) {
for (size_t i = 0; i < extensions.size(); ++i) {
auto& extension = extensions[i];
auto version = Version::Parse(extension.specVersion);
XELOGVK("{}- {} ({})", indent, extension.extensionName,
version.pretty_string);
}
}
void VulkanInstance::DumpDeviceInfo(const DeviceInfo& device_info) {
auto& properties = device_info.properties;
auto api_version = Version::Parse(properties.apiVersion);
auto driver_version = Version::Parse(properties.driverVersion);
XELOGVK(" apiVersion = {}", api_version.pretty_string);
XELOGVK(" driverVersion = {}", driver_version.pretty_string);
XELOGVK(" vendorId = {:#04x}", properties.vendorID);
XELOGVK(" deviceId = {:#04x}", properties.deviceID);
XELOGVK(" deviceType = {}", to_string(properties.deviceType));
XELOGVK(" deviceName = {}", properties.deviceName);
auto& memory_props = device_info.memory_properties;
XELOGVK(" Memory Heaps:");
for (size_t j = 0; j < memory_props.memoryHeapCount; ++j) {
XELOGVK(" - Heap {}: {} bytes", j, memory_props.memoryHeaps[j].size);
for (size_t k = 0; k < memory_props.memoryTypeCount; ++k) {
if (memory_props.memoryTypes[k].heapIndex == j) {
XELOGVK(" - Type {}:", k);
auto type_flags = memory_props.memoryTypes[k].propertyFlags;
if (!type_flags) {
XELOGVK(" VK_MEMORY_PROPERTY_DEVICE_ONLY");
}
if (type_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
XELOGVK(" VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT");
}
if (type_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
XELOGVK(" VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT");
}
if (type_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
XELOGVK(" VK_MEMORY_PROPERTY_HOST_COHERENT_BIT");
}
if (type_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
XELOGVK(" VK_MEMORY_PROPERTY_HOST_CACHED_BIT");
}
if (type_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
XELOGVK(" VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT");
}
}
}
}
XELOGVK(" Queue Families:");
for (size_t j = 0; j < device_info.queue_family_properties.size(); ++j) {
auto& queue_props = device_info.queue_family_properties[j];
XELOGVK(" - Queue {}:", j);
XELOGVK(
" queueFlags = {}{}{}{}",
(queue_props.queueFlags & VK_QUEUE_GRAPHICS_BIT) ? "graphics, " : "",
(queue_props.queueFlags & VK_QUEUE_COMPUTE_BIT) ? "compute, " : "",
(queue_props.queueFlags & VK_QUEUE_TRANSFER_BIT) ? "transfer, " : "",
(queue_props.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) ? "sparse, "
: "");
XELOGVK(" queueCount = {}", queue_props.queueCount);
XELOGVK(" timestampValidBits = {}", queue_props.timestampValidBits);
}
XELOGVK(" Layers:");
DumpLayers(device_info.layers, " ");
XELOGVK(" Extensions:");
DumpExtensions(device_info.extensions, " ");
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,145 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_INSTANCE_H_
#define XENIA_UI_VULKAN_VULKAN_INSTANCE_H_
#include <memory>
#include <string>
#include <vector>
#include "xenia/base/platform.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/window.h"
namespace xe {
namespace ui {
namespace vulkan {
// Wrappers and utilities for VkInstance.
class VulkanInstance {
public:
VulkanInstance();
~VulkanInstance();
struct LibraryFunctions {
// From the module.
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkDestroyInstance vkDestroyInstance;
// From vkGetInstanceProcAddr.
PFN_vkCreateInstance vkCreateInstance;
PFN_vkEnumerateInstanceExtensionProperties
vkEnumerateInstanceExtensionProperties;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
};
const LibraryFunctions& lfn() const { return lfn_; }
VkInstance handle = nullptr;
operator VkInstance() const { return handle; }
struct InstanceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
#include "xenia/ui/vulkan/functions/instance_ext_debug_report.inc"
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
#if XE_PLATFORM_ANDROID
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
#elif XE_PLATFORM_GNU_LINUX
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
#elif XE_PLATFORM_WIN32
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
#endif
#undef XE_UI_VULKAN_FUNCTION
};
const InstanceFunctions& ifn() const { return ifn_; }
// Declares a layer to verify and enable upon initialization.
// Must be called before Initialize.
void DeclareRequiredLayer(std::string name, uint32_t min_version,
bool is_optional) {
required_layers_.push_back({name, min_version, is_optional});
}
// Declares an extension to verify and enable upon initialization.
// Must be called before Initialize.
void DeclareRequiredExtension(std::string name, uint32_t min_version,
bool is_optional) {
required_extensions_.push_back({name, min_version, is_optional});
}
// Initializes the instance, querying and enabling extensions and layers and
// preparing the instance for general use.
// If initialization succeeds it's likely that no more failures beyond runtime
// issues will occur.
bool Initialize();
// Returns a list of all available devices as detected during initialization.
const std::vector<DeviceInfo>& available_devices() const {
return available_devices_;
}
// True if RenderDoc is attached and available for use.
bool is_renderdoc_attached() const { return is_renderdoc_attached_; }
// RenderDoc API handle, if attached.
void* renderdoc_api() const { return renderdoc_api_; }
private:
// Attempts to enable RenderDoc via the API, if it is attached.
bool EnableRenderDoc();
// Queries the system to find global extensions and layers.
bool QueryGlobals();
// Creates the instance, enabling required extensions and layers.
bool CreateInstance();
void DestroyInstance();
// Enables debugging info and callbacks for supported layers.
void EnableDebugValidation();
void DisableDebugValidation();
// Queries all available physical devices.
bool QueryDevices();
void DumpLayers(const std::vector<LayerInfo>& layers, const char* indent);
void DumpExtensions(const std::vector<VkExtensionProperties>& extensions,
const char* indent);
void DumpDeviceInfo(const DeviceInfo& device_info);
#if XE_PLATFORM_LINUX
void* library_ = nullptr;
#elif XE_PLATFORM_WIN32
HMODULE library_ = nullptr;
#endif
LibraryFunctions lfn_ = {};
std::vector<Requirement> required_layers_;
std::vector<Requirement> required_extensions_;
InstanceFunctions ifn_ = {};
std::vector<LayerInfo> global_layers_;
std::vector<VkExtensionProperties> global_extensions_;
std::vector<DeviceInfo> available_devices_;
bool dbg_report_ena_ = false;
VkDebugReportCallbackEXT dbg_report_callback_ = nullptr;
void* renderdoc_api_ = nullptr;
bool is_renderdoc_attached_ = false;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_INSTANCE_H_

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 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. *
******************************************************************************
*/
@@ -10,20 +10,32 @@
#ifndef XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_
#define XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#include "third_party/vulkan/vk_mem_alloc.h"
// Make sure vulkan.h is included from third_party (rather than from the system
// include directory) before vk_mem_alloc.h.
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_instance.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#define VMA_STATIC_VULKAN_FUNCTIONS 0
// Work around the pointer nullability completeness warnings on Clang.
#ifndef VMA_NULLABLE
#define VMA_NULLABLE
#endif
#ifndef VMA_NOT_NULL
#define VMA_NOT_NULL
#endif
#include "third_party/vulkan/vk_mem_alloc.h"
namespace xe {
namespace ui {
namespace vulkan {
inline void FillVMAVulkanFunctions(VmaVulkanFunctions* vma_funcs,
const VulkanDevice& device) {
const VulkanInstance::InstanceFunctions& ifn = device.instance()->ifn();
const VulkanDevice::DeviceFunctions& dfn = device.dfn();
const VulkanProvider& provider) {
const VulkanProvider::LibraryFunctions& lfn = provider.lfn();
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
vma_funcs->vkGetInstanceProcAddr = lfn.vkGetInstanceProcAddr;
vma_funcs->vkGetDeviceProcAddr = ifn.vkGetDeviceProcAddr;
vma_funcs->vkGetPhysicalDeviceProperties = ifn.vkGetPhysicalDeviceProperties;
vma_funcs->vkGetPhysicalDeviceMemoryProperties =
ifn.vkGetPhysicalDeviceMemoryProperties;
@@ -31,6 +43,9 @@ inline void FillVMAVulkanFunctions(VmaVulkanFunctions* vma_funcs,
vma_funcs->vkFreeMemory = dfn.vkFreeMemory;
vma_funcs->vkMapMemory = dfn.vkMapMemory;
vma_funcs->vkUnmapMemory = dfn.vkUnmapMemory;
vma_funcs->vkFlushMappedMemoryRanges = dfn.vkFlushMappedMemoryRanges;
vma_funcs->vkInvalidateMappedMemoryRanges =
dfn.vkInvalidateMappedMemoryRanges;
vma_funcs->vkBindBufferMemory = dfn.vkBindBufferMemory;
vma_funcs->vkBindImageMemory = dfn.vkBindImageMemory;
vma_funcs->vkGetBufferMemoryRequirements = dfn.vkGetBufferMemoryRequirements;
@@ -39,6 +54,7 @@ inline void FillVMAVulkanFunctions(VmaVulkanFunctions* vma_funcs,
vma_funcs->vkDestroyBuffer = dfn.vkDestroyBuffer;
vma_funcs->vkCreateImage = dfn.vkCreateImage;
vma_funcs->vkDestroyImage = dfn.vkDestroyImage;
vma_funcs->vkCmdCopyBuffer = dfn.vkCmdCopyBuffer;
}
} // namespace vulkan

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,518 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_PRESENTER_H_
#define XENIA_UI_VULKAN_VULKAN_PRESENTER_H_
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/surface.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanUIDrawContext final : public UIDrawContext {
public:
VulkanUIDrawContext(Presenter& presenter, uint32_t render_target_width,
uint32_t render_target_height,
VkCommandBuffer draw_command_buffer,
uint64_t submission_index_current,
uint64_t submission_index_completed,
VkRenderPass render_pass, VkFormat render_pass_format)
: UIDrawContext(presenter, render_target_width, render_target_height),
draw_command_buffer_(draw_command_buffer),
submission_index_current_(submission_index_current),
submission_index_completed_(submission_index_completed),
render_pass_(render_pass),
render_pass_format_(render_pass_format) {}
VkCommandBuffer draw_command_buffer() const { return draw_command_buffer_; }
uint64_t submission_index_current() const {
return submission_index_current_;
}
uint64_t submission_index_completed() const {
return submission_index_completed_;
}
VkRenderPass render_pass() const { return render_pass_; }
VkFormat render_pass_format() const { return render_pass_format_; }
private:
VkCommandBuffer draw_command_buffer_;
uint64_t submission_index_current_;
uint64_t submission_index_completed_;
// Has 1 subpass with a single render_pass_format_ attachment.
VkRenderPass render_pass_;
VkFormat render_pass_format_;
};
class VulkanPresenter final : public Presenter {
public:
// Maximum number of different guest output image versions still potentially
// considered alive that may be given to the refresher - this many instances
// of dependent objects (such as framebuffers) may need to be kept by the
// refresher across invocations (due to multiple-buffering of guest output
// images inside the presenter, different versions may be given even every
// invocation), to avoid recreation of dependent objects every frame.
static constexpr size_t kMaxActiveGuestOutputImageVersions =
kGuestOutputMailboxSize;
static constexpr VkFormat kGuestOutputFormat =
VK_FORMAT_A2B10G10R10_UNORM_PACK32;
// The guest output is expected to be acquired and released in this state by
// the refresher. The exception is the first write to the current guest output
// image - in this case, a barrier is only needed from
// VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT without access. Also if the image is
// being refreshed for the first time, it's in VK_IMAGE_LAYOUT_UNDEFINED (but
// it's safe, and preferred, to transition it from VK_IMAGE_LAYOUT_UNDEFINED
// when writing to it in general).
static constexpr VkPipelineStageFlagBits kGuestOutputInternalStageMask =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
static constexpr VkAccessFlags kGuestOutputInternalAccessMask =
VK_ACCESS_SHADER_READ_BIT;
static constexpr VkImageLayout kGuestOutputInternalLayout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// The callback must use the graphics and compute queue 0 of the provider.
class VulkanGuestOutputRefreshContext final
: public GuestOutputRefreshContext {
public:
VulkanGuestOutputRefreshContext(bool& is_8bpc_out_ref, VkImage image,
VkImageView image_view,
uint64_t image_version,
bool image_ever_written_previously)
: GuestOutputRefreshContext(is_8bpc_out_ref),
image_(image),
image_view_(image_view),
image_version_(image_version),
image_ever_written_previously_(image_ever_written_previously) {}
// The format is kGuestOutputFormat.
// Supports usage as a color attachment and as a sampled image, as well as
// transfer source (but the reason of that is guest output capturing).
VkImage image() const { return image_; }
VkImageView image_view() const { return image_view_; }
uint64_t image_version() const { return image_version_; }
// Whether a proper barrier must be done to acquire the image.
bool image_ever_written_previously() const {
return image_ever_written_previously_;
}
private:
VkImage image_;
VkImageView image_view_;
uint64_t image_version_;
bool image_ever_written_previously_;
};
static std::unique_ptr<VulkanPresenter> Create(
HostGpuLossCallback host_gpu_loss_callback, VulkanProvider& provider) {
auto presenter = std::unique_ptr<VulkanPresenter>(
new VulkanPresenter(host_gpu_loss_callback, provider));
if (!presenter->InitializeSurfaceIndependent()) {
return nullptr;
}
return presenter;
}
~VulkanPresenter();
VulkanProvider& provider() const { return provider_; }
static Surface::TypeFlags GetSurfaceTypesSupportedByInstance(
const VulkanProvider::InstanceExtensions& instance_extensions) {
if (!instance_extensions.khr_surface) {
return 0;
}
Surface::TypeFlags type_flags = 0;
#if XE_PLATFORM_ANDROID
if (instance_extensions.khr_android_surface) {
type_flags |= Surface::kTypeFlag_AndroidNativeWindow;
}
#elif XE_PLATFORM_GNU_LINUX
if (instance_extensions.khr_xcb_surface) {
type_flags |= Surface::kTypeFlag_XcbWindow;
}
#elif XE_PLATFORM_WIN32
if (instance_extensions.khr_win32_surface) {
type_flags |= Surface::kTypeFlag_Win32Hwnd;
}
#endif
return type_flags;
}
Surface::TypeFlags GetSupportedSurfaceTypes() const override;
bool CaptureGuestOutput(RawImage& image_out) override;
void AwaitUISubmissionCompletionFromUIThread(uint64_t submission_index) {
ui_submission_tracker_.AwaitSubmissionCompletion(submission_index);
}
VkCommandBuffer AcquireUISetupCommandBufferFromUIThread();
protected:
SurfacePaintConnectResult ConnectOrReconnectPaintingToSurfaceFromUIThread(
Surface& new_surface, uint32_t new_surface_width,
uint32_t new_surface_height, bool was_paintable,
bool& is_vsync_implicit_out) override;
void DisconnectPaintingFromSurfaceFromUIThreadImpl() override;
bool RefreshGuestOutputImpl(
uint32_t mailbox_index, uint32_t frontbuffer_width,
uint32_t frontbuffer_height,
std::function<bool(GuestOutputRefreshContext& context)> refresher,
bool& is_8bpc_out_ref) override;
PaintResult PaintAndPresentImpl(bool execute_ui_drawers) override;
private:
// Usable for both the guest output image itself and for intermediate images.
class GuestOutputImage {
public:
static std::unique_ptr<GuestOutputImage> Create(
const VulkanProvider& provider, uint32_t width, uint32_t height) {
assert_not_zero(width);
assert_not_zero(height);
auto image = std::unique_ptr<GuestOutputImage>(
new GuestOutputImage(provider, width, height));
if (!image->Initialize()) {
return nullptr;
}
return std::move(image);
}
GuestOutputImage(const GuestOutputImage& image) = delete;
GuestOutputImage& operator=(const GuestOutputImage& image) = delete;
~GuestOutputImage();
const VkExtent2D& extent() const { return extent_; }
VkImage image() const { return image_; }
VkDeviceMemory memory() const { return memory_; }
VkImageView view() const { return view_; }
private:
GuestOutputImage(const VulkanProvider& provider, uint32_t width,
uint32_t height)
: provider_(provider) {
extent_.width = width;
extent_.height = height;
}
bool Initialize();
const VulkanProvider& provider_;
VkExtent2D extent_;
VkImage image_ = VK_NULL_HANDLE;
VkDeviceMemory memory_ = VK_NULL_HANDLE;
VkImageView view_ = VK_NULL_HANDLE;
};
struct GuestOutputImageInstance {
// Refresher-side reference (painting has its own references for the purpose
// of destruction only after painting is done on the GPU).
std::shared_ptr<GuestOutputImage> image;
uint64_t version = UINT64_MAX;
uint64_t last_refresher_submission = 0;
// For choosing the barrier stage and access mask and layout depending on
// whether the image has previously been written. If an image is active
// after a refresh, it can be assumed that this is true.
bool ever_successfully_refreshed = false;
void SetToNewImage(const std::shared_ptr<GuestOutputImage>& new_image,
uint64_t new_version) {
image = new_image;
version = new_version;
last_refresher_submission = 0;
ever_successfully_refreshed = false;
}
};
struct GuestOutputPaintRectangleConstants {
union {
struct {
float x;
float y;
};
float offset[2];
};
union {
struct {
float width;
float height;
};
float size[2];
};
};
enum GuestOutputPaintPipelineLayoutIndex : size_t {
kGuestOutputPaintPipelineLayoutIndexBilinear,
kGuestOutputPaintPipelineLayoutIndexCasSharpen,
kGuestOutputPaintPipelineLayoutIndexCasResample,
kGuestOutputPaintPipelineLayoutIndexFsrEasu,
kGuestOutputPaintPipelineLayoutIndexFsrRcas,
kGuestOutputPaintPipelineLayoutCount,
};
static constexpr GuestOutputPaintPipelineLayoutIndex
GetGuestOutputPaintPipelineLayoutIndex(GuestOutputPaintEffect effect) {
switch (effect) {
case GuestOutputPaintEffect::kBilinear:
case GuestOutputPaintEffect::kBilinearDither:
return kGuestOutputPaintPipelineLayoutIndexBilinear;
case GuestOutputPaintEffect::kCasSharpen:
case GuestOutputPaintEffect::kCasSharpenDither:
return kGuestOutputPaintPipelineLayoutIndexCasSharpen;
case GuestOutputPaintEffect::kCasResample:
case GuestOutputPaintEffect::kCasResampleDither:
return kGuestOutputPaintPipelineLayoutIndexCasResample;
case GuestOutputPaintEffect::kFsrEasu:
return kGuestOutputPaintPipelineLayoutIndexFsrEasu;
case GuestOutputPaintEffect::kFsrRcas:
case GuestOutputPaintEffect::kFsrRcasDither:
return kGuestOutputPaintPipelineLayoutIndexFsrRcas;
default:
assert_unhandled_case(effect);
return kGuestOutputPaintPipelineLayoutCount;
}
}
struct PaintContext {
class Submission {
public:
static std::unique_ptr<Submission> Create(
const VulkanProvider& provider) {
auto submission = std::unique_ptr<Submission>(new Submission(provider));
if (!submission->Initialize()) {
return nullptr;
}
return submission;
}
Submission(const Submission& submission) = delete;
Submission& operator=(const Submission& submission) = delete;
~Submission();
VkSemaphore acquire_semaphore() const { return acquire_semaphore_; }
VkSemaphore present_semaphore() const { return present_semaphore_; }
VkCommandPool draw_command_pool() const { return draw_command_pool_; }
VkCommandBuffer draw_command_buffer() const {
return draw_command_buffer_;
}
private:
explicit Submission(const VulkanProvider& provider)
: provider_(provider) {}
bool Initialize();
const VulkanProvider& provider_;
VkSemaphore acquire_semaphore_ = VK_NULL_HANDLE;
VkSemaphore present_semaphore_ = VK_NULL_HANDLE;
VkCommandPool draw_command_pool_ = VK_NULL_HANDLE;
VkCommandBuffer draw_command_buffer_ = VK_NULL_HANDLE;
};
static constexpr uint32_t kSubmissionCount = 3;
struct GuestOutputPaintPipeline {
// Created on initialization.
VkPipeline intermediate_pipeline = VK_NULL_HANDLE;
// Created during guest output painting (after awaiting the last guest
// output paint if outdated and needs to be recreated), when needed, for
// the up-to-date render pass that draws to the swapchain with the actual
// image format.
VkPipeline swapchain_pipeline = VK_NULL_HANDLE;
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
};
enum GuestOutputDescriptorSet : uint32_t {
kGuestOutputDescriptorSetGuestOutput0Sampled,
kGuestOutputDescriptorSetIntermediate0Sampled =
kGuestOutputDescriptorSetGuestOutput0Sampled +
kGuestOutputMailboxSize,
kGuestOutputDescriptorSetCount =
kGuestOutputDescriptorSetIntermediate0Sampled +
kMaxGuestOutputPaintEffects - 1,
};
struct UISetupCommandBuffer {
UISetupCommandBuffer(VkCommandPool command_pool,
VkCommandBuffer command_buffer,
uint64_t last_usage_submission_index = 0)
: command_pool(command_pool),
command_buffer(command_buffer),
last_usage_submission_index(last_usage_submission_index) {}
VkCommandPool command_pool;
VkCommandBuffer command_buffer;
uint64_t last_usage_submission_index;
};
struct SwapchainFramebuffer {
SwapchainFramebuffer(VkImageView image_view, VkFramebuffer framebuffer)
: image_view(image_view), framebuffer(framebuffer) {}
VkImageView image_view;
VkFramebuffer framebuffer;
};
explicit PaintContext(VulkanProvider& provider)
: provider(provider), submission_tracker(provider) {}
PaintContext(const PaintContext& paint_context) = delete;
PaintContext& operator=(const PaintContext& paint_context) = delete;
// The old swapchain, if passed, should be assumed to be retired after this
// call (though it may fail before the vkCreateSwapchainKHR that will
// technically retire it, so it will be in an undefined state), and needs to
// be destroyed externally no matter what the result is.
static VkSwapchainKHR CreateSwapchainForVulkanSurface(
const VulkanProvider& provider, VkSurfaceKHR surface, uint32_t width,
uint32_t height, VkSwapchainKHR old_swapchain,
uint32_t& present_queue_family_out, VkFormat& image_format_out,
VkExtent2D& image_extent_out, bool& is_fifo_out,
bool& ui_surface_unusable_out);
// Destroys the swapchain and its derivatives, nulls `swapchain` and returns
// the original swapchain object, if it existed, for use as oldSwapchain if
// needed and for destruction.
VkSwapchainKHR PrepareForSwapchainRetirement();
// May be called from the destructor of the presenter.
void DestroySwapchainAndVulkanSurface();
// Connection-indepedent.
const VulkanProvider& provider;
std::array<std::unique_ptr<PaintContext::Submission>, kSubmissionCount>
submissions;
VulkanSubmissionTracker submission_tracker;
std::array<GuestOutputPaintPipeline, size_t(GuestOutputPaintEffect::kCount)>
guest_output_paint_pipelines;
VkDescriptorPool guest_output_descriptor_pool = VK_NULL_HANDLE;
// Descriptors are updated while painting if they're out of date.
VkDescriptorSet
guest_output_descriptor_sets[kGuestOutputDescriptorSetCount];
// Refreshed and cleaned up during guest output painting. The first is the
// paint submission index in which the guest output image (and its
// descriptors) was last used, the second is the reference to the image,
// which may be null. The indices are not mailbox indices here, rather, if
// the reference is not in this array yet, the most outdated reference, if
// needed, is replaced with the new one, awaiting the usage completion of
// the last paint usage.
std::array<std::pair<uint64_t, std::shared_ptr<GuestOutputImage>>,
kGuestOutputMailboxSize>
guest_output_image_paint_refs;
// The latest submission index at which any guest output image was drawn.
uint64_t guest_output_image_paint_last_submission = 0;
// Current intermediate images for guest output painting, refreshed when
// painting guest output.
std::array<std::unique_ptr<GuestOutputImage>,
kMaxGuestOutputPaintEffects - 1>
guest_output_intermediate_images;
// Created and destroyed alongside the images. UNORM only.
std::array<VkFramebuffer, kMaxGuestOutputPaintEffects - 1>
guest_output_intermediate_framebuffers = {};
uint64_t guest_output_intermediate_image_last_submission = 0;
// Command buffers optionally executed before the draw command buffer,
// outside the painting render pass.
std::vector<UISetupCommandBuffer> ui_setup_command_buffers;
size_t ui_setup_command_buffer_current_index = SIZE_MAX;
// Connection-specific.
// May be reused between connections if the format stays the same.
VkRenderPass swapchain_render_pass = VK_NULL_HANDLE;
VkFormat swapchain_render_pass_format = VK_FORMAT_UNDEFINED;
bool swapchain_render_pass_clear_load_op = false;
VkSurfaceKHR vulkan_surface = VK_NULL_HANDLE;
uint32_t present_queue_family = UINT32_MAX;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkExtent2D swapchain_extent = {};
bool swapchain_is_fifo = false;
std::vector<VkImage> swapchain_images;
std::vector<SwapchainFramebuffer> swapchain_framebuffers;
};
explicit VulkanPresenter(HostGpuLossCallback host_gpu_loss_callback,
VulkanProvider& provider)
: Presenter(host_gpu_loss_callback),
provider_(provider),
guest_output_image_refresher_submission_tracker_(provider),
ui_submission_tracker_(provider),
paint_context_(provider) {}
bool InitializeSurfaceIndependent();
[[nodiscard]] VkPipeline CreateGuestOutputPaintPipeline(
GuestOutputPaintEffect effect, VkRenderPass render_pass);
VulkanProvider& provider_;
// Static objects for guest output presentation, used only when painting the
// main target (can be destroyed only after awaiting main target usage
// completion).
VkDescriptorSetLayout guest_output_paint_image_descriptor_set_layout_ =
VK_NULL_HANDLE;
std::array<VkPipelineLayout, kGuestOutputPaintPipelineLayoutCount>
guest_output_paint_pipeline_layouts_ = {};
VkShaderModule guest_output_paint_vs_ = VK_NULL_HANDLE;
std::array<VkShaderModule, size_t(GuestOutputPaintEffect::kCount)>
guest_output_paint_fs_ = {};
// Not compatible with the swapchain render pass even if the format is the
// same due to different dependencies (this is shader read > color
// attachment > shader read).
VkRenderPass guest_output_intermediate_render_pass_ = VK_NULL_HANDLE;
// Value monotonically increased every time a new guest output image is
// initialized, for recreation of dependent objects such as framebuffers in
// the refreshers - saving and comparing the handle in the refresher is not
// enough as Create > Destroy > Create may result in the same handle for
// actually different objects without the refresher being aware of the
// destruction.
uint64_t guest_output_image_next_version_ = 0;
std::array<GuestOutputImageInstance, kGuestOutputMailboxSize>
guest_output_images_;
VulkanSubmissionTracker guest_output_image_refresher_submission_tracker_;
// UI submission tracker with the submission index that can be given to UI
// drawers (accessible from the UI thread only, at any time).
VulkanSubmissionTracker ui_submission_tracker_;
// Accessible only by painting and by surface connection lifetime management
// (ConnectOrReconnectPaintingToSurfaceFromUIThread,
// DisconnectPaintingFromSurfaceFromUIThreadImpl) by the thread doing it, as
// well as by presenter initialization and shutdown.
PaintContext paint_context_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_D3D12_D3D12_PRESENTER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
******************************************************************************
* 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. *
******************************************************************************
*/
@@ -10,37 +10,282 @@
#ifndef XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
#define XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/renderdoc_api.h"
#if XE_PLATFORM_ANDROID
#ifndef VK_USE_PLATFORM_ANDROID_KHR
#define VK_USE_PLATFORM_ANDROID_KHR 1
#endif
#elif XE_PLATFORM_GNU_LINUX
#ifndef VK_USE_PLATFORM_XCB_KHR
#define VK_USE_PLATFORM_XCB_KHR 1
#endif
#elif XE_PLATFORM_WIN32
// Must be included before vulkan.h with VK_USE_PLATFORM_WIN32_KHR because it
// includes Windows.h too.
#include "xenia/base/platform_win.h"
#ifndef VK_USE_PLATFORM_WIN32_KHR
#define VK_USE_PLATFORM_WIN32_KHR 1
#endif
#endif
#ifndef VK_NO_PROTOTYPES
#define VK_NO_PROTOTYPES 1
#endif
#include "third_party/vulkan/vulkan.h"
#define XELOGVK XELOGI
#define XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES 1
namespace xe {
namespace ui {
namespace vulkan {
class VulkanDevice;
class VulkanInstance;
class VulkanProvider : public GraphicsProvider {
public:
~VulkanProvider() override;
~VulkanProvider();
static std::unique_ptr<VulkanProvider> Create();
static std::unique_ptr<VulkanProvider> Create(bool is_surface_required);
VulkanInstance* instance() const { return instance_.get(); }
VulkanDevice* device() const { return device_.get(); }
std::unique_ptr<Presenter> CreatePresenter(
Presenter::HostGpuLossCallback host_gpu_loss_callback =
Presenter::FatalErrorHostGpuLossCallback) override;
std::unique_ptr<GraphicsContext> CreateContext(
Window* target_window) override;
std::unique_ptr<GraphicsContext> CreateOffscreenContext() override;
std::unique_ptr<ImmediateDrawer> CreateImmediateDrawer() override;
protected:
VulkanProvider() = default;
const RenderdocApi& renderdoc_api() const { return renderdoc_api_; }
struct LibraryFunctions {
// From the module.
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkDestroyInstance vkDestroyInstance;
// From vkGetInstanceProcAddr.
PFN_vkCreateInstance vkCreateInstance;
PFN_vkEnumerateInstanceExtensionProperties
vkEnumerateInstanceExtensionProperties;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
struct {
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
} v_1_1;
};
const LibraryFunctions& lfn() const { return lfn_; }
struct InstanceExtensions {
bool ext_debug_utils;
// Core since 1.1.0.
bool khr_get_physical_device_properties2;
// Surface extensions.
bool khr_surface;
#if XE_PLATFORM_ANDROID
bool khr_android_surface;
#elif XE_PLATFORM_GNU_LINUX
bool khr_xcb_surface;
#elif XE_PLATFORM_WIN32
bool khr_win32_surface;
#endif
};
const InstanceExtensions& instance_extensions() const {
return instance_extensions_;
}
VkInstance instance() const { return instance_; }
struct InstanceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##extension_name extension_name;
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
#include "xenia/ui/vulkan/functions/instance_khr_get_physical_device_properties2.inc"
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
#if XE_PLATFORM_ANDROID
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
#elif XE_PLATFORM_GNU_LINUX
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
#elif XE_PLATFORM_WIN32
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
#endif
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const InstanceFunctions& ifn() const { return ifn_; }
VkPhysicalDevice physical_device() const { return physical_device_; }
const VkPhysicalDeviceProperties& device_properties() const {
return device_properties_;
}
const VkPhysicalDeviceFeatures& device_features() const {
return device_features_;
}
struct DeviceExtensions {
bool amd_shader_info;
bool ext_fragment_shader_interlock;
// Core since 1.1.0.
bool khr_dedicated_allocation;
// Core since 1.2.0.
bool khr_image_format_list;
// Core since 1.2.0.
bool khr_shader_float_controls;
// Core since 1.2.0.
bool khr_spirv_1_4;
bool khr_swapchain;
};
const DeviceExtensions& device_extensions() const {
return device_extensions_;
}
uint32_t memory_types_device_local() const {
return memory_types_device_local_;
}
uint32_t memory_types_host_visible() const {
return memory_types_host_visible_;
}
uint32_t memory_types_host_coherent() const {
return memory_types_host_coherent_;
}
uint32_t memory_types_host_cached() const {
return memory_types_host_cached_;
}
struct QueueFamily {
uint32_t queue_first_index = 0;
uint32_t queue_count = 0;
bool potentially_supports_present = false;
};
const std::vector<QueueFamily>& queue_families() const {
return queue_families_;
}
// Required.
uint32_t queue_family_graphics_compute() const {
return queue_family_graphics_compute_;
}
// Optional, if sparse binding is supported (UINT32_MAX otherwise). May be the
// same as queue_family_graphics_compute_.
uint32_t queue_family_sparse_binding() const {
return queue_family_sparse_binding_;
}
const VkPhysicalDeviceFloatControlsPropertiesKHR&
device_float_controls_properties() const {
return device_float_controls_properties_;
}
struct Queue {
VkQueue queue = VK_NULL_HANDLE;
std::recursive_mutex mutex;
};
struct QueueAcquisition {
QueueAcquisition(std::unique_lock<std::recursive_mutex>&& lock,
VkQueue queue)
: lock(std::move(lock)), queue(queue) {}
std::unique_lock<std::recursive_mutex> lock;
VkQueue queue;
};
QueueAcquisition AcquireQueue(uint32_t index) {
Queue& queue = queues_[index];
return QueueAcquisition(std::unique_lock<std::recursive_mutex>(queue.mutex),
queue.queue);
}
QueueAcquisition AcquireQueue(uint32_t family_index, uint32_t index) {
assert_true(family_index != UINT32_MAX);
return AcquireQueue(queue_families_[family_index].queue_first_index +
index);
}
VkDevice device() const { return device_; }
struct DeviceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
#include "xenia/ui/vulkan/functions/device_amd_shader_info.inc"
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
#undef XE_UI_VULKAN_FUNCTION
};
const DeviceFunctions& dfn() const { return dfn_; }
void SetDeviceObjectName(VkObjectType type, uint64_t handle,
const char* name) const;
bool IsSparseBindingSupported() const {
return queue_family_sparse_binding_ != UINT32_MAX;
}
// Samplers that may be useful for host needs. Only these samplers should be
// used in host, non-emulation contexts, because the total number of samplers
// is heavily limited (4000) on Nvidia GPUs - the rest of samplers are
// allocated for emulation.
enum class HostSampler {
kNearestClamp,
kLinearClamp,
kNearestRepeat,
kLinearRepeat,
kCount,
};
VkSampler GetHostSampler(HostSampler sampler) const {
return host_samplers_[size_t(sampler)];
}
private:
explicit VulkanProvider(bool is_surface_required)
: is_surface_required_(is_surface_required) {}
bool Initialize();
std::unique_ptr<VulkanInstance> instance_;
std::unique_ptr<VulkanDevice> device_;
static void AccumulateInstanceExtensions(
size_t properties_count, const VkExtensionProperties* properties,
bool request_debug_utils, InstanceExtensions& instance_extensions,
std::vector<const char*>& instance_extensions_enabled);
static VkBool32 VKAPI_CALL DebugMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
bool is_surface_required_;
RenderdocApi renderdoc_api_;
#if XE_PLATFORM_LINUX
void* library_ = nullptr;
#elif XE_PLATFORM_WIN32
HMODULE library_ = nullptr;
#endif
LibraryFunctions lfn_ = {};
InstanceExtensions instance_extensions_;
VkInstance instance_ = VK_NULL_HANDLE;
InstanceFunctions ifn_;
VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE;
bool debug_names_used_ = false;
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
VkPhysicalDeviceProperties device_properties_;
VkPhysicalDeviceFeatures device_features_;
DeviceExtensions device_extensions_;
uint32_t memory_types_device_local_;
uint32_t memory_types_host_visible_;
uint32_t memory_types_host_coherent_;
uint32_t memory_types_host_cached_;
std::vector<QueueFamily> queue_families_;
uint32_t queue_family_graphics_compute_;
uint32_t queue_family_sparse_binding_;
VkPhysicalDeviceFloatControlsPropertiesKHR device_float_controls_properties_;
VkDevice device_ = VK_NULL_HANDLE;
DeviceFunctions dfn_ = {};
// Queues contain a mutex, can't use std::vector.
std::unique_ptr<Queue[]> queues_;
VkSampler host_samplers_[size_t(HostSampler::kCount)] = {};
};
} // namespace vulkan

View File

@@ -0,0 +1,174 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
#include <cstdint>
#include "xenia/base/assert.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
VulkanSubmissionTracker::FenceAcquisition::~FenceAcquisition() {
if (!submission_tracker_) {
// Dropped submission or left after std::move.
return;
}
assert_true(submission_tracker_->fence_acquired_ == fence_);
if (fence_ != VK_NULL_HANDLE) {
if (signal_failed_) {
// Left in the unsignaled state.
submission_tracker_->fences_reclaimed_.push_back(fence_);
} else {
// Left in the pending state.
submission_tracker_->fences_pending_.emplace_back(
submission_tracker_->submission_current_, fence_);
}
submission_tracker_->fence_acquired_ = VK_NULL_HANDLE;
}
++submission_tracker_->submission_current_;
}
void VulkanSubmissionTracker::Shutdown() {
AwaitAllSubmissionsCompletion();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
for (VkFence fence : fences_reclaimed_) {
dfn.vkDestroyFence(device, fence, nullptr);
}
fences_reclaimed_.clear();
for (const std::pair<uint64_t, VkFence>& fence_pair : fences_pending_) {
dfn.vkDestroyFence(device, fence_pair.second, nullptr);
}
fences_pending_.clear();
assert_true(fence_acquired_ == VK_NULL_HANDLE);
util::DestroyAndNullHandle(dfn.vkDestroyFence, device, fence_acquired_);
}
void VulkanSubmissionTracker::FenceAcquisition::SubmissionFailedOrDropped() {
if (!submission_tracker_) {
return;
}
assert_true(submission_tracker_->fence_acquired_ == fence_);
if (fence_ != VK_NULL_HANDLE) {
submission_tracker_->fences_reclaimed_.push_back(fence_);
}
submission_tracker_->fence_acquired_ = VK_NULL_HANDLE;
fence_ = VK_NULL_HANDLE;
// No submission acquisition from now on, don't increment the current
// submission index as well.
submission_tracker_ = VK_NULL_HANDLE;
}
uint64_t VulkanSubmissionTracker::UpdateAndGetCompletedSubmission() {
if (!fences_pending_.empty()) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
while (!fences_pending_.empty()) {
const std::pair<uint64_t, VkFence>& pending_pair =
fences_pending_.front();
assert_true(pending_pair.first > submission_completed_on_gpu_);
if (dfn.vkGetFenceStatus(device, pending_pair.second) != VK_SUCCESS) {
break;
}
fences_reclaimed_.push_back(pending_pair.second);
submission_completed_on_gpu_ = pending_pair.first;
fences_pending_.pop_front();
}
}
return submission_completed_on_gpu_;
}
bool VulkanSubmissionTracker::AwaitSubmissionCompletion(
uint64_t submission_index) {
// The tracker itself can't give a submission index for a submission that
// hasn't even started being recorded yet, the client has provided a
// completely invalid value or has done overly optimistic math if such an
// index has been obtained somehow.
assert_true(submission_index <= submission_current_);
// Waiting for the current submission is fine if there was a failure or a
// refusal to submit, and the submission index wasn't incremented, but still
// need to release objects referenced in the dropped submission (while
// shutting down, for instance - in this case, waiting for the last successful
// submission, which could have also referenced the objects from the new
// submission - we can't know since the client has already overwritten its
// last usage index, would correctly ensure that GPU usage of the objects is
// not pending). Waiting for successful submissions, but failed signals, will
// result in a true race condition, however, but waiting for the closest
// successful signal is the best approximation - also retrying to signal in
// this case.
// Go from the most recent to wait only for one fence, which includes all the
// preceding ones.
// "Fence signal operations that are defined by vkQueueSubmit additionally
// include in the first synchronization scope all commands that occur earlier
// in submission order."
size_t reclaim_end = fences_pending_.size();
if (reclaim_end) {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
while (reclaim_end) {
const std::pair<uint64_t, VkFence>& pending_pair =
fences_pending_[reclaim_end - 1];
assert_true(pending_pair.first > submission_completed_on_gpu_);
if (pending_pair.first <= submission_index) {
// Wait if requested.
if (dfn.vkWaitForFences(device, 1, &pending_pair.second, VK_TRUE,
UINT64_MAX) == VK_SUCCESS) {
break;
}
}
// Just refresh the completed submission.
if (dfn.vkGetFenceStatus(device, pending_pair.second) == VK_SUCCESS) {
break;
}
--reclaim_end;
}
if (reclaim_end) {
submission_completed_on_gpu_ = fences_pending_[reclaim_end - 1].first;
for (; reclaim_end; --reclaim_end) {
fences_reclaimed_.push_back(fences_pending_.front().second);
fences_pending_.pop_front();
}
}
}
return submission_completed_on_gpu_ == submission_index;
}
VulkanSubmissionTracker::FenceAcquisition
VulkanSubmissionTracker::AcquireFenceToAdvanceSubmission() {
assert_true(fence_acquired_ == VK_NULL_HANDLE);
// Reclaim fences if the client only gets the completed submission index or
// awaits in special cases such as shutdown.
UpdateAndGetCompletedSubmission();
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
if (!fences_reclaimed_.empty()) {
VkFence reclaimed_fence = fences_reclaimed_.back();
if (dfn.vkResetFences(device, 1, &reclaimed_fence) == VK_SUCCESS) {
fence_acquired_ = fences_reclaimed_.back();
fences_reclaimed_.pop_back();
}
}
if (fence_acquired_ == VK_NULL_HANDLE) {
VkFenceCreateInfo fence_create_info;
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.pNext = nullptr;
fence_create_info.flags = 0;
// May fail, a null fence is handled in FenceAcquisition.
dfn.vkCreateFence(device, &fence_create_info, nullptr, &fence_acquired_);
}
return FenceAcquisition(*this, fence_acquired_);
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,135 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_
#define XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_
#include <cstdint>
#include <deque>
#include <utility>
#include <vector>
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
// Fence wrapper, safely handling cases when the fence has not been initialized
// yet or has already been shut down, and failed or dropped submissions.
//
// The current submission index can be associated with the usage of objects to
// release them when the GPU isn't potentially referencing them anymore, and
// should be incremented only
//
// 0 can be used as a "never referenced" submission index.
//
// The submission index timeline survives Shutdown, so submission indices can be
// given to clients that are not aware of the lifetime of the tracker.
//
// To transfer the tracker to another queue (to make sure the first signal on
// the new queue does not happen before the last signal on the old one), call
// AwaitAllSubmissionsCompletion before doing the first submission on the new
// one.
class VulkanSubmissionTracker {
public:
class FenceAcquisition {
public:
FenceAcquisition() : submission_tracker_(nullptr), fence_(VK_NULL_HANDLE) {}
FenceAcquisition(VulkanSubmissionTracker& submission_tracker, VkFence fence)
: submission_tracker_(&submission_tracker), fence_(fence) {}
FenceAcquisition(const FenceAcquisition& fence_acquisition) = delete;
FenceAcquisition& operator=(const FenceAcquisition& fence_acquisition) =
delete;
FenceAcquisition(FenceAcquisition&& fence_acquisition) {
*this = std::move(fence_acquisition);
}
FenceAcquisition& operator=(FenceAcquisition&& fence_acquisition) {
if (this == &fence_acquisition) {
return *this;
}
submission_tracker_ = fence_acquisition.submission_tracker_;
fence_acquisition.submission_tracker_ = nullptr;
fence_ = fence_acquisition.fence_;
fence_acquisition.fence_ = VK_NULL_HANDLE;
return *this;
}
~FenceAcquisition();
// In unsignaled state. May be null if failed to create or to reset a fence.
VkFence fence() { return fence_; }
// Call if vkQueueSubmit has failed (or it was decided not to commit the
// submission), and the submission index shouldn't be incremented by
// releasing this submission (for instance, to retry commands with long-term
// effects like copying or image layout changes later if in the attempt the
// submission index stays the same).
void SubmissionFailedOrDropped();
// Call if for some reason (like signaling multiple fences) the fence
// signaling was done in a separate submission than the command buffer, and
// the command buffer vkQueueSubmit succeeded (so commands with long-term
// effects will be executed), but the fence-only vkQueueSubmit has failed,
// thus the tracker shouldn't attempt to wait for that fence (it will be in
// the unsignaled state).
void SubmissionSucceededSignalFailed() { signal_failed_ = true; }
private:
// If nullptr, has been moved to another FenceAcquisition - not holding a
// fence from now on.
VulkanSubmissionTracker* submission_tracker_;
VkFence fence_;
bool signal_failed_ = false;
};
VulkanSubmissionTracker(VulkanProvider& provider) : provider_(provider) {}
VulkanSubmissionTracker(const VulkanSubmissionTracker& submission_tracker) =
delete;
VulkanSubmissionTracker& operator=(
const VulkanSubmissionTracker& submission_tracker) = delete;
~VulkanSubmissionTracker() { Shutdown(); }
void Shutdown();
uint64_t GetCurrentSubmission() const { return submission_current_; }
uint64_t UpdateAndGetCompletedSubmission();
// Returns whether the expected GPU signal has actually been reached (rather
// than some fallback condition) for cases when stronger completeness
// guarantees as needed (when downloading, as opposed to just destroying).
// If false is returned, it's also not guaranteed that GetCompletedSubmission
// will return a value >= submission_index.
bool AwaitSubmissionCompletion(uint64_t submission_index);
bool AwaitAllSubmissionsCompletion() {
return AwaitSubmissionCompletion(submission_current_ - 1);
}
[[nodiscard]] FenceAcquisition AcquireFenceToAdvanceSubmission();
private:
VulkanProvider& provider_;
uint64_t submission_current_ = 1;
// Last submission with a successful fence signal as well as a successful
// fence wait / query.
uint64_t submission_completed_on_gpu_ = 0;
// The flow is:
// Reclaimed (or create if empty) > acquired > pending > reclaimed.
// Or, if dropped the submission while acquired:
// Reclaimed (or create if empty) > acquired > reclaimed.
VkFence fence_acquired_ = VK_NULL_HANDLE;
// Ordered by the submission index (the first pair member).
std::deque<std::pair<uint64_t, VkFence>> fences_pending_;
// Fences are reclaimed when awaiting or when refreshing the completed value.
std::vector<VkFence> fences_reclaimed_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_

View File

@@ -1,824 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_swap_chain.h"
#include <mutex>
#include <string>
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_device.h"
#include "xenia/ui/vulkan/vulkan_instance.h"
#include "xenia/ui/vulkan/vulkan_util.h"
DEFINE_bool(vulkan_random_clear_color, false,
"Randomizes framebuffer clear color.", "Vulkan");
namespace xe {
namespace ui {
namespace vulkan {
VulkanSwapChain::VulkanSwapChain(VulkanInstance* instance, VulkanDevice* device)
: instance_(instance), device_(device) {}
VulkanSwapChain::~VulkanSwapChain() { Shutdown(); }
VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) {
surface_ = surface;
const VulkanInstance::InstanceFunctions& ifn = instance_->ifn();
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status;
// Find a queue family that supports presentation.
VkBool32 surface_supported = false;
uint32_t queue_family_index = -1;
for (uint32_t i = 0;
i < device_->device_info().queue_family_properties.size(); i++) {
const VkQueueFamilyProperties& family_props =
device_->device_info().queue_family_properties[i];
if (!(family_props.queueFlags & VK_QUEUE_GRAPHICS_BIT) ||
!(family_props.queueFlags & VK_QUEUE_TRANSFER_BIT)) {
continue;
}
status = ifn.vkGetPhysicalDeviceSurfaceSupportKHR(*device_, i, surface,
&surface_supported);
if (status == VK_SUCCESS && surface_supported == VK_TRUE) {
queue_family_index = i;
break;
}
}
if (!surface_supported) {
XELOGE(
"Physical device does not have a queue that supports "
"graphics/transfer/presentation!");
return VK_ERROR_INCOMPATIBLE_DRIVER;
}
presentation_queue_ = device_->AcquireQueue(queue_family_index);
presentation_queue_family_ = queue_family_index;
if (!presentation_queue_) {
// That's okay, use the primary queue.
presentation_queue_ = device_->primary_queue();
presentation_queue_mutex_ = &device_->primary_queue_mutex();
presentation_queue_family_ = device_->queue_family_index();
if (!presentation_queue_) {
XELOGE("Failed to acquire swap chain presentation queue!");
return VK_ERROR_INITIALIZATION_FAILED;
}
}
// Query supported target formats.
uint32_t count = 0;
status = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count,
nullptr);
CheckResult(status, "vkGetPhysicalDeviceSurfaceFormatsKHR");
std::vector<VkSurfaceFormatKHR> surface_formats;
surface_formats.resize(count);
status = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count,
surface_formats.data());
CheckResult(status, "vkGetPhysicalDeviceSurfaceFormatsKHR");
if (status != VK_SUCCESS) {
return status;
}
// If the format list includes just one entry of VK_FORMAT_UNDEFINED the
// surface has no preferred format.
// Otherwise, at least one supported format will be returned.
assert_true(surface_formats.size() >= 1);
if (surface_formats.size() == 1 &&
surface_formats[0].format == VK_FORMAT_UNDEFINED) {
// Fallback to common RGBA.
surface_format_ = VK_FORMAT_R8G8B8A8_UNORM;
} else {
// Use first defined format.
surface_format_ = surface_formats[0].format;
}
// Query surface min/max/caps.
VkSurfaceCapabilitiesKHR surface_caps;
status = ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(*device_, surface_,
&surface_caps);
CheckResult(status, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
if (status != VK_SUCCESS) {
return status;
}
// Query surface properties so we can configure ourselves within bounds.
std::vector<VkPresentModeKHR> present_modes;
status = ifn.vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_,
&count, nullptr);
CheckResult(status, "vkGetPhysicalDeviceSurfacePresentModesKHR");
if (status != VK_SUCCESS) {
return status;
}
present_modes.resize(count);
status = ifn.vkGetPhysicalDeviceSurfacePresentModesKHR(
*device_, surface_, &count, present_modes.data());
CheckResult(status, "vkGetPhysicalDeviceSurfacePresentModesKHR");
if (status != VK_SUCCESS) {
return status;
}
// Calculate swapchain target dimensions.
VkExtent2D extent = surface_caps.currentExtent;
if (surface_caps.currentExtent.width == -1) {
assert_true(surface_caps.currentExtent.height == -1);
// Undefined extents, so we need to pick something.
XELOGI("Swap chain target surface extents undefined; guessing value");
extent.width = 1280;
extent.height = 720;
}
surface_width_ = extent.width;
surface_height_ = extent.height;
// Always prefer mailbox mode (non-tearing, low-latency).
// If it's not available we'll use immediate (tearing, low-latency).
// If not even that we fall back to FIFO, which sucks.
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (size_t i = 0; i < present_modes.size(); ++i) {
if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
// This is the best, so early-out.
present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
} else if (present_modes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) {
present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
}
// Determine the number of images (1 + number queued).
uint32_t image_count = surface_caps.minImageCount + 1;
if (surface_caps.maxImageCount > 0 &&
image_count > surface_caps.maxImageCount) {
// Too many requested - use whatever we can.
XELOGI("Requested number of swapchain images ({}) exceeds maximum ({})",
image_count, surface_caps.maxImageCount);
image_count = surface_caps.maxImageCount;
}
// Always pass through whatever transform the surface started with (so long
// as it's supported).
VkSurfaceTransformFlagBitsKHR pre_transform = surface_caps.currentTransform;
VkSwapchainCreateInfoKHR create_info;
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.surface = surface_;
create_info.minImageCount = image_count;
create_info.imageFormat = surface_format_;
create_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
create_info.imageExtent.width = extent.width;
create_info.imageExtent.height = extent.height;
create_info.imageArrayLayers = 1;
create_info.imageUsage =
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
create_info.queueFamilyIndexCount = 0;
create_info.pQueueFamilyIndices = nullptr;
create_info.preTransform = pre_transform;
create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
create_info.presentMode = present_mode;
create_info.clipped = VK_TRUE;
create_info.oldSwapchain = nullptr;
XELOGVK("Creating swap chain:");
XELOGVK(" minImageCount = {}", create_info.minImageCount);
XELOGVK(" imageFormat = {}", to_string(create_info.imageFormat));
XELOGVK(" imageExtent = {} x {}", create_info.imageExtent.width,
create_info.imageExtent.height);
auto pre_transform_str = to_flags_string(create_info.preTransform);
XELOGVK(" preTransform = {}", pre_transform_str);
XELOGVK(" imageArrayLayers = {}", create_info.imageArrayLayers);
XELOGVK(" presentMode = {}", to_string(create_info.presentMode));
XELOGVK(" clipped = {}", create_info.clipped ? "true" : "false");
XELOGVK(" imageColorSpace = {}", to_string(create_info.imageColorSpace));
auto image_usage_flags_str = to_flags_string(
static_cast<VkImageUsageFlagBits>(create_info.imageUsage));
XELOGVK(" imageUsageFlags = {}", image_usage_flags_str);
XELOGVK(" imageSharingMode = {}", to_string(create_info.imageSharingMode));
XELOGVK(" queueFamilyCount = {}", create_info.queueFamilyIndexCount);
status = dfn.vkCreateSwapchainKHR(*device_, &create_info, nullptr, &handle);
if (status != VK_SUCCESS) {
XELOGE("Failed to create swapchain: {}", to_string(status));
return status;
}
// Create the pool used for transient buffers, so we can reset them all at
// once.
VkCommandPoolCreateInfo cmd_pool_info;
cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmd_pool_info.pNext = nullptr;
cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
cmd_pool_info.queueFamilyIndex = presentation_queue_family_;
status =
dfn.vkCreateCommandPool(*device_, &cmd_pool_info, nullptr, &cmd_pool_);
CheckResult(status, "vkCreateCommandPool");
if (status != VK_SUCCESS) {
return status;
}
// Primary command buffer
VkCommandBufferAllocateInfo cmd_buffer_info;
cmd_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmd_buffer_info.pNext = nullptr;
cmd_buffer_info.commandPool = cmd_pool_;
cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmd_buffer_info.commandBufferCount = 2;
status =
dfn.vkAllocateCommandBuffers(*device_, &cmd_buffer_info, &cmd_buffer_);
CheckResult(status, "vkCreateCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Make two command buffers we'll do all our primary rendering from.
VkCommandBuffer command_buffers[2];
cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
cmd_buffer_info.commandBufferCount = 2;
status =
dfn.vkAllocateCommandBuffers(*device_, &cmd_buffer_info, command_buffers);
CheckResult(status, "vkCreateCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
render_cmd_buffer_ = command_buffers[0];
copy_cmd_buffer_ = command_buffers[1];
// Create the render pass used to draw to the swap chain.
// The actual framebuffer attached will depend on which image we are drawing
// into.
VkAttachmentDescription color_attachment;
color_attachment.flags = 0;
color_attachment.format = surface_format_;
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; // CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
color_attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
color_attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference color_reference;
color_reference.attachment = 0;
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_reference;
depth_reference.attachment = VK_ATTACHMENT_UNUSED;
depth_reference.layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkSubpassDescription render_subpass;
render_subpass.flags = 0;
render_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
render_subpass.inputAttachmentCount = 0;
render_subpass.pInputAttachments = nullptr;
render_subpass.colorAttachmentCount = 1;
render_subpass.pColorAttachments = &color_reference;
render_subpass.pResolveAttachments = nullptr;
render_subpass.pDepthStencilAttachment = &depth_reference;
render_subpass.preserveAttachmentCount = 0,
render_subpass.pPreserveAttachments = nullptr;
VkRenderPassCreateInfo render_pass_info;
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.pNext = nullptr;
render_pass_info.flags = 0;
render_pass_info.attachmentCount = 1;
render_pass_info.pAttachments = &color_attachment;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &render_subpass;
render_pass_info.dependencyCount = 0;
render_pass_info.pDependencies = nullptr;
status = dfn.vkCreateRenderPass(*device_, &render_pass_info, nullptr,
&render_pass_);
CheckResult(status, "vkCreateRenderPass");
if (status != VK_SUCCESS) {
return status;
}
// Create a semaphore we'll use to synchronize with the swapchain.
VkSemaphoreCreateInfo semaphore_info;
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphore_info.pNext = nullptr;
semaphore_info.flags = 0;
status = dfn.vkCreateSemaphore(*device_, &semaphore_info, nullptr,
&image_available_semaphore_);
CheckResult(status, "vkCreateSemaphore");
if (status != VK_SUCCESS) {
return status;
}
// Create another semaphore used to synchronize writes to the swap image.
status = dfn.vkCreateSemaphore(*device_, &semaphore_info, nullptr,
&image_usage_semaphore_);
CheckResult(status, "vkCreateSemaphore");
if (status != VK_SUCCESS) {
return status;
}
// Get images we will be presenting to.
// Note that this may differ from our requested amount.
uint32_t actual_image_count = 0;
std::vector<VkImage> images;
status = dfn.vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count,
nullptr);
CheckResult(status, "vkGetSwapchainImagesKHR");
if (status != VK_SUCCESS) {
return status;
}
images.resize(actual_image_count);
status = dfn.vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count,
images.data());
CheckResult(status, "vkGetSwapchainImagesKHR");
if (status != VK_SUCCESS) {
return status;
}
// Create all buffers.
buffers_.resize(images.size());
for (size_t i = 0; i < buffers_.size(); ++i) {
status = InitializeBuffer(&buffers_[i], images[i]);
if (status != VK_SUCCESS) {
XELOGE("Failed to initialize a swapchain buffer");
return status;
}
buffers_[i].image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
// Create a fence we'll use to wait for commands to finish.
VkFenceCreateInfo fence_create_info = {
VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
nullptr,
VK_FENCE_CREATE_SIGNALED_BIT,
};
status = dfn.vkCreateFence(*device_, &fence_create_info, nullptr,
&synchronization_fence_);
CheckResult(status, "vkGetSwapchainImagesKHR");
if (status != VK_SUCCESS) {
return status;
}
XELOGVK("Swap chain initialized successfully!");
return VK_SUCCESS;
}
VkResult VulkanSwapChain::InitializeBuffer(Buffer* buffer,
VkImage target_image) {
DestroyBuffer(buffer);
buffer->image = target_image;
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status;
// Create an image view for the presentation image.
// This will be used as a framebuffer attachment.
VkImageViewCreateInfo image_view_info;
image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_info.pNext = nullptr;
image_view_info.flags = 0;
image_view_info.image = buffer->image;
image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_info.format = surface_format_;
image_view_info.components.r = VK_COMPONENT_SWIZZLE_R;
image_view_info.components.g = VK_COMPONENT_SWIZZLE_G;
image_view_info.components.b = VK_COMPONENT_SWIZZLE_B;
image_view_info.components.a = VK_COMPONENT_SWIZZLE_A;
image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_info.subresourceRange.baseMipLevel = 0;
image_view_info.subresourceRange.levelCount = 1;
image_view_info.subresourceRange.baseArrayLayer = 0;
image_view_info.subresourceRange.layerCount = 1;
status = dfn.vkCreateImageView(*device_, &image_view_info, nullptr,
&buffer->image_view);
CheckResult(status, "vkCreateImageView");
if (status != VK_SUCCESS) {
return status;
}
// Create the framebuffer used to render into this image.
VkImageView attachments[] = {buffer->image_view};
VkFramebufferCreateInfo framebuffer_info;
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.pNext = nullptr;
framebuffer_info.flags = 0;
framebuffer_info.renderPass = render_pass_;
framebuffer_info.attachmentCount =
static_cast<uint32_t>(xe::countof(attachments));
framebuffer_info.pAttachments = attachments;
framebuffer_info.width = surface_width_;
framebuffer_info.height = surface_height_;
framebuffer_info.layers = 1;
status = dfn.vkCreateFramebuffer(*device_, &framebuffer_info, nullptr,
&buffer->framebuffer);
CheckResult(status, "vkCreateFramebuffer");
if (status != VK_SUCCESS) {
return status;
}
return VK_SUCCESS;
}
void VulkanSwapChain::DestroyBuffer(Buffer* buffer) {
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
if (buffer->framebuffer) {
dfn.vkDestroyFramebuffer(*device_, buffer->framebuffer, nullptr);
buffer->framebuffer = nullptr;
}
if (buffer->image_view) {
dfn.vkDestroyImageView(*device_, buffer->image_view, nullptr);
buffer->image_view = nullptr;
}
// Image is taken care of by the presentation engine.
buffer->image = nullptr;
}
VkResult VulkanSwapChain::Reinitialize() {
// Hacky, but stash the surface so we can reuse it.
auto surface = surface_;
surface_ = nullptr;
Shutdown();
return Initialize(surface);
}
void VulkanSwapChain::WaitOnSemaphore(VkSemaphore sem) {
wait_semaphores_.push_back(sem);
}
void VulkanSwapChain::Shutdown() {
// TODO(benvanik): properly wait for a clean state.
for (auto& buffer : buffers_) {
DestroyBuffer(&buffer);
}
buffers_.clear();
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
DestroyAndNullHandle(dfn.vkDestroySemaphore, *device_,
image_available_semaphore_);
DestroyAndNullHandle(dfn.vkDestroyRenderPass, *device_, render_pass_);
if (copy_cmd_buffer_) {
dfn.vkFreeCommandBuffers(*device_, cmd_pool_, 1, &copy_cmd_buffer_);
copy_cmd_buffer_ = nullptr;
}
if (render_cmd_buffer_) {
dfn.vkFreeCommandBuffers(*device_, cmd_pool_, 1, &render_cmd_buffer_);
render_cmd_buffer_ = nullptr;
}
DestroyAndNullHandle(dfn.vkDestroyCommandPool, *device_, cmd_pool_);
if (presentation_queue_) {
if (!presentation_queue_mutex_) {
// We own the queue and need to release it.
device_->ReleaseQueue(presentation_queue_, presentation_queue_family_);
}
presentation_queue_ = nullptr;
presentation_queue_mutex_ = nullptr;
presentation_queue_family_ = -1;
}
DestroyAndNullHandle(dfn.vkDestroyFence, *device_, synchronization_fence_);
// images_ doesn't need to be cleaned up as the swapchain does it implicitly.
DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, *device_, handle);
const VulkanInstance::InstanceFunctions& ifn = instance_->ifn();
DestroyAndNullHandle(ifn.vkDestroySurfaceKHR, *instance_, surface_);
}
VkResult VulkanSwapChain::Begin() {
wait_semaphores_.clear();
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status;
// Wait for the last swap to finish.
status =
dfn.vkWaitForFences(*device_, 1, &synchronization_fence_, VK_TRUE, -1);
if (status != VK_SUCCESS) {
return status;
}
status = dfn.vkResetFences(*device_, 1, &synchronization_fence_);
if (status != VK_SUCCESS) {
return status;
}
// Get the index of the next available swapchain image.
status =
dfn.vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_,
nullptr, &current_buffer_index_);
if (status != VK_SUCCESS) {
return status;
}
// Wait for the acquire semaphore to be signaled so that the following
// operations know they can start modifying the image.
VkSubmitInfo wait_submit_info;
wait_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
wait_submit_info.pNext = nullptr;
VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
wait_submit_info.waitSemaphoreCount = 1;
wait_submit_info.pWaitSemaphores = &image_available_semaphore_;
wait_submit_info.pWaitDstStageMask = &wait_dst_stage;
wait_submit_info.commandBufferCount = 0;
wait_submit_info.pCommandBuffers = nullptr;
wait_submit_info.signalSemaphoreCount = 1;
wait_submit_info.pSignalSemaphores = &image_usage_semaphore_;
if (presentation_queue_mutex_) {
presentation_queue_mutex_->lock();
}
status =
dfn.vkQueueSubmit(presentation_queue_, 1, &wait_submit_info, nullptr);
if (presentation_queue_mutex_) {
presentation_queue_mutex_->unlock();
}
if (status != VK_SUCCESS) {
return status;
}
// Reset all command buffers.
dfn.vkResetCommandBuffer(render_cmd_buffer_, 0);
dfn.vkResetCommandBuffer(copy_cmd_buffer_, 0);
auto& current_buffer = buffers_[current_buffer_index_];
// Build the command buffer that will execute all queued rendering buffers.
VkCommandBufferInheritanceInfo inherit_info;
inherit_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
inherit_info.pNext = nullptr;
inherit_info.renderPass = render_pass_;
inherit_info.subpass = 0;
inherit_info.framebuffer = current_buffer.framebuffer;
inherit_info.occlusionQueryEnable = VK_FALSE;
inherit_info.queryFlags = 0;
inherit_info.pipelineStatistics = 0;
VkCommandBufferBeginInfo begin_info;
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.pNext = nullptr;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT |
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
begin_info.pInheritanceInfo = &inherit_info;
status = dfn.vkBeginCommandBuffer(render_cmd_buffer_, &begin_info);
CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Start recording the copy command buffer as well.
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
status = dfn.vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info);
CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// First: Issue a command to clear the render target.
VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkClearColorValue clear_color;
clear_color.float32[0] = 238 / 255.0f;
clear_color.float32[1] = 238 / 255.0f;
clear_color.float32[2] = 238 / 255.0f;
clear_color.float32[3] = 1.0f;
if (cvars::vulkan_random_clear_color) {
clear_color.float32[0] =
rand() / static_cast<float>(RAND_MAX); // NOLINT(runtime/threadsafe_fn)
clear_color.float32[1] = 1.0f;
clear_color.float32[2] = 0.0f;
}
dfn.vkCmdClearColorImage(copy_cmd_buffer_, current_buffer.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color,
1, &clear_range);
return VK_SUCCESS;
}
VkResult VulkanSwapChain::End() {
auto& current_buffer = buffers_[current_buffer_index_];
const VulkanDevice::DeviceFunctions& dfn = device_->dfn();
VkResult status;
status = dfn.vkEndCommandBuffer(render_cmd_buffer_);
CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
status = dfn.vkEndCommandBuffer(copy_cmd_buffer_);
CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Build primary command buffer.
status = dfn.vkResetCommandBuffer(cmd_buffer_, 0);
CheckResult(status, "vkResetCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
VkCommandBufferBeginInfo begin_info;
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.pNext = nullptr;
begin_info.flags = 0;
begin_info.pInheritanceInfo = nullptr;
status = dfn.vkBeginCommandBuffer(cmd_buffer_, &begin_info);
CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Transition the image to a format we can copy to.
VkImageMemoryBarrier pre_image_copy_barrier;
pre_image_copy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
pre_image_copy_barrier.pNext = nullptr;
pre_image_copy_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
pre_image_copy_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
pre_image_copy_barrier.oldLayout = current_buffer.image_layout;
pre_image_copy_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
pre_image_copy_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_image_copy_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_image_copy_barrier.image = current_buffer.image;
pre_image_copy_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0,
1};
dfn.vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &pre_image_copy_barrier);
current_buffer.image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
// Execute copy commands
dfn.vkCmdExecuteCommands(cmd_buffer_, 1, &copy_cmd_buffer_);
// Transition the image to a color attachment target for drawing.
VkImageMemoryBarrier pre_image_memory_barrier;
pre_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
pre_image_memory_barrier.pNext = nullptr;
pre_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
pre_image_memory_barrier.image = current_buffer.image;
pre_image_memory_barrier.subresourceRange.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
pre_image_memory_barrier.subresourceRange.baseMipLevel = 0;
pre_image_memory_barrier.subresourceRange.levelCount = 1;
pre_image_memory_barrier.subresourceRange.baseArrayLayer = 0;
pre_image_memory_barrier.subresourceRange.layerCount = 1;
pre_image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
pre_image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
pre_image_memory_barrier.oldLayout = current_buffer.image_layout;
pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
dfn.vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0,
nullptr, 0, nullptr, 1, &pre_image_memory_barrier);
current_buffer.image_layout = pre_image_memory_barrier.newLayout;
// Begin render pass.
VkRenderPassBeginInfo render_pass_begin_info;
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin_info.pNext = nullptr;
render_pass_begin_info.renderPass = render_pass_;
render_pass_begin_info.framebuffer = current_buffer.framebuffer;
render_pass_begin_info.renderArea.offset.x = 0;
render_pass_begin_info.renderArea.offset.y = 0;
render_pass_begin_info.renderArea.extent.width = surface_width_;
render_pass_begin_info.renderArea.extent.height = surface_height_;
render_pass_begin_info.clearValueCount = 0;
render_pass_begin_info.pClearValues = nullptr;
dfn.vkCmdBeginRenderPass(cmd_buffer_, &render_pass_begin_info,
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
// Render commands.
dfn.vkCmdExecuteCommands(cmd_buffer_, 1, &render_cmd_buffer_);
// End render pass.
dfn.vkCmdEndRenderPass(cmd_buffer_);
// Transition the image to a format the presentation engine can source from.
// FIXME: Do we need more synchronization here between the copy buffer?
VkImageMemoryBarrier post_image_memory_barrier;
post_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
post_image_memory_barrier.pNext = nullptr;
post_image_memory_barrier.srcAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
post_image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
post_image_memory_barrier.oldLayout = current_buffer.image_layout;
post_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
post_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
post_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
post_image_memory_barrier.image = current_buffer.image;
post_image_memory_barrier.subresourceRange.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
post_image_memory_barrier.subresourceRange.baseMipLevel = 0;
post_image_memory_barrier.subresourceRange.levelCount = 1;
post_image_memory_barrier.subresourceRange.baseArrayLayer = 0;
post_image_memory_barrier.subresourceRange.layerCount = 1;
dfn.vkCmdPipelineBarrier(cmd_buffer_,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
nullptr, 1, &post_image_memory_barrier);
current_buffer.image_layout = post_image_memory_barrier.newLayout;
status = dfn.vkEndCommandBuffer(cmd_buffer_);
CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
std::vector<VkSemaphore> semaphores;
std::vector<VkPipelineStageFlags> wait_dst_stages;
for (size_t i = 0; i < wait_semaphores_.size(); i++) {
semaphores.push_back(wait_semaphores_[i]);
wait_dst_stages.push_back(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
}
semaphores.push_back(image_usage_semaphore_);
wait_dst_stages.push_back(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
// Submit commands.
// Wait on the image usage semaphore (signaled when an image is available)
VkSubmitInfo render_submit_info;
render_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
render_submit_info.pNext = nullptr;
render_submit_info.waitSemaphoreCount = uint32_t(semaphores.size());
render_submit_info.pWaitSemaphores = semaphores.data();
render_submit_info.pWaitDstStageMask = wait_dst_stages.data();
render_submit_info.commandBufferCount = 1;
render_submit_info.pCommandBuffers = &cmd_buffer_;
render_submit_info.signalSemaphoreCount = 0;
render_submit_info.pSignalSemaphores = nullptr;
if (presentation_queue_mutex_) {
presentation_queue_mutex_->lock();
}
status = dfn.vkQueueSubmit(presentation_queue_, 1, &render_submit_info,
synchronization_fence_);
if (presentation_queue_mutex_) {
presentation_queue_mutex_->unlock();
}
if (status != VK_SUCCESS) {
return status;
}
// Queue the present of our current image.
const VkSwapchainKHR swap_chains[] = {handle};
const uint32_t swap_chain_image_indices[] = {current_buffer_index_};
VkPresentInfoKHR present_info;
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.pNext = nullptr;
present_info.waitSemaphoreCount = 0;
present_info.pWaitSemaphores = nullptr;
present_info.swapchainCount = static_cast<uint32_t>(xe::countof(swap_chains));
present_info.pSwapchains = swap_chains;
present_info.pImageIndices = swap_chain_image_indices;
present_info.pResults = nullptr;
if (presentation_queue_mutex_) {
presentation_queue_mutex_->lock();
}
status = dfn.vkQueuePresentKHR(presentation_queue_, &present_info);
if (presentation_queue_mutex_) {
presentation_queue_mutex_->unlock();
}
switch (status) {
case VK_SUCCESS:
break;
case VK_SUBOPTIMAL_KHR:
// We are not rendering at the right size - but the presentation engine
// will scale the output for us.
status = VK_SUCCESS;
break;
case VK_ERROR_OUT_OF_DATE_KHR:
// Lost presentation ability; need to recreate the swapchain.
// TODO(benvanik): recreate swapchain.
assert_always("Swapchain recreation not implemented");
break;
case VK_ERROR_DEVICE_LOST:
// Fatal. Device lost.
break;
default:
XELOGE("Failed to queue present: {}", to_string(status));
assert_always("Unexpected queue present failure");
}
return status;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -1,106 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_SWAP_CHAIN_H_
#define XENIA_UI_VULKAN_VULKAN_SWAP_CHAIN_H_
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/ui/vulkan/vulkan.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanDevice;
class VulkanInstance;
class VulkanSwapChain {
public:
VulkanSwapChain(VulkanInstance* instance, VulkanDevice* device);
~VulkanSwapChain();
VkSwapchainKHR handle = nullptr;
operator VkSwapchainKHR() const { return handle; }
uint32_t surface_width() const { return surface_width_; }
uint32_t surface_height() const { return surface_height_; }
VkImage surface_image() const {
return buffers_[current_buffer_index_].image;
}
// Render pass used for compositing.
VkRenderPass render_pass() const { return render_pass_; }
// Render command buffer, active inside the render pass from Begin to End.
VkCommandBuffer render_cmd_buffer() const { return render_cmd_buffer_; }
// Copy commands, ran before the render command buffer.
VkCommandBuffer copy_cmd_buffer() const { return copy_cmd_buffer_; }
// Initializes the swap chain with the given WSI surface.
VkResult Initialize(VkSurfaceKHR surface);
// Reinitializes the swap chain with the initial surface.
// The surface will be retained but all other swap chain resources will be
// torn down and recreated with the new surface properties (size/etc).
VkResult Reinitialize();
// Waits on and signals a semaphore in this operation.
void WaitOnSemaphore(VkSemaphore sem);
// Begins the swap operation, preparing state for rendering.
VkResult Begin();
// Ends the swap operation, finalizing rendering and presenting the results.
VkResult End();
private:
struct Buffer {
VkImage image = nullptr;
VkImageLayout image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageView image_view = nullptr;
VkFramebuffer framebuffer = nullptr;
};
VkResult InitializeBuffer(Buffer* buffer, VkImage target_image);
void DestroyBuffer(Buffer* buffer);
// Safely releases all swap chain resources.
void Shutdown();
VulkanInstance* instance_ = nullptr;
VulkanDevice* device_ = nullptr;
VkFence synchronization_fence_ = nullptr;
VkQueue presentation_queue_ = nullptr;
std::mutex* presentation_queue_mutex_ = nullptr;
uint32_t presentation_queue_family_ = -1;
VkSurfaceKHR surface_ = nullptr;
uint32_t surface_width_ = 0;
uint32_t surface_height_ = 0;
VkFormat surface_format_ = VK_FORMAT_UNDEFINED;
VkCommandPool cmd_pool_ = nullptr;
VkCommandBuffer cmd_buffer_ = nullptr;
VkCommandBuffer copy_cmd_buffer_ = nullptr;
VkCommandBuffer render_cmd_buffer_ = nullptr;
VkRenderPass render_pass_ = nullptr;
VkSemaphore image_available_semaphore_ = nullptr;
VkSemaphore image_usage_semaphore_ = nullptr;
uint32_t current_buffer_index_ = 0;
std::vector<Buffer> buffers_;
std::vector<VkSemaphore> wait_semaphores_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_SWAP_CHAIN_H_

View File

@@ -0,0 +1,199 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
#include <algorithm>
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace ui {
namespace vulkan {
// Host-visible memory sizes are likely to be internally rounded to
// nonCoherentAtomSize (it's the memory mapping granularity, though as the map
// or flush range must be clamped to the actual allocation size as a special
// case, but it's still unlikely that the allocation won't be aligned to it), so
// try not to waste that padding.
VulkanUploadBufferPool::VulkanUploadBufferPool(const VulkanProvider& provider,
VkBufferUsageFlags usage,
size_t page_size)
: GraphicsUploadBufferPool(size_t(
util::GetMappableMemorySize(provider, VkDeviceSize(page_size)))),
provider_(provider),
usage_(usage) {}
uint8_t* VulkanUploadBufferPool::Request(uint64_t submission_index, size_t size,
size_t alignment, VkBuffer& buffer_out,
VkDeviceSize& offset_out) {
size_t offset;
const VulkanPage* page =
static_cast<const VulkanPage*>(GraphicsUploadBufferPool::Request(
submission_index, size, alignment, offset));
if (!page) {
return nullptr;
}
buffer_out = page->buffer_;
offset_out = VkDeviceSize(offset);
return reinterpret_cast<uint8_t*>(page->mapping_) + offset;
}
uint8_t* VulkanUploadBufferPool::RequestPartial(uint64_t submission_index,
size_t size, size_t alignment,
VkBuffer& buffer_out,
VkDeviceSize& offset_out,
VkDeviceSize& size_out) {
size_t offset, size_obtained;
const VulkanPage* page =
static_cast<const VulkanPage*>(GraphicsUploadBufferPool::RequestPartial(
submission_index, size, alignment, offset, size_obtained));
if (!page) {
return nullptr;
}
buffer_out = page->buffer_;
offset_out = VkDeviceSize(offset);
size_out = VkDeviceSize(size_obtained);
return reinterpret_cast<uint8_t*>(page->mapping_) + offset;
}
GraphicsUploadBufferPool::Page*
VulkanUploadBufferPool::CreatePageImplementation() {
if (memory_type_ == kMemoryTypeUnavailable) {
// Don't try to create everything again and again if totally broken.
return nullptr;
}
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkBufferCreateInfo buffer_create_info;
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.pNext = nullptr;
buffer_create_info.flags = 0;
buffer_create_info.size = VkDeviceSize(page_size_);
buffer_create_info.usage = usage_;
buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_create_info.queueFamilyIndexCount = 0;
buffer_create_info.pQueueFamilyIndices = nullptr;
VkBuffer buffer;
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer) !=
VK_SUCCESS) {
XELOGE("Failed to create a Vulkan upload buffer with {} bytes", page_size_);
return nullptr;
}
if (memory_type_ == kMemoryTypeUnknown) {
VkMemoryRequirements memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
memory_type_ = util::ChooseHostMemoryType(
provider_, memory_requirements.memoryTypeBits, false);
if (memory_type_ == UINT32_MAX) {
XELOGE(
"No host-visible memory types can store an Vulkan upload buffer with "
"{} bytes",
page_size_);
memory_type_ = kMemoryTypeUnavailable;
dfn.vkDestroyBuffer(device, buffer, nullptr);
return nullptr;
}
allocation_size_ = memory_requirements.size;
if (allocation_size_ > page_size_) {
// Try to occupy the allocation padding. If that's going to require even
// more memory for some reason, don't.
buffer_create_info.size = allocation_size_;
VkBuffer buffer_expanded;
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr,
&buffer_expanded) == VK_SUCCESS) {
VkMemoryRequirements memory_requirements_expanded;
dfn.vkGetBufferMemoryRequirements(device, buffer_expanded,
&memory_requirements_expanded);
uint32_t memory_type_expanded = util::ChooseHostMemoryType(
provider_, memory_requirements.memoryTypeBits, false);
if (memory_requirements_expanded.size <= allocation_size_ &&
memory_type_expanded != UINT32_MAX) {
page_size_ = size_t(allocation_size_);
allocation_size_ = memory_requirements_expanded.size;
memory_type_ = memory_type_expanded;
dfn.vkDestroyBuffer(device, buffer, nullptr);
buffer = buffer_expanded;
} else {
dfn.vkDestroyBuffer(device, buffer_expanded, nullptr);
}
}
}
}
VkMemoryAllocateInfo memory_allocate_info;
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = allocation_size_;
memory_allocate_info.memoryTypeIndex = memory_type_;
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
if (provider_.device_extensions().khr_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
memory_dedicated_allocate_info.pNext = nullptr;
memory_dedicated_allocate_info.image = VK_NULL_HANDLE;
memory_dedicated_allocate_info.buffer = buffer;
}
VkDeviceMemory memory;
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
VK_SUCCESS) {
XELOGE("Failed to allocate {} bytes of Vulkan upload buffer memory",
allocation_size_);
dfn.vkDestroyBuffer(device, buffer, nullptr);
return nullptr;
}
if (dfn.vkBindBufferMemory(device, buffer, memory, 0) != VK_SUCCESS) {
XELOGE("Failed to bind memory to a Vulkan upload buffer with {} bytes",
page_size_);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, memory, nullptr);
return nullptr;
}
void* mapping;
if (dfn.vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapping) !=
VK_SUCCESS) {
XELOGE("Failed to map {} bytes of Vulkan upload buffer memory",
allocation_size_);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, memory, nullptr);
return nullptr;
}
return new VulkanPage(provider_, buffer, memory, mapping);
}
void VulkanUploadBufferPool::FlushPageWrites(Page* page, size_t offset,
size_t size) {
util::FlushMappedMemoryRange(
provider_, static_cast<const VulkanPage*>(page)->memory_, memory_type_,
VkDeviceSize(offset), allocation_size_, VkDeviceSize(size));
}
VulkanUploadBufferPool::VulkanPage::~VulkanPage() {
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkDestroyBuffer(device, buffer_, nullptr);
// Unmapping is done implicitly when the memory is freed.
dfn.vkFreeMemory(device, memory_, nullptr);
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,67 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_VULKAN_VULKAN_UPLOAD_BUFFER_POOL_H_
#define XENIA_UI_VULKAN_VULKAN_UPLOAD_BUFFER_POOL_H_
#include "xenia/ui/graphics_upload_buffer_pool.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
class VulkanUploadBufferPool : public GraphicsUploadBufferPool {
public:
VulkanUploadBufferPool(const VulkanProvider& provider,
VkBufferUsageFlags usage,
size_t page_size = kDefaultPageSize);
uint8_t* Request(uint64_t submission_index, size_t size, size_t alignment,
VkBuffer& buffer_out, VkDeviceSize& offset_out);
uint8_t* RequestPartial(uint64_t submission_index, size_t size,
size_t alignment, VkBuffer& buffer_out,
VkDeviceSize& offset_out, VkDeviceSize& size_out);
protected:
Page* CreatePageImplementation() override;
void FlushPageWrites(Page* page, size_t offset, size_t size) override;
private:
struct VulkanPage : public Page {
// Takes ownership of the buffer and its memory and mapping.
VulkanPage(const VulkanProvider& provider, VkBuffer buffer,
VkDeviceMemory memory, void* mapping)
: provider_(provider),
buffer_(buffer),
memory_(memory),
mapping_(mapping) {}
~VulkanPage() override;
const VulkanProvider& provider_;
VkBuffer buffer_;
VkDeviceMemory memory_;
void* mapping_;
};
const VulkanProvider& provider_;
VkDeviceSize allocation_size_;
static constexpr uint32_t kMemoryTypeUnknown = UINT32_MAX;
static constexpr uint32_t kMemoryTypeUnavailable = kMemoryTypeUnknown - 1;
uint32_t memory_type_ = UINT32_MAX;
VkBufferUsageFlags usage_;
};
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_UPLOAD_BUFFER_POOL_H_

View File

@@ -9,496 +9,187 @@
#include "xenia/ui/vulkan/vulkan_util.h"
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include <cstdint>
// Implement AMD's VMA here.
#define VMA_IMPLEMENTATION
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
namespace util {
uint32_t Version::Make(uint32_t major, uint32_t minor, uint32_t patch) {
return VK_MAKE_VERSION(major, minor, patch);
}
Version Version::Parse(uint32_t value) {
Version version;
version.major = VK_VERSION_MAJOR(value);
version.minor = VK_VERSION_MINOR(value);
version.patch = VK_VERSION_PATCH(value);
version.pretty_string =
fmt::format("{}.{}.{}", version.major, version.minor, version.patch);
return version;
}
const char* to_string(VkFormat format) {
switch (format) {
#define STR(r) \
case r: \
return #r
STR(VK_FORMAT_UNDEFINED);
STR(VK_FORMAT_R4G4_UNORM_PACK8);
STR(VK_FORMAT_R4G4B4A4_UNORM_PACK16);
STR(VK_FORMAT_B4G4R4A4_UNORM_PACK16);
STR(VK_FORMAT_R5G6B5_UNORM_PACK16);
STR(VK_FORMAT_B5G6R5_UNORM_PACK16);
STR(VK_FORMAT_R5G5B5A1_UNORM_PACK16);
STR(VK_FORMAT_B5G5R5A1_UNORM_PACK16);
STR(VK_FORMAT_A1R5G5B5_UNORM_PACK16);
STR(VK_FORMAT_R8_UNORM);
STR(VK_FORMAT_R8_SNORM);
STR(VK_FORMAT_R8_USCALED);
STR(VK_FORMAT_R8_SSCALED);
STR(VK_FORMAT_R8_UINT);
STR(VK_FORMAT_R8_SINT);
STR(VK_FORMAT_R8_SRGB);
STR(VK_FORMAT_R8G8_UNORM);
STR(VK_FORMAT_R8G8_SNORM);
STR(VK_FORMAT_R8G8_USCALED);
STR(VK_FORMAT_R8G8_SSCALED);
STR(VK_FORMAT_R8G8_UINT);
STR(VK_FORMAT_R8G8_SINT);
STR(VK_FORMAT_R8G8_SRGB);
STR(VK_FORMAT_R8G8B8_UNORM);
STR(VK_FORMAT_R8G8B8_SNORM);
STR(VK_FORMAT_R8G8B8_USCALED);
STR(VK_FORMAT_R8G8B8_SSCALED);
STR(VK_FORMAT_R8G8B8_UINT);
STR(VK_FORMAT_R8G8B8_SINT);
STR(VK_FORMAT_R8G8B8_SRGB);
STR(VK_FORMAT_B8G8R8_UNORM);
STR(VK_FORMAT_B8G8R8_SNORM);
STR(VK_FORMAT_B8G8R8_USCALED);
STR(VK_FORMAT_B8G8R8_SSCALED);
STR(VK_FORMAT_B8G8R8_UINT);
STR(VK_FORMAT_B8G8R8_SINT);
STR(VK_FORMAT_B8G8R8_SRGB);
STR(VK_FORMAT_R8G8B8A8_UNORM);
STR(VK_FORMAT_R8G8B8A8_SNORM);
STR(VK_FORMAT_R8G8B8A8_USCALED);
STR(VK_FORMAT_R8G8B8A8_SSCALED);
STR(VK_FORMAT_R8G8B8A8_UINT);
STR(VK_FORMAT_R8G8B8A8_SINT);
STR(VK_FORMAT_R8G8B8A8_SRGB);
STR(VK_FORMAT_B8G8R8A8_UNORM);
STR(VK_FORMAT_B8G8R8A8_SNORM);
STR(VK_FORMAT_B8G8R8A8_USCALED);
STR(VK_FORMAT_B8G8R8A8_SSCALED);
STR(VK_FORMAT_B8G8R8A8_UINT);
STR(VK_FORMAT_B8G8R8A8_SINT);
STR(VK_FORMAT_B8G8R8A8_SRGB);
STR(VK_FORMAT_A8B8G8R8_UNORM_PACK32);
STR(VK_FORMAT_A8B8G8R8_SNORM_PACK32);
STR(VK_FORMAT_A8B8G8R8_USCALED_PACK32);
STR(VK_FORMAT_A8B8G8R8_SSCALED_PACK32);
STR(VK_FORMAT_A8B8G8R8_UINT_PACK32);
STR(VK_FORMAT_A8B8G8R8_SINT_PACK32);
STR(VK_FORMAT_A8B8G8R8_SRGB_PACK32);
STR(VK_FORMAT_A2R10G10B10_UNORM_PACK32);
STR(VK_FORMAT_A2R10G10B10_SNORM_PACK32);
STR(VK_FORMAT_A2R10G10B10_USCALED_PACK32);
STR(VK_FORMAT_A2R10G10B10_SSCALED_PACK32);
STR(VK_FORMAT_A2R10G10B10_UINT_PACK32);
STR(VK_FORMAT_A2R10G10B10_SINT_PACK32);
STR(VK_FORMAT_A2B10G10R10_UNORM_PACK32);
STR(VK_FORMAT_A2B10G10R10_SNORM_PACK32);
STR(VK_FORMAT_A2B10G10R10_USCALED_PACK32);
STR(VK_FORMAT_A2B10G10R10_SSCALED_PACK32);
STR(VK_FORMAT_A2B10G10R10_UINT_PACK32);
STR(VK_FORMAT_A2B10G10R10_SINT_PACK32);
STR(VK_FORMAT_R16_UNORM);
STR(VK_FORMAT_R16_SNORM);
STR(VK_FORMAT_R16_USCALED);
STR(VK_FORMAT_R16_SSCALED);
STR(VK_FORMAT_R16_UINT);
STR(VK_FORMAT_R16_SINT);
STR(VK_FORMAT_R16_SFLOAT);
STR(VK_FORMAT_R16G16_UNORM);
STR(VK_FORMAT_R16G16_SNORM);
STR(VK_FORMAT_R16G16_USCALED);
STR(VK_FORMAT_R16G16_SSCALED);
STR(VK_FORMAT_R16G16_UINT);
STR(VK_FORMAT_R16G16_SINT);
STR(VK_FORMAT_R16G16_SFLOAT);
STR(VK_FORMAT_R16G16B16_UNORM);
STR(VK_FORMAT_R16G16B16_SNORM);
STR(VK_FORMAT_R16G16B16_USCALED);
STR(VK_FORMAT_R16G16B16_SSCALED);
STR(VK_FORMAT_R16G16B16_UINT);
STR(VK_FORMAT_R16G16B16_SINT);
STR(VK_FORMAT_R16G16B16_SFLOAT);
STR(VK_FORMAT_R16G16B16A16_UNORM);
STR(VK_FORMAT_R16G16B16A16_SNORM);
STR(VK_FORMAT_R16G16B16A16_USCALED);
STR(VK_FORMAT_R16G16B16A16_SSCALED);
STR(VK_FORMAT_R16G16B16A16_UINT);
STR(VK_FORMAT_R16G16B16A16_SINT);
STR(VK_FORMAT_R16G16B16A16_SFLOAT);
STR(VK_FORMAT_R32_UINT);
STR(VK_FORMAT_R32_SINT);
STR(VK_FORMAT_R32_SFLOAT);
STR(VK_FORMAT_R32G32_UINT);
STR(VK_FORMAT_R32G32_SINT);
STR(VK_FORMAT_R32G32_SFLOAT);
STR(VK_FORMAT_R32G32B32_UINT);
STR(VK_FORMAT_R32G32B32_SINT);
STR(VK_FORMAT_R32G32B32_SFLOAT);
STR(VK_FORMAT_R32G32B32A32_UINT);
STR(VK_FORMAT_R32G32B32A32_SINT);
STR(VK_FORMAT_R32G32B32A32_SFLOAT);
STR(VK_FORMAT_R64_UINT);
STR(VK_FORMAT_R64_SINT);
STR(VK_FORMAT_R64_SFLOAT);
STR(VK_FORMAT_R64G64_UINT);
STR(VK_FORMAT_R64G64_SINT);
STR(VK_FORMAT_R64G64_SFLOAT);
STR(VK_FORMAT_R64G64B64_UINT);
STR(VK_FORMAT_R64G64B64_SINT);
STR(VK_FORMAT_R64G64B64_SFLOAT);
STR(VK_FORMAT_R64G64B64A64_UINT);
STR(VK_FORMAT_R64G64B64A64_SINT);
STR(VK_FORMAT_R64G64B64A64_SFLOAT);
STR(VK_FORMAT_B10G11R11_UFLOAT_PACK32);
STR(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32);
STR(VK_FORMAT_D16_UNORM);
STR(VK_FORMAT_X8_D24_UNORM_PACK32);
STR(VK_FORMAT_D32_SFLOAT);
STR(VK_FORMAT_S8_UINT);
STR(VK_FORMAT_D16_UNORM_S8_UINT);
STR(VK_FORMAT_D24_UNORM_S8_UINT);
STR(VK_FORMAT_D32_SFLOAT_S8_UINT);
STR(VK_FORMAT_BC1_RGB_UNORM_BLOCK);
STR(VK_FORMAT_BC1_RGB_SRGB_BLOCK);
STR(VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
STR(VK_FORMAT_BC1_RGBA_SRGB_BLOCK);
STR(VK_FORMAT_BC2_UNORM_BLOCK);
STR(VK_FORMAT_BC2_SRGB_BLOCK);
STR(VK_FORMAT_BC3_UNORM_BLOCK);
STR(VK_FORMAT_BC3_SRGB_BLOCK);
STR(VK_FORMAT_BC4_UNORM_BLOCK);
STR(VK_FORMAT_BC4_SNORM_BLOCK);
STR(VK_FORMAT_BC5_UNORM_BLOCK);
STR(VK_FORMAT_BC5_SNORM_BLOCK);
STR(VK_FORMAT_BC6H_UFLOAT_BLOCK);
STR(VK_FORMAT_BC6H_SFLOAT_BLOCK);
STR(VK_FORMAT_BC7_UNORM_BLOCK);
STR(VK_FORMAT_BC7_SRGB_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK);
STR(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK);
STR(VK_FORMAT_EAC_R11_UNORM_BLOCK);
STR(VK_FORMAT_EAC_R11_SNORM_BLOCK);
STR(VK_FORMAT_EAC_R11G11_UNORM_BLOCK);
STR(VK_FORMAT_EAC_R11G11_SNORM_BLOCK);
STR(VK_FORMAT_ASTC_4x4_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_4x4_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_5x4_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_5x4_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_5x5_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_5x5_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_6x5_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_6x5_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_6x6_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_6x6_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_8x5_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_8x5_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_8x6_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_8x6_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_8x8_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_8x8_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_10x5_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_10x5_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_10x6_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_10x6_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_10x8_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_10x8_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_10x10_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_10x10_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_12x10_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_12x10_SRGB_BLOCK);
STR(VK_FORMAT_ASTC_12x12_UNORM_BLOCK);
STR(VK_FORMAT_ASTC_12x12_SRGB_BLOCK);
#undef STR
default:
return "UNKNOWN_FORMAT";
void FlushMappedMemoryRange(const VulkanProvider& provider,
VkDeviceMemory memory, uint32_t memory_type,
VkDeviceSize offset, VkDeviceSize memory_size,
VkDeviceSize size) {
assert_false(size != VK_WHOLE_SIZE && memory_size == VK_WHOLE_SIZE);
assert_true(memory_size == VK_WHOLE_SIZE || offset <= memory_size);
assert_true(memory_size == VK_WHOLE_SIZE || size <= memory_size - offset);
if (!size ||
(provider.memory_types_host_coherent() & (uint32_t(1) << memory_type))) {
return;
}
}
const char* to_string(VkPhysicalDeviceType type) {
switch (type) {
#define STR(r) \
case r: \
return #r
STR(VK_PHYSICAL_DEVICE_TYPE_OTHER);
STR(VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU);
STR(VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU);
STR(VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU);
STR(VK_PHYSICAL_DEVICE_TYPE_CPU);
#undef STR
default:
return "UNKNOWN_DEVICE";
}
}
const char* to_string(VkSharingMode sharing_mode) {
switch (sharing_mode) {
#define STR(r) \
case r: \
return #r
STR(VK_SHARING_MODE_EXCLUSIVE);
STR(VK_SHARING_MODE_CONCURRENT);
#undef STR
default:
return "UNKNOWN_SHARING_MODE";
}
}
const char* to_string(VkResult result) {
switch (result) {
#define STR(r) \
case r: \
return #r
STR(VK_SUCCESS);
STR(VK_NOT_READY);
STR(VK_TIMEOUT);
STR(VK_EVENT_SET);
STR(VK_EVENT_RESET);
STR(VK_INCOMPLETE);
STR(VK_ERROR_OUT_OF_HOST_MEMORY);
STR(VK_ERROR_OUT_OF_DEVICE_MEMORY);
STR(VK_ERROR_INITIALIZATION_FAILED);
STR(VK_ERROR_DEVICE_LOST);
STR(VK_ERROR_MEMORY_MAP_FAILED);
STR(VK_ERROR_LAYER_NOT_PRESENT);
STR(VK_ERROR_EXTENSION_NOT_PRESENT);
STR(VK_ERROR_FEATURE_NOT_PRESENT);
STR(VK_ERROR_INCOMPATIBLE_DRIVER);
STR(VK_ERROR_TOO_MANY_OBJECTS);
STR(VK_ERROR_FORMAT_NOT_SUPPORTED);
STR(VK_ERROR_SURFACE_LOST_KHR);
STR(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR);
STR(VK_SUBOPTIMAL_KHR);
STR(VK_ERROR_OUT_OF_DATE_KHR);
STR(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
STR(VK_ERROR_VALIDATION_FAILED_EXT);
STR(VK_ERROR_INVALID_SHADER_NV);
STR(VK_ERROR_OUT_OF_POOL_MEMORY_KHR);
STR(VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR);
#undef STR
default:
return "UNKNOWN_RESULT";
}
}
std::string to_flags_string(VkImageUsageFlagBits flags) {
std::string result;
#define OR_FLAG(f) \
if (flags & f) { \
if (!result.empty()) { \
result += " | "; \
} \
result += #f; \
}
OR_FLAG(VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
OR_FLAG(VK_IMAGE_USAGE_TRANSFER_DST_BIT);
OR_FLAG(VK_IMAGE_USAGE_SAMPLED_BIT);
OR_FLAG(VK_IMAGE_USAGE_STORAGE_BIT);
OR_FLAG(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
OR_FLAG(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
OR_FLAG(VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT);
OR_FLAG(VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT);
#undef OR_FLAG
return result;
}
std::string to_flags_string(VkFormatFeatureFlagBits flags) {
std::string result;
#define OR_FLAG(f) \
if (flags & f) { \
if (!result.empty()) { \
result += " | "; \
} \
result += #f; \
}
OR_FLAG(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
OR_FLAG(VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
OR_FLAG(VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT);
OR_FLAG(VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT);
OR_FLAG(VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT);
OR_FLAG(VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT);
OR_FLAG(VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT);
OR_FLAG(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
OR_FLAG(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT);
OR_FLAG(VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
OR_FLAG(VK_FORMAT_FEATURE_BLIT_SRC_BIT);
OR_FLAG(VK_FORMAT_FEATURE_BLIT_DST_BIT);
OR_FLAG(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
OR_FLAG(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG);
OR_FLAG(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT_KHR);
OR_FLAG(VK_FORMAT_FEATURE_TRANSFER_DST_BIT_KHR);
OR_FLAG(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT);
#undef OR_FLAG
return result;
}
std::string to_flags_string(VkSurfaceTransformFlagBitsKHR flags) {
std::string result;
#define OR_FLAG(f) \
if (flags & f) { \
if (!result.empty()) { \
result += " | "; \
} \
result += #f; \
}
OR_FLAG(VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR);
OR_FLAG(VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR);
#undef OR_FLAG
return result;
}
const char* to_string(VkColorSpaceKHR color_space) {
switch (color_space) {
#define STR(r) \
case r: \
return #r
STR(VK_COLORSPACE_SRGB_NONLINEAR_KHR);
#undef STR
default:
return "UNKNOWN_COLORSPACE";
}
}
const char* to_string(VkPresentModeKHR present_mode) {
switch (present_mode) {
#define STR(r) \
case r: \
return #r
STR(VK_PRESENT_MODE_IMMEDIATE_KHR);
STR(VK_PRESENT_MODE_MAILBOX_KHR);
STR(VK_PRESENT_MODE_FIFO_KHR);
STR(VK_PRESENT_MODE_FIFO_RELAXED_KHR);
#undef STR
default:
return "UNKNOWN_PRESENT_MODE";
}
}
void FatalVulkanError(std::string error) {
xe::FatalError(
error +
"\n\n"
"Ensure you have the latest drivers for your GPU and that it supports "
"Vulkan.\n"
"\n"
"See https://xenia.jp/faq/ for more information and a list of supported "
"GPUs.");
}
void CheckResult(VkResult result, const char* action) {
if (result) {
XELOGE("Vulkan check: {} returned {}", action, to_string(result));
}
assert_true(result == VK_SUCCESS, action);
}
std::pair<bool, std::vector<const char*>> CheckRequirements(
const std::vector<Requirement>& requirements,
const std::vector<LayerInfo>& layer_infos) {
bool any_missing = false;
std::vector<const char*> enabled_layers;
for (auto& requirement : requirements) {
bool found = false;
for (size_t j = 0; j < layer_infos.size(); ++j) {
auto layer_name = layer_infos[j].properties.layerName;
auto layer_version =
Version::Parse(layer_infos[j].properties.specVersion);
if (requirement.name == layer_name) {
found = true;
if (requirement.min_version > layer_infos[j].properties.specVersion) {
if (requirement.is_optional) {
XELOGVK("- optional layer {} ({}) version mismatch", layer_name,
layer_version.pretty_string);
continue;
}
XELOGE("ERROR: required layer {} ({}) version mismatch", layer_name,
layer_version.pretty_string);
any_missing = true;
break;
}
XELOGVK("- enabling layer {} ({})", layer_name,
layer_version.pretty_string);
enabled_layers.push_back(layer_name);
break;
}
}
if (!found) {
if (requirement.is_optional) {
XELOGVK("- optional layer {} not found", requirement.name);
} else {
XELOGE("ERROR: required layer {} not found", requirement.name);
any_missing = true;
}
VkMappedMemoryRange range;
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.pNext = nullptr;
range.memory = memory;
range.offset = offset;
range.size = size;
VkDeviceSize non_coherent_atom_size =
provider.device_properties().limits.nonCoherentAtomSize;
// On some Android implementations, nonCoherentAtomSize is 0, not 1.
if (non_coherent_atom_size > 1) {
range.offset = offset / non_coherent_atom_size * non_coherent_atom_size;
if (size != VK_WHOLE_SIZE) {
range.size = std::min(xe::round_up(offset + size, non_coherent_atom_size),
memory_size) -
range.offset;
}
}
return {!any_missing, enabled_layers};
provider.dfn().vkFlushMappedMemoryRanges(provider.device(), 1, &range);
}
std::pair<bool, std::vector<const char*>> CheckRequirements(
const std::vector<Requirement>& requirements,
const std::vector<VkExtensionProperties>& extension_properties) {
bool any_missing = false;
std::vector<const char*> enabled_extensions;
for (auto& requirement : requirements) {
bool found = false;
for (size_t j = 0; j < extension_properties.size(); ++j) {
auto extension_name = extension_properties[j].extensionName;
auto extension_version =
Version::Parse(extension_properties[j].specVersion);
if (requirement.name == extension_name) {
found = true;
if (requirement.min_version > extension_properties[j].specVersion) {
if (requirement.is_optional) {
XELOGVK("- optional extension {} ({}) version mismatch",
extension_name, extension_version.pretty_string);
continue;
}
XELOGE("ERROR: required extension {} ({}) version mismatch",
extension_name, extension_version.pretty_string);
any_missing = true;
break;
}
XELOGVK("- enabling extension {} ({})", extension_name,
extension_version.pretty_string);
enabled_extensions.push_back(extension_name);
break;
}
}
if (!found) {
if (requirement.is_optional) {
XELOGVK("- optional extension {} not found", requirement.name);
} else {
XELOGE("ERROR: required extension {} not found", requirement.name);
any_missing = true;
}
}
bool CreateDedicatedAllocationBuffer(
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
VkDeviceMemory& memory_out, uint32_t* memory_type_out,
VkDeviceSize* memory_size_out) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
VkBufferCreateInfo buffer_create_info;
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.pNext = nullptr;
buffer_create_info.flags = 0;
buffer_create_info.size = size;
buffer_create_info.usage = usage;
buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_create_info.queueFamilyIndexCount = 0;
buffer_create_info.pQueueFamilyIndices = nullptr;
VkBuffer buffer;
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer) !=
VK_SUCCESS) {
return false;
}
return {!any_missing, enabled_extensions};
VkMemoryRequirements memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
uint32_t memory_type = ChooseMemoryType(
provider, memory_requirements.memoryTypeBits, memory_purpose);
if (memory_type == UINT32_MAX) {
dfn.vkDestroyBuffer(device, buffer, nullptr);
return false;
}
VkMemoryAllocateInfo memory_allocate_info;
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = memory_type;
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
if (provider.device_extensions().khr_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
memory_dedicated_allocate_info.pNext = nullptr;
memory_dedicated_allocate_info.image = VK_NULL_HANDLE;
memory_dedicated_allocate_info.buffer = buffer;
}
VkDeviceMemory memory;
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
VK_SUCCESS) {
dfn.vkDestroyBuffer(device, buffer, nullptr);
return false;
}
if (dfn.vkBindBufferMemory(device, buffer, memory, 0) != VK_SUCCESS) {
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, memory, nullptr);
return false;
}
buffer_out = buffer;
memory_out = memory;
if (memory_type_out) {
*memory_type_out = memory_type;
}
if (memory_size_out) {
*memory_size_out = memory_allocate_info.allocationSize;
}
return true;
}
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
const VkImageCreateInfo& create_info,
MemoryPurpose memory_purpose,
VkImage& image_out,
VkDeviceMemory& memory_out,
uint32_t* memory_type_out,
VkDeviceSize* memory_size_out) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
VkImage image;
if (dfn.vkCreateImage(device, &create_info, nullptr, &image) != VK_SUCCESS) {
return false;
}
VkMemoryRequirements memory_requirements;
dfn.vkGetImageMemoryRequirements(device, image, &memory_requirements);
uint32_t memory_type = ChooseMemoryType(
provider, memory_requirements.memoryTypeBits, memory_purpose);
if (memory_type == UINT32_MAX) {
dfn.vkDestroyImage(device, image, nullptr);
return false;
}
VkMemoryAllocateInfo memory_allocate_info;
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = memory_type;
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
if (provider.device_extensions().khr_dedicated_allocation) {
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
&memory_dedicated_allocate_info);
memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
memory_dedicated_allocate_info.pNext = nullptr;
memory_dedicated_allocate_info.image = image;
memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
}
VkDeviceMemory memory;
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
VK_SUCCESS) {
dfn.vkDestroyImage(device, image, nullptr);
return false;
}
if (dfn.vkBindImageMemory(device, image, memory, 0) != VK_SUCCESS) {
dfn.vkDestroyImage(device, image, nullptr);
dfn.vkFreeMemory(device, memory, nullptr);
return false;
}
image_out = image;
memory_out = memory;
if (memory_type_out) {
*memory_type_out = memory_type;
}
if (memory_size_out) {
*memory_size_out = memory_allocate_info.allocationSize;
}
return true;
}
} // namespace util
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -2,7 +2,7 @@
******************************************************************************
* 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. *
******************************************************************************
*/
@@ -10,21 +10,24 @@
#ifndef XENIA_UI_VULKAN_VULKAN_UTIL_H_
#define XENIA_UI_VULKAN_VULKAN_UTIL_H_
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdint>
#include "xenia/ui/vulkan/vulkan.h"
namespace xe {
namespace ui {
class Window;
} // namespace ui
} // namespace xe
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
namespace util {
inline void CheckResult(VkResult result, const char* action) {
if (result != VK_SUCCESS) {
XELOGE("Vulkan check: {} returned 0x{:X}", action, uint32_t(result));
}
assert_true(result == VK_SUCCESS, action);
}
template <typename F, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, T& handle) {
@@ -36,9 +39,8 @@ inline bool DestroyAndNullHandle(F* destroy_function, T& handle) {
return false;
}
template <typename F, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, VkInstance parent,
T& handle) {
template <typename F, typename P, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, P parent, T& handle) {
if (handle != VK_NULL_HANDLE) {
destroy_function(parent, handle, nullptr);
handle = VK_NULL_HANDLE;
@@ -47,86 +49,128 @@ inline bool DestroyAndNullHandle(F* destroy_function, VkInstance parent,
return false;
}
template <typename F, typename T>
inline bool DestroyAndNullHandle(F* destroy_function, VkDevice parent,
T& handle) {
if (handle != VK_NULL_HANDLE) {
destroy_function(parent, handle, nullptr);
handle = VK_NULL_HANDLE;
return true;
enum class MemoryPurpose {
kDeviceLocal,
kUpload,
kReadback,
};
inline VkDeviceSize GetMappableMemorySize(const VulkanProvider& provider,
VkDeviceSize size) {
VkDeviceSize non_coherent_atom_size =
provider.device_properties().limits.nonCoherentAtomSize;
// On some Android implementations, nonCoherentAtomSize is 0, not 1.
if (non_coherent_atom_size > 1) {
size = xe::round_up(size, non_coherent_atom_size, false);
}
return false;
return size;
}
struct Version {
uint32_t major;
uint32_t minor;
uint32_t patch;
std::string pretty_string;
inline uint32_t ChooseHostMemoryType(const VulkanProvider& provider,
uint32_t supported_types,
bool is_readback) {
supported_types &= provider.memory_types_host_visible();
uint32_t host_cached = provider.memory_types_host_cached();
uint32_t memory_type;
// For upload, uncached is preferred so writes do not pollute the CPU cache.
// For readback, cached is preferred so multiple CPU reads are fast.
// If the preferred caching behavior is not available, pick any host-visible.
if (xe::bit_scan_forward(
supported_types & (is_readback ? host_cached : ~host_cached),
&memory_type) ||
xe::bit_scan_forward(supported_types, &memory_type)) {
return memory_type;
}
return UINT32_MAX;
}
static uint32_t Make(uint32_t major, uint32_t minor, uint32_t patch);
inline uint32_t ChooseMemoryType(const VulkanProvider& provider,
uint32_t supported_types,
MemoryPurpose purpose) {
switch (purpose) {
case MemoryPurpose::kDeviceLocal: {
uint32_t memory_type;
return xe::bit_scan_forward(supported_types, &memory_type) ? memory_type
: UINT32_MAX;
} break;
case MemoryPurpose::kUpload:
case MemoryPurpose::kReadback:
return ChooseHostMemoryType(provider, supported_types,
purpose == MemoryPurpose::kReadback);
default:
assert_unhandled_case(purpose);
return UINT32_MAX;
}
}
static Version Parse(uint32_t value);
};
// Actual memory size is required if explicit size is specified for clamping to
// the actual memory allocation size while rounding to the non-coherent atom
// size (offset + size passed to vkFlushMappedMemoryRanges inside this function
// must be either a multiple of nonCoherentAtomSize (but not exceeding the
// memory size) or equal to the memory size).
void FlushMappedMemoryRange(const VulkanProvider& provider,
VkDeviceMemory memory, uint32_t memory_type,
VkDeviceSize offset = 0,
VkDeviceSize memory_size = VK_WHOLE_SIZE,
VkDeviceSize size = VK_WHOLE_SIZE);
const char* to_string(VkFormat format);
const char* to_string(VkPhysicalDeviceType type);
const char* to_string(VkSharingMode sharing_mode);
const char* to_string(VkResult result);
inline VkExtent2D GetMax2DFramebufferExtent(const VulkanProvider& provider) {
const VkPhysicalDeviceLimits& limits = provider.device_properties().limits;
VkExtent2D max_extent;
max_extent.width =
std::min(limits.maxFramebufferWidth, limits.maxImageDimension2D);
max_extent.height =
std::min(limits.maxFramebufferHeight, limits.maxImageDimension2D);
return max_extent;
}
std::string to_flags_string(VkImageUsageFlagBits flags);
std::string to_flags_string(VkFormatFeatureFlagBits flags);
std::string to_flags_string(VkSurfaceTransformFlagBitsKHR flags);
inline void InitializeSubresourceRange(
VkImageSubresourceRange& range,
VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT,
uint32_t base_mip_level = 0, uint32_t level_count = VK_REMAINING_MIP_LEVELS,
uint32_t base_array_layer = 0,
uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS) {
range.aspectMask = aspect_mask;
range.baseMipLevel = base_mip_level;
range.levelCount = level_count;
range.baseArrayLayer = base_array_layer;
range.layerCount = layer_count;
}
const char* to_string(VkColorSpaceKHR color_space);
const char* to_string(VkPresentModeKHR present_mode);
// Creates a buffer backed by a dedicated allocation. The allocation size will
// NOT be aligned to nonCoherentAtomSize - if mapping or flushing not the whole
// size, memory_size_out must be used for clamping the range.
bool CreateDedicatedAllocationBuffer(
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
VkDeviceMemory& memory_out, uint32_t* memory_type_out = nullptr,
VkDeviceSize* memory_size_out = nullptr);
// Throws a fatal error with some Vulkan help text.
void FatalVulkanError(std::string error);
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
const VkImageCreateInfo& create_info,
MemoryPurpose memory_purpose,
VkImage& image_out,
VkDeviceMemory& memory_out,
uint32_t* memory_type_out = nullptr,
VkDeviceSize* memory_size_out = nullptr);
// Logs and assets expecting the result to be VK_SUCCESS.
void CheckResult(VkResult result, const char* action);
struct LayerInfo {
VkLayerProperties properties;
std::vector<VkExtensionProperties> extensions;
};
struct DeviceInfo {
VkPhysicalDevice handle;
VkPhysicalDeviceProperties properties;
VkPhysicalDeviceFeatures features;
VkPhysicalDeviceMemoryProperties memory_properties;
std::vector<VkQueueFamilyProperties> queue_family_properties;
std::vector<LayerInfo> layers;
std::vector<VkExtensionProperties> extensions;
};
// Defines a requirement for a layer or extension, used to both verify and
// enable them on initialization.
struct Requirement {
// Layer or extension name.
std::string name;
// Minimum required spec version of the layer or extension.
uint32_t min_version;
// True if the requirement is optional (will not cause verification to fail).
bool is_optional;
};
// Gets a list of enabled layer names based on the given layer requirements and
// available layer info.
// Returns a boolean indicating whether all required layers are present.
std::pair<bool, std::vector<const char*>> CheckRequirements(
const std::vector<Requirement>& requirements,
const std::vector<LayerInfo>& layer_infos);
// Gets a list of enabled extension names based on the given extension
// requirements and available extensions.
// Returns a boolean indicating whether all required extensions are present.
std::pair<bool, std::vector<const char*>> CheckRequirements(
const std::vector<Requirement>& requirements,
const std::vector<VkExtensionProperties>& extension_properties);
inline VkShaderModule CreateShaderModule(const VulkanProvider& provider,
const void* code, size_t code_size) {
VkShaderModuleCreateInfo shader_module_create_info;
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.pNext = nullptr;
shader_module_create_info.flags = 0;
shader_module_create_info.codeSize = code_size;
shader_module_create_info.pCode = reinterpret_cast<const uint32_t*>(code);
VkShaderModule shader_module;
return provider.dfn().vkCreateShaderModule(
provider.device(), &shader_module_create_info, nullptr,
&shader_module) == VK_SUCCESS
? shader_module
: VK_NULL_HANDLE;
}
} // namespace util
} // namespace vulkan
} // namespace ui
} // namespace xe

View File

@@ -34,7 +34,7 @@ class VulkanWindowDemoApp final : public WindowDemoApp {
std::unique_ptr<GraphicsProvider> VulkanWindowDemoApp::CreateGraphicsProvider()
const {
return VulkanProvider::Create();
return VulkanProvider::Create(true);
}
} // namespace vulkan