[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:
@@ -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 = ℑ
|
||||
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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// VK_EXT_debug_report functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateDebugReportCallbackEXT)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyDebugReportCallbackEXT)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -1,2 +1,3 @@
|
||||
// VK_KHR_win32_surface functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateWin32SurfaceKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceWin32PresentationSupportKHR)
|
||||
|
||||
Binary file not shown.
@@ -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,
|
||||
};
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
Binary file not shown.
@@ -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
|
||||
2
src/xenia/ui/vulkan/shaders/bytecode/.clang-format
generated
Normal file
2
src/xenia/ui/vulkan/shaders/bytecode/.clang-format
generated
Normal file
@@ -0,0 +1,2 @@
|
||||
DisableFormat: true
|
||||
SortIncludes: false
|
||||
@@ -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,
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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,
|
||||
BIN
src/xenia/ui/vulkan/shaders/bytecode/vulkan_spirv/blit_depth_frag.spv
generated
Normal file
BIN
src/xenia/ui/vulkan/shaders/bytecode/vulkan_spirv/blit_depth_frag.spv
generated
Normal file
Binary file not shown.
@@ -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
|
||||
@@ -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,
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
2548
src/xenia/ui/vulkan/vulkan_presenter.cc
Normal file
2548
src/xenia/ui/vulkan/vulkan_presenter.cc
Normal file
File diff suppressed because it is too large
Load Diff
518
src/xenia/ui/vulkan/vulkan_presenter.h
Normal file
518
src/xenia/ui/vulkan/vulkan_presenter.h
Normal 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
@@ -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
|
||||
|
||||
174
src/xenia/ui/vulkan/vulkan_submission_tracker.cc
Normal file
174
src/xenia/ui/vulkan/vulkan_submission_tracker.cc
Normal 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
|
||||
135
src/xenia/ui/vulkan/vulkan_submission_tracker.h
Normal file
135
src/xenia/ui/vulkan/vulkan_submission_tracker.h
Normal 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_
|
||||
@@ -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, ©_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, ¤t_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, ©_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
|
||||
@@ -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_
|
||||
199
src/xenia/ui/vulkan/vulkan_upload_buffer_pool.cc
Normal file
199
src/xenia/ui/vulkan/vulkan_upload_buffer_pool.cc
Normal 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
|
||||
67
src/xenia/ui/vulkan/vulkan_upload_buffer_pool.h
Normal file
67
src/xenia/ui/vulkan/vulkan_upload_buffer_pool.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user