This commit is contained in:
2026-03-23 12:11:07 +01:00
commit e64eb40b38
4573 changed files with 3117439 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "buffer.hpp"
#include "device.hpp"
namespace Vulkan
{
Buffer::Buffer(Device *device_, VkBuffer buffer_, const DeviceAllocation &alloc_, const BufferCreateInfo &info_,
VkDeviceAddress bda_)
: Cookie(device_)
, device(device_)
, buffer(buffer_)
, alloc(alloc_)
, info(info_)
, bda(bda_)
{
}
ExternalHandle Buffer::export_handle()
{
return alloc.export_handle(*device);
}
Buffer::~Buffer()
{
if (internal_sync)
{
device->destroy_buffer_nolock(buffer);
device->free_memory_nolock(alloc);
}
else
{
device->destroy_buffer(buffer);
device->free_memory(alloc);
}
}
void BufferDeleter::operator()(Buffer *buffer)
{
buffer->device->handle_pool.buffers.free(buffer);
}
BufferView::BufferView(Device *device_, VkBufferView view_, const BufferViewCreateInfo &create_info_)
: Cookie(device_)
, device(device_)
, view(view_)
, info(create_info_)
{
}
BufferView::~BufferView()
{
if (view != VK_NULL_HANDLE)
{
if (internal_sync)
device->destroy_buffer_view_nolock(view);
else
device->destroy_buffer_view(view);
}
}
void BufferViewDeleter::operator()(BufferView *view)
{
view->device->handle_pool.buffer_views.free(view);
}
}

View File

@@ -0,0 +1,162 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "cookie.hpp"
#include "vulkan_common.hpp"
#include "memory_allocator.hpp"
namespace Vulkan
{
class Device;
enum class BufferDomain
{
Device, // Device local. Probably not visible from CPU.
LinkedDeviceHost, // On desktop, directly mapped VRAM over PCI.
LinkedDeviceHostPreferDevice, // Prefer device local of host visible.
Host, // Host-only, needs to be synced to GPU. Might be device local as well on iGPUs.
CachedHost,
CachedCoherentHostPreferCoherent, // Aim for both cached and coherent, but prefer COHERENT
CachedCoherentHostPreferCached, // Aim for both cached and coherent, but prefer CACHED
};
enum BufferMiscFlagBits
{
BUFFER_MISC_ZERO_INITIALIZE_BIT = 1 << 0,
BUFFER_MISC_EXTERNAL_MEMORY_BIT = 1 << 1
};
using BufferMiscFlags = uint32_t;
struct BufferCreateInfo
{
BufferDomain domain = BufferDomain::Device;
VkDeviceSize size = 0;
VkBufferUsageFlags usage = 0;
BufferMiscFlags misc = 0;
VkMemoryRequirements allocation_requirements = {};
ExternalHandle external;
void *pnext = nullptr;
};
class Buffer;
struct BufferDeleter
{
void operator()(Buffer *buffer);
};
class BufferView;
struct BufferViewDeleter
{
void operator()(BufferView *view);
};
class Buffer : public Util::IntrusivePtrEnabled<Buffer, BufferDeleter, HandleCounter>,
public Cookie, public InternalSyncEnabled
{
public:
friend struct BufferDeleter;
~Buffer();
VkBuffer get_buffer() const
{
return buffer;
}
const BufferCreateInfo &get_create_info() const
{
return info;
}
DeviceAllocation &get_allocation()
{
return alloc;
}
const DeviceAllocation &get_allocation() const
{
return alloc;
}
ExternalHandle export_handle();
VkDeviceAddress get_device_address() const
{
VK_ASSERT(bda);
return bda;
}
private:
friend class Util::ObjectPool<Buffer>;
Buffer(Device *device, VkBuffer buffer, const DeviceAllocation &alloc, const BufferCreateInfo &info,
VkDeviceAddress bda);
Device *device;
VkBuffer buffer;
DeviceAllocation alloc;
BufferCreateInfo info;
VkDeviceAddress bda;
};
using BufferHandle = Util::IntrusivePtr<Buffer>;
struct BufferViewCreateInfo
{
const Buffer *buffer;
VkFormat format;
VkDeviceSize offset;
VkDeviceSize range;
};
class BufferView : public Util::IntrusivePtrEnabled<BufferView, BufferViewDeleter, HandleCounter>,
public Cookie, public InternalSyncEnabled
{
public:
friend struct BufferViewDeleter;
~BufferView();
VkBufferView get_view() const
{
return view;
}
const BufferViewCreateInfo &get_create_info()
{
return info;
}
const Buffer &get_buffer() const
{
return *info.buffer;
}
private:
friend class Util::ObjectPool<BufferView>;
BufferView(Device *device, VkBufferView view, const BufferViewCreateInfo &info);
Device *device;
VkBufferView view;
BufferViewCreateInfo info;
};
using BufferViewHandle = Util::IntrusivePtr<BufferView>;
}

View File

@@ -0,0 +1,138 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "buffer_pool.hpp"
#include "device.hpp"
#include <utility>
namespace Vulkan
{
void BufferPool::init(Device *device_, VkDeviceSize block_size_,
VkDeviceSize alignment_, VkBufferUsageFlags usage_)
{
device = device_;
block_size = block_size_;
alignment = alignment_;
usage = usage_;
}
void BufferPool::set_spill_region_size(VkDeviceSize spill_size_)
{
spill_size = spill_size_;
}
void BufferPool::set_max_retained_blocks(size_t max_blocks)
{
max_retained_blocks = max_blocks;
}
BufferBlock::~BufferBlock()
{
}
void BufferPool::reset()
{
blocks.clear();
}
BufferBlock BufferPool::allocate_block(VkDeviceSize size)
{
BufferDomain ideal_domain = ((usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT) != 0) ?
BufferDomain::Host : BufferDomain::LinkedDeviceHost;
BufferBlock block;
BufferCreateInfo info;
info.domain = ideal_domain;
info.size = size;
info.usage = usage;
block.buffer = device->create_buffer(info, nullptr);
device->set_name(*block.buffer, "chain-allocated-block");
block.buffer->set_internal_sync_object();
// Try to map it, will fail unless the memory is host visible.
block.mapped = static_cast<uint8_t *>(device->map_host_buffer(*block.buffer, MEMORY_ACCESS_WRITE_BIT));
block.offset = 0;
block.alignment = alignment;
block.size = size;
block.spill_size = spill_size;
return block;
}
BufferBlock BufferPool::request_block(VkDeviceSize minimum_size)
{
if ((minimum_size > block_size) || blocks.empty())
{
return allocate_block(std::max(block_size, minimum_size));
}
else
{
auto back = std::move(blocks.back());
blocks.pop_back();
back.mapped = static_cast<uint8_t *>(device->map_host_buffer(*back.buffer, MEMORY_ACCESS_WRITE_BIT));
back.offset = 0;
return back;
}
}
void BufferPool::recycle_block(BufferBlock &block)
{
VK_ASSERT(block.size == block_size);
if (blocks.size() < max_retained_blocks)
blocks.push_back(std::move(block));
else
block = {};
}
BufferPool::~BufferPool()
{
VK_ASSERT(blocks.empty());
}
BufferBlockAllocation BufferBlock::allocate(VkDeviceSize allocate_size)
{
auto aligned_offset = (offset + alignment - 1) & ~(alignment - 1);
if (aligned_offset + allocate_size <= size)
{
auto *ret = mapped + aligned_offset;
offset = aligned_offset + allocate_size;
VkDeviceSize padded_size = std::max<VkDeviceSize>(allocate_size, spill_size);
padded_size = std::min<VkDeviceSize>(padded_size, size - aligned_offset);
return { ret, buffer, aligned_offset, padded_size };
}
else
return { nullptr, {}, 0, 0 };
}
void BufferBlock::unmap(Device &device)
{
device.unmap_host_buffer(*buffer, MEMORY_ACCESS_WRITE_BIT);
mapped = nullptr;
}
}

View File

@@ -0,0 +1,96 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "intrusive.hpp"
#include <vector>
#include <algorithm>
namespace Vulkan
{
class Device;
class Buffer;
struct BufferBlockAllocation
{
uint8_t *host;
Util::IntrusivePtr<Buffer> buffer;
VkDeviceSize offset;
VkDeviceSize padded_size;
};
class BufferBlock
{
public:
~BufferBlock();
BufferBlockAllocation allocate(VkDeviceSize allocate_size);
inline bool is_mapped() const { return mapped != nullptr; }
const Buffer &get_buffer() const { return *buffer; }
void unmap(Device &device);
inline VkDeviceSize get_offset() const { return offset; }
inline VkDeviceSize get_size() const { return size; }
private:
friend class BufferPool;
Util::IntrusivePtr<Buffer> buffer;
VkDeviceSize offset = 0;
VkDeviceSize alignment = 0;
VkDeviceSize size = 0;
VkDeviceSize spill_size = 0;
uint8_t *mapped = nullptr;
};
class BufferPool
{
public:
~BufferPool();
void init(Device *device, VkDeviceSize block_size, VkDeviceSize alignment, VkBufferUsageFlags usage);
void reset();
// Used for allocating UBOs, where we want to specify a fixed size for range,
// and we need to make sure we don't allocate beyond the block.
void set_spill_region_size(VkDeviceSize spill_size);
void set_max_retained_blocks(size_t max_blocks);
VkDeviceSize get_block_size() const
{
return block_size;
}
BufferBlock request_block(VkDeviceSize minimum_size);
void recycle_block(BufferBlock &block);
private:
Device *device = nullptr;
VkDeviceSize block_size = 0;
VkDeviceSize alignment = 0;
VkDeviceSize spill_size = 0;
VkBufferUsageFlags usage = 0;
size_t max_retained_blocks = 0;
std::vector<BufferBlock> blocks;
BufferBlock allocate_block(VkDeviceSize size);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,958 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "buffer.hpp"
#include "buffer_pool.hpp"
#include "vulkan_headers.hpp"
#include "image.hpp"
#include "pipeline_event.hpp"
#include "query_pool.hpp"
#include "render_pass.hpp"
#include "sampler.hpp"
#include "shader.hpp"
#include "vulkan_common.hpp"
#include <string.h>
namespace Vulkan
{
class DebugChannelInterface;
class IndirectLayout;
static inline VkPipelineStageFlags convert_vk_stage2(VkPipelineStageFlags2 stages)
{
constexpr VkPipelineStageFlags2 transfer_mask =
VK_PIPELINE_STAGE_2_COPY_BIT |
VK_PIPELINE_STAGE_2_BLIT_BIT |
VK_PIPELINE_STAGE_2_RESOLVE_BIT |
VK_PIPELINE_STAGE_2_CLEAR_BIT |
VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT_KHR;
constexpr VkPipelineStageFlags2 preraster_mask =
VK_PIPELINE_STAGE_2_PRE_RASTERIZATION_SHADERS_BIT;
if ((stages & transfer_mask) != 0)
{
stages |= VK_PIPELINE_STAGE_TRANSFER_BIT;
stages &= ~transfer_mask;
}
if ((stages & preraster_mask) != 0)
{
// TODO: Augment if we add mesh shader support eventually.
stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
stages &= ~preraster_mask;
}
return VkPipelineStageFlags(stages);
}
static inline VkPipelineStageFlags convert_vk_src_stage2(VkPipelineStageFlags2 stages)
{
stages = convert_vk_stage2(stages);
if (stages == VK_PIPELINE_STAGE_NONE)
stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
return VkPipelineStageFlags(stages);
}
static inline VkPipelineStageFlags convert_vk_dst_stage2(VkPipelineStageFlags2 stages)
{
stages = convert_vk_stage2(stages);
if (stages == VK_PIPELINE_STAGE_NONE)
stages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
return VkPipelineStageFlags(stages);
}
static inline VkAccessFlags convert_vk_access_flags2(VkAccessFlags2 access)
{
constexpr VkAccessFlags2 sampled_mask =
VK_ACCESS_2_SHADER_SAMPLED_READ_BIT |
VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
VK_ACCESS_2_SHADER_BINDING_TABLE_READ_BIT_KHR;
constexpr VkAccessFlags2 storage_mask =
VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
if ((access & sampled_mask) != 0)
{
access |= VK_ACCESS_SHADER_READ_BIT;
access &= ~sampled_mask;
}
if ((access & storage_mask) != 0)
{
access |= VK_ACCESS_SHADER_WRITE_BIT;
access &= ~storage_mask;
}
return VkAccessFlags(access);
}
enum CommandBufferDirtyBits
{
COMMAND_BUFFER_DIRTY_STATIC_STATE_BIT = 1 << 0,
COMMAND_BUFFER_DIRTY_PIPELINE_BIT = 1 << 1,
COMMAND_BUFFER_DIRTY_VIEWPORT_BIT = 1 << 2,
COMMAND_BUFFER_DIRTY_SCISSOR_BIT = 1 << 3,
COMMAND_BUFFER_DIRTY_DEPTH_BIAS_BIT = 1 << 4,
COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT = 1 << 5,
COMMAND_BUFFER_DIRTY_STATIC_VERTEX_BIT = 1 << 6,
COMMAND_BUFFER_DIRTY_PUSH_CONSTANTS_BIT = 1 << 7,
COMMAND_BUFFER_DYNAMIC_BITS = COMMAND_BUFFER_DIRTY_VIEWPORT_BIT | COMMAND_BUFFER_DIRTY_SCISSOR_BIT |
COMMAND_BUFFER_DIRTY_DEPTH_BIAS_BIT |
COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT
};
using CommandBufferDirtyFlags = uint32_t;
#define COMPARE_OP_BITS 3
#define STENCIL_OP_BITS 3
#define BLEND_FACTOR_BITS 5
#define BLEND_OP_BITS 3
#define CULL_MODE_BITS 2
#define FRONT_FACE_BITS 1
#define TOPOLOGY_BITS 4
union PipelineState {
struct
{
// Word 0, tightly packed.
uint32_t depth_write : 1;
uint32_t depth_test : 1;
uint32_t blend_enable : 1;
uint32_t cull_mode : CULL_MODE_BITS;
uint32_t front_face : FRONT_FACE_BITS;
uint32_t depth_compare : COMPARE_OP_BITS;
uint32_t depth_bias_enable : 1;
uint32_t stencil_test : 1;
uint32_t stencil_front_fail : STENCIL_OP_BITS;
uint32_t stencil_front_pass : STENCIL_OP_BITS;
uint32_t stencil_front_depth_fail : STENCIL_OP_BITS;
uint32_t stencil_front_compare_op : COMPARE_OP_BITS;
uint32_t stencil_back_fail : STENCIL_OP_BITS;
uint32_t stencil_back_pass : STENCIL_OP_BITS;
uint32_t stencil_back_depth_fail : STENCIL_OP_BITS;
// Word 1, tightly packed.
uint32_t stencil_back_compare_op : COMPARE_OP_BITS;
uint32_t alpha_to_coverage : 1;
uint32_t alpha_to_one : 1;
uint32_t sample_shading : 1;
uint32_t src_color_blend : BLEND_FACTOR_BITS;
uint32_t dst_color_blend : BLEND_FACTOR_BITS;
uint32_t color_blend_op : BLEND_OP_BITS;
uint32_t src_alpha_blend : BLEND_FACTOR_BITS;
uint32_t dst_alpha_blend : BLEND_FACTOR_BITS;
uint32_t alpha_blend_op : BLEND_OP_BITS;
// Word 2, tightly packed.
uint32_t primitive_restart : 1;
uint32_t topology : TOPOLOGY_BITS;
uint32_t wireframe : 1;
uint32_t subgroup_control_size : 1;
uint32_t subgroup_full_group : 1;
uint32_t subgroup_minimum_size_log2 : 3;
uint32_t subgroup_maximum_size_log2 : 3;
uint32_t subgroup_control_size_task : 1;
uint32_t subgroup_full_group_task : 1;
uint32_t subgroup_minimum_size_log2_task : 3;
uint32_t subgroup_maximum_size_log2_task : 3;
uint32_t conservative_raster : 1;
uint32_t padding : 9;
// Word 3
uint32_t write_mask;
} state;
uint32_t words[4];
};
struct PotentialState
{
float blend_constants[4];
uint32_t spec_constants[VULKAN_NUM_TOTAL_SPEC_CONSTANTS];
uint8_t spec_constant_mask;
uint8_t internal_spec_constant_mask;
};
struct DynamicState
{
float depth_bias_constant = 0.0f;
float depth_bias_slope = 0.0f;
uint8_t front_compare_mask = 0;
uint8_t front_write_mask = 0;
uint8_t front_reference = 0;
uint8_t back_compare_mask = 0;
uint8_t back_write_mask = 0;
uint8_t back_reference = 0;
};
struct VertexAttribState
{
uint32_t binding;
VkFormat format;
uint32_t offset;
};
struct IndexState
{
VkBuffer buffer;
VkDeviceSize offset;
VkIndexType index_type;
};
struct VertexBindingState
{
VkBuffer buffers[VULKAN_NUM_VERTEX_BUFFERS];
VkDeviceSize offsets[VULKAN_NUM_VERTEX_BUFFERS];
};
enum CommandBufferSavedStateBits
{
COMMAND_BUFFER_SAVED_BINDINGS_0_BIT = 1u << 0,
COMMAND_BUFFER_SAVED_BINDINGS_1_BIT = 1u << 1,
COMMAND_BUFFER_SAVED_BINDINGS_2_BIT = 1u << 2,
COMMAND_BUFFER_SAVED_BINDINGS_3_BIT = 1u << 3,
COMMAND_BUFFER_SAVED_VIEWPORT_BIT = 1u << 4,
COMMAND_BUFFER_SAVED_SCISSOR_BIT = 1u << 5,
COMMAND_BUFFER_SAVED_RENDER_STATE_BIT = 1u << 6,
COMMAND_BUFFER_SAVED_PUSH_CONSTANT_BIT = 1u << 7
};
static_assert(VULKAN_NUM_DESCRIPTOR_SETS == 4, "Number of descriptor sets != 4.");
using CommandBufferSaveStateFlags = uint32_t;
struct CommandBufferSavedState
{
CommandBufferSaveStateFlags flags;
ResourceBindings bindings;
VkViewport viewport;
VkRect2D scissor;
PipelineState static_state;
PotentialState potential_static_state;
DynamicState dynamic_state;
};
struct DeferredPipelineCompile
{
Program *program;
const PipelineLayout *layout;
std::vector<Program *> program_group;
const RenderPass *compatible_render_pass;
PipelineState static_state;
PotentialState potential_static_state;
VertexAttribState attribs[VULKAN_NUM_VERTEX_ATTRIBS];
VkDeviceSize strides[VULKAN_NUM_VERTEX_BUFFERS];
VkVertexInputRate input_rates[VULKAN_NUM_VERTEX_BUFFERS];
unsigned subpass_index;
Util::Hash hash;
VkPipelineCache cache;
uint32_t subgroup_size_tag;
};
class CommandBuffer;
struct CommandBufferDeleter
{
void operator()(CommandBuffer *cmd);
};
class Device;
class CommandBuffer : public Util::IntrusivePtrEnabled<CommandBuffer, CommandBufferDeleter, HandleCounter>
{
public:
friend struct CommandBufferDeleter;
enum class Type
{
Generic = QUEUE_INDEX_GRAPHICS,
AsyncCompute = QUEUE_INDEX_COMPUTE,
AsyncTransfer = QUEUE_INDEX_TRANSFER,
VideoDecode = QUEUE_INDEX_VIDEO_DECODE,
VideoEncode = QUEUE_INDEX_VIDEO_ENCODE,
Count
};
~CommandBuffer();
VkCommandBuffer get_command_buffer() const
{
return cmd;
}
void begin_region(const char *name, const float *color = nullptr);
void insert_label(const char *name, const float *color = nullptr);
void end_region();
Device &get_device()
{
return *device;
}
VkPipelineStageFlags2 swapchain_touched_in_stages() const
{
return uses_swapchain_in_stages;
}
// Only used when using swapchain in non-obvious ways, like compute or transfer.
void swapchain_touch_in_stages(VkPipelineStageFlags2 stages)
{
uses_swapchain_in_stages |= stages;
}
void set_thread_index(unsigned index_)
{
thread_index = index_;
}
unsigned get_thread_index() const
{
return thread_index;
}
void set_is_secondary()
{
is_secondary = true;
}
bool get_is_secondary() const
{
return is_secondary;
}
void clear_image(const Image &image, const VkClearValue &value);
void clear_image(const Image &image, const VkClearValue &value, VkImageAspectFlags aspect);
void clear_quad(unsigned attachment, const VkClearRect &rect, const VkClearValue &value,
VkImageAspectFlags = VK_IMAGE_ASPECT_COLOR_BIT);
void clear_quad(const VkClearRect &rect, const VkClearAttachment *attachments, unsigned num_attachments);
void fill_buffer(const Buffer &dst, uint32_t value);
void fill_buffer(const Buffer &dst, uint32_t value, VkDeviceSize offset, VkDeviceSize size);
void copy_buffer(const Buffer &dst, VkDeviceSize dst_offset, const Buffer &src, VkDeviceSize src_offset,
VkDeviceSize size);
void copy_buffer(const Buffer &dst, const Buffer &src);
void copy_buffer(const Buffer &dst, const Buffer &src, const VkBufferCopy *copies, size_t count);
void copy_image(const Image &dst, const Image &src);
void copy_image(const Image &dst, const Image &src,
const VkOffset3D &dst_offset, const VkOffset3D &src_offset,
const VkExtent3D &extent,
const VkImageSubresourceLayers &dst_subresource,
const VkImageSubresourceLayers &src_subresource);
void copy_buffer_to_image(const Image &image, const Buffer &buffer, VkDeviceSize buffer_offset,
const VkOffset3D &offset, const VkExtent3D &extent, unsigned row_length,
unsigned slice_height, const VkImageSubresourceLayers &subresrouce);
void copy_buffer_to_image(const Image &image, const Buffer &buffer, unsigned num_blits, const VkBufferImageCopy *blits);
void copy_image_to_buffer(const Buffer &buffer, const Image &image, unsigned num_blits, const VkBufferImageCopy *blits);
void copy_image_to_buffer(const Buffer &dst, const Image &src, VkDeviceSize buffer_offset, const VkOffset3D &offset,
const VkExtent3D &extent, unsigned row_length, unsigned slice_height,
const VkImageSubresourceLayers &subresrouce);
void full_barrier();
void pixel_barrier();
// Simplified global memory barrier.
void barrier(VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
VkPipelineStageFlags2 dst_stage, VkAccessFlags2 dst_access);
PipelineEvent signal_event(const VkDependencyInfo &dep);
void wait_events(uint32_t num_events, const PipelineEvent *events, const VkDependencyInfo *deps);
// Full expressive barrier.
void barrier(const VkDependencyInfo &dep);
void buffer_barrier(const Buffer &buffer,
VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
VkPipelineStageFlags2 dst_stage, VkAccessFlags2 dst_access);
void image_barrier(const Image &image,
VkImageLayout old_layout, VkImageLayout new_layout,
VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
VkPipelineStageFlags2 dst_stage, VkAccessFlags2 dst_access);
void buffer_barriers(uint32_t buffer_barriers, const VkBufferMemoryBarrier2 *buffers);
void image_barriers(uint32_t image_barriers, const VkImageMemoryBarrier2 *images);
void release_buffer_barrier(const Buffer &buffer, VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
uint32_t dst_queue_family = VK_QUEUE_FAMILY_EXTERNAL);
void acquire_buffer_barrier(const Buffer &buffer, VkPipelineStageFlags2 dst_stage, VkAccessFlags2 dst_access,
uint32_t src_queue_family = VK_QUEUE_FAMILY_EXTERNAL);
void release_image_barrier(const Image &image,
VkImageLayout old_layout, VkImageLayout new_layout,
VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
uint32_t dst_queue_family = VK_QUEUE_FAMILY_EXTERNAL);
void acquire_image_barrier(const Image &image, VkImageLayout old_layout, VkImageLayout new_layout,
VkPipelineStageFlags2 dst_stage, VkAccessFlags2 dst_access,
uint32_t src_queue_family = VK_QUEUE_FAMILY_EXTERNAL);
void blit_image(const Image &dst,
const Image &src,
const VkOffset3D &dst_offset0, const VkOffset3D &dst_extent,
const VkOffset3D &src_offset0, const VkOffset3D &src_extent, unsigned dst_level, unsigned src_level,
unsigned dst_base_layer = 0, uint32_t src_base_layer = 0, unsigned num_layers = 1,
VkFilter filter = VK_FILTER_LINEAR);
// Prepares an image to have its mipmap generated.
// Puts the top-level into TRANSFER_SRC_OPTIMAL, and all other levels are invalidated with an UNDEFINED -> TRANSFER_DST_OPTIMAL.
void barrier_prepare_generate_mipmap(const Image &image, VkImageLayout base_level_layout,
VkPipelineStageFlags2 src_stage, VkAccessFlags2 src_access,
bool need_top_level_barrier = true);
// The image must have been transitioned with barrier_prepare_generate_mipmap before calling this function.
// After calling this function, the image will be entirely in TRANSFER_SRC_OPTIMAL layout.
// Wait for TRANSFER stage to drain before transitioning away from TRANSFER_SRC_OPTIMAL.
void generate_mipmap(const Image &image);
void begin_render_pass(const RenderPassInfo &info, VkSubpassContents contents = VK_SUBPASS_CONTENTS_INLINE);
void next_subpass(VkSubpassContents contents = VK_SUBPASS_CONTENTS_INLINE);
void end_render_pass();
void submit_secondary(Util::IntrusivePtr<CommandBuffer> secondary);
inline unsigned get_current_subpass() const
{
return pipeline_state.subpass_index;
}
Util::IntrusivePtr<CommandBuffer> request_secondary_command_buffer(unsigned thread_index, unsigned subpass);
static Util::IntrusivePtr<CommandBuffer> request_secondary_command_buffer(Device &device,
const RenderPassInfo &rp, unsigned thread_index, unsigned subpass);
void set_program(Program *program);
void set_program_group(Program * const *programs, unsigned num_programs, const PipelineLayout *layout);
#ifdef GRANITE_VULKAN_SYSTEM_HANDLES
// Convenience functions for one-off shader binds.
void set_program(const std::string &task, const std::string &mesh, const std::string &fragment,
const std::vector<std::pair<std::string, int>> &defines = {});
void set_program(const std::string &vertex, const std::string &fragment,
const std::vector<std::pair<std::string, int>> &defines = {});
void set_program(const std::string &compute,
const std::vector<std::pair<std::string, int>> &defines = {});
#endif
void set_buffer_view(unsigned set, unsigned binding, const BufferView &view);
void set_storage_buffer_view(unsigned set, unsigned binding, const BufferView &view);
void set_input_attachments(unsigned set, unsigned start_binding);
void set_texture(unsigned set, unsigned binding, const ImageView &view);
void set_unorm_texture(unsigned set, unsigned binding, const ImageView &view);
void set_srgb_texture(unsigned set, unsigned binding, const ImageView &view);
void set_texture(unsigned set, unsigned binding, const ImageView &view, const Sampler &sampler);
void set_texture(unsigned set, unsigned binding, const ImageView &view, StockSampler sampler);
void set_storage_texture(unsigned set, unsigned binding, const ImageView &view);
void set_unorm_storage_texture(unsigned set, unsigned binding, const ImageView &view);
void set_sampler(unsigned set, unsigned binding, const Sampler &sampler);
void set_sampler(unsigned set, unsigned binding, StockSampler sampler);
void set_uniform_buffer(unsigned set, unsigned binding, const Buffer &buffer);
void set_uniform_buffer(unsigned set, unsigned binding, const Buffer &buffer, VkDeviceSize offset,
VkDeviceSize range);
void set_storage_buffer(unsigned set, unsigned binding, const Buffer &buffer);
void set_storage_buffer(unsigned set, unsigned binding, const Buffer &buffer, VkDeviceSize offset,
VkDeviceSize range);
void set_bindless(unsigned set, VkDescriptorSet desc_set);
void push_constants(const void *data, VkDeviceSize offset, VkDeviceSize range);
void *allocate_constant_data(unsigned set, unsigned binding, VkDeviceSize size);
template <typename T>
T *allocate_typed_constant_data(unsigned set, unsigned binding, unsigned count)
{
return static_cast<T *>(allocate_constant_data(set, binding, count * sizeof(T)));
}
void *allocate_vertex_data(unsigned binding, VkDeviceSize size, VkDeviceSize stride,
VkVertexInputRate step_rate = VK_VERTEX_INPUT_RATE_VERTEX);
void *allocate_index_data(VkDeviceSize size, VkIndexType index_type);
void *update_buffer(const Buffer &buffer, VkDeviceSize offset, VkDeviceSize size);
void *update_image(const Image &image, const VkOffset3D &offset, const VkExtent3D &extent, uint32_t row_length,
uint32_t image_height, const VkImageSubresourceLayers &subresource);
void *update_image(const Image &image, uint32_t row_length = 0, uint32_t image_height = 0);
BufferBlockAllocation request_scratch_buffer_memory(VkDeviceSize size);
void set_viewport(const VkViewport &viewport);
const VkViewport &get_viewport() const;
void set_scissor(const VkRect2D &rect);
void set_vertex_attrib(uint32_t attrib, uint32_t binding, VkFormat format, VkDeviceSize offset);
void set_vertex_binding(uint32_t binding, const Buffer &buffer, VkDeviceSize offset, VkDeviceSize stride,
VkVertexInputRate step_rate = VK_VERTEX_INPUT_RATE_VERTEX);
void set_index_buffer(const Buffer &buffer, VkDeviceSize offset, VkIndexType index_type);
void draw(uint32_t vertex_count, uint32_t instance_count = 1, uint32_t first_vertex = 0,
uint32_t first_instance = 0);
void draw_indexed(uint32_t index_count, uint32_t instance_count = 1, uint32_t first_index = 0,
int32_t vertex_offset = 0, uint32_t first_instance = 0);
void draw_mesh_tasks(uint32_t tasks_x, uint32_t tasks_y, uint32_t tasks_z);
void dispatch(uint32_t groups_x, uint32_t groups_y, uint32_t groups_z);
void draw_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride);
void draw_indexed_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride);
void draw_multi_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride,
const Buffer &count, VkDeviceSize count_offset);
void draw_indexed_multi_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride,
const Buffer &count, VkDeviceSize count_offset);
void dispatch_indirect(const Buffer &buffer, VkDeviceSize offset);
void draw_mesh_tasks_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride);
void draw_mesh_tasks_multi_indirect(const Buffer &buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride,
const Buffer &count, VkDeviceSize count_offset);
void execute_indirect_commands(const IndirectLayout *indirect_layout,
uint32_t sequences,
const Buffer &indirect, VkDeviceSize offset,
const Buffer *count, size_t count_offset);
void set_opaque_state();
void set_quad_state();
void set_opaque_sprite_state();
void set_transparent_sprite_state();
void save_state(CommandBufferSaveStateFlags flags, CommandBufferSavedState &state);
void restore_state(const CommandBufferSavedState &state);
#define SET_STATIC_STATE(value) \
do \
{ \
if (pipeline_state.static_state.state.value != value) \
{ \
pipeline_state.static_state.state.value = value; \
set_dirty(COMMAND_BUFFER_DIRTY_STATIC_STATE_BIT); \
} \
} while (0)
#define SET_POTENTIALLY_STATIC_STATE(value) \
do \
{ \
if (pipeline_state.potential_static_state.value != value) \
{ \
pipeline_state.potential_static_state.value = value; \
set_dirty(COMMAND_BUFFER_DIRTY_STATIC_STATE_BIT); \
} \
} while (0)
inline void set_depth_test(bool depth_test, bool depth_write)
{
SET_STATIC_STATE(depth_test);
SET_STATIC_STATE(depth_write);
}
inline void set_wireframe(bool wireframe)
{
SET_STATIC_STATE(wireframe);
}
inline void set_depth_compare(VkCompareOp depth_compare)
{
SET_STATIC_STATE(depth_compare);
}
inline void set_blend_enable(bool blend_enable)
{
SET_STATIC_STATE(blend_enable);
}
inline void set_blend_factors(VkBlendFactor src_color_blend, VkBlendFactor src_alpha_blend,
VkBlendFactor dst_color_blend, VkBlendFactor dst_alpha_blend)
{
SET_STATIC_STATE(src_color_blend);
SET_STATIC_STATE(dst_color_blend);
SET_STATIC_STATE(src_alpha_blend);
SET_STATIC_STATE(dst_alpha_blend);
}
inline void set_blend_factors(VkBlendFactor src_blend, VkBlendFactor dst_blend)
{
set_blend_factors(src_blend, src_blend, dst_blend, dst_blend);
}
inline void set_blend_op(VkBlendOp color_blend_op, VkBlendOp alpha_blend_op)
{
SET_STATIC_STATE(color_blend_op);
SET_STATIC_STATE(alpha_blend_op);
}
inline void set_blend_op(VkBlendOp blend_op)
{
set_blend_op(blend_op, blend_op);
}
inline void set_depth_bias(bool depth_bias_enable)
{
SET_STATIC_STATE(depth_bias_enable);
}
inline void set_color_write_mask(uint32_t write_mask)
{
SET_STATIC_STATE(write_mask);
}
inline void set_stencil_test(bool stencil_test)
{
SET_STATIC_STATE(stencil_test);
}
inline void set_stencil_front_ops(VkCompareOp stencil_front_compare_op, VkStencilOp stencil_front_pass,
VkStencilOp stencil_front_fail, VkStencilOp stencil_front_depth_fail)
{
SET_STATIC_STATE(stencil_front_compare_op);
SET_STATIC_STATE(stencil_front_pass);
SET_STATIC_STATE(stencil_front_fail);
SET_STATIC_STATE(stencil_front_depth_fail);
}
inline void set_stencil_back_ops(VkCompareOp stencil_back_compare_op, VkStencilOp stencil_back_pass,
VkStencilOp stencil_back_fail, VkStencilOp stencil_back_depth_fail)
{
SET_STATIC_STATE(stencil_back_compare_op);
SET_STATIC_STATE(stencil_back_pass);
SET_STATIC_STATE(stencil_back_fail);
SET_STATIC_STATE(stencil_back_depth_fail);
}
inline void set_stencil_ops(VkCompareOp stencil_compare_op, VkStencilOp stencil_pass, VkStencilOp stencil_fail,
VkStencilOp stencil_depth_fail)
{
set_stencil_front_ops(stencil_compare_op, stencil_pass, stencil_fail, stencil_depth_fail);
set_stencil_back_ops(stencil_compare_op, stencil_pass, stencil_fail, stencil_depth_fail);
}
inline void set_primitive_topology(VkPrimitiveTopology topology)
{
SET_STATIC_STATE(topology);
}
inline void set_primitive_restart(bool primitive_restart)
{
SET_STATIC_STATE(primitive_restart);
}
inline void set_multisample_state(bool alpha_to_coverage, bool alpha_to_one = false, bool sample_shading = false)
{
SET_STATIC_STATE(alpha_to_coverage);
SET_STATIC_STATE(alpha_to_one);
SET_STATIC_STATE(sample_shading);
}
inline void set_front_face(VkFrontFace front_face)
{
SET_STATIC_STATE(front_face);
}
inline void set_cull_mode(VkCullModeFlags cull_mode)
{
SET_STATIC_STATE(cull_mode);
}
inline void set_blend_constants(const float blend_constants[4])
{
SET_POTENTIALLY_STATIC_STATE(blend_constants[0]);
SET_POTENTIALLY_STATIC_STATE(blend_constants[1]);
SET_POTENTIALLY_STATIC_STATE(blend_constants[2]);
SET_POTENTIALLY_STATIC_STATE(blend_constants[3]);
}
inline void set_specialization_constant_mask(uint32_t spec_constant_mask)
{
VK_ASSERT((spec_constant_mask & ~((1u << VULKAN_NUM_USER_SPEC_CONSTANTS) - 1u)) == 0u);
SET_POTENTIALLY_STATIC_STATE(spec_constant_mask);
}
template <typename T>
inline void set_specialization_constant(unsigned index, const T &value)
{
VK_ASSERT(index < VULKAN_NUM_USER_SPEC_CONSTANTS);
static_assert(sizeof(value) == sizeof(uint32_t), "Spec constant data must be 32-bit.");
if (memcmp(&pipeline_state.potential_static_state.spec_constants[index], &value, sizeof(value)))
{
memcpy(&pipeline_state.potential_static_state.spec_constants[index], &value, sizeof(value));
if (pipeline_state.potential_static_state.spec_constant_mask & (1u << index))
set_dirty(COMMAND_BUFFER_DIRTY_STATIC_STATE_BIT);
}
}
inline void set_specialization_constant(unsigned index, bool value)
{
set_specialization_constant(index, uint32_t(value));
}
inline void enable_subgroup_size_control(bool subgroup_control_size,
VkShaderStageFlagBits stage = VK_SHADER_STAGE_COMPUTE_BIT)
{
VK_ASSERT(stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
stage == VK_SHADER_STAGE_MESH_BIT_EXT ||
stage == VK_SHADER_STAGE_COMPUTE_BIT);
if (stage != VK_SHADER_STAGE_TASK_BIT_EXT)
{
SET_STATIC_STATE(subgroup_control_size);
}
else
{
auto subgroup_control_size_task = subgroup_control_size;
SET_STATIC_STATE(subgroup_control_size_task);
}
}
inline void set_subgroup_size_log2(bool subgroup_full_group,
uint8_t subgroup_minimum_size_log2,
uint8_t subgroup_maximum_size_log2,
VkShaderStageFlagBits stage = VK_SHADER_STAGE_COMPUTE_BIT)
{
VK_ASSERT(stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
stage == VK_SHADER_STAGE_MESH_BIT_EXT ||
stage == VK_SHADER_STAGE_COMPUTE_BIT);
VK_ASSERT(subgroup_minimum_size_log2 < 8);
VK_ASSERT(subgroup_maximum_size_log2 < 8);
if (stage != VK_SHADER_STAGE_TASK_BIT_EXT)
{
SET_STATIC_STATE(subgroup_full_group);
SET_STATIC_STATE(subgroup_minimum_size_log2);
SET_STATIC_STATE(subgroup_maximum_size_log2);
}
else
{
auto subgroup_full_group_task = subgroup_full_group;
auto subgroup_minimum_size_log2_task = subgroup_minimum_size_log2;
auto subgroup_maximum_size_log2_task = subgroup_maximum_size_log2;
SET_STATIC_STATE(subgroup_full_group_task);
SET_STATIC_STATE(subgroup_minimum_size_log2_task);
SET_STATIC_STATE(subgroup_maximum_size_log2_task);
}
}
inline void set_conservative_rasterization(bool conservative_raster)
{
SET_STATIC_STATE(conservative_raster);
}
#define SET_DYNAMIC_STATE(state, flags) \
do \
{ \
if (dynamic_state.state != state) \
{ \
dynamic_state.state = state; \
set_dirty(flags); \
} \
} while (0)
inline void set_depth_bias(float depth_bias_constant, float depth_bias_slope)
{
SET_DYNAMIC_STATE(depth_bias_constant, COMMAND_BUFFER_DIRTY_DEPTH_BIAS_BIT);
SET_DYNAMIC_STATE(depth_bias_slope, COMMAND_BUFFER_DIRTY_DEPTH_BIAS_BIT);
}
inline void set_stencil_front_reference(uint8_t front_compare_mask, uint8_t front_write_mask,
uint8_t front_reference)
{
SET_DYNAMIC_STATE(front_compare_mask, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
SET_DYNAMIC_STATE(front_write_mask, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
SET_DYNAMIC_STATE(front_reference, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
}
inline void set_stencil_back_reference(uint8_t back_compare_mask, uint8_t back_write_mask, uint8_t back_reference)
{
SET_DYNAMIC_STATE(back_compare_mask, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
SET_DYNAMIC_STATE(back_write_mask, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
SET_DYNAMIC_STATE(back_reference, COMMAND_BUFFER_DIRTY_STENCIL_REFERENCE_BIT);
}
inline void set_stencil_reference(uint8_t compare_mask, uint8_t write_mask, uint8_t reference)
{
set_stencil_front_reference(compare_mask, write_mask, reference);
set_stencil_back_reference(compare_mask, write_mask, reference);
}
inline Type get_command_buffer_type() const
{
return type;
}
QueryPoolHandle write_timestamp(VkPipelineStageFlags2 stage);
// Used when recording command buffers in a thread, and submitting them in a different thread.
// Need to make sure that no further commands on the VkCommandBuffer happen.
void end_threaded_recording();
// End is called automatically by Device in submission. Should not be called by application.
void end();
void enable_profiling();
bool has_profiling() const;
void begin_debug_channel(DebugChannelInterface *iface, const char *tag, VkDeviceSize size);
void end_debug_channel();
void extract_pipeline_state(DeferredPipelineCompile &compile) const;
enum class CompileMode
{
Sync,
FailOnCompileRequired,
AsyncThread,
IndirectBindable
};
static Pipeline build_graphics_pipeline(Device *device, const DeferredPipelineCompile &compile, CompileMode mode);
static Pipeline build_compute_pipeline(Device *device, const DeferredPipelineCompile &compile, CompileMode mode);
bool flush_pipeline_state_without_blocking();
VkPipeline get_current_compute_pipeline();
VkPipeline get_current_graphics_pipeline();
private:
friend class Util::ObjectPool<CommandBuffer>;
CommandBuffer(Device *device, VkCommandBuffer cmd, VkPipelineCache cache, Type type);
Device *device;
const VolkDeviceTable &table;
VkCommandBuffer cmd;
Type type;
const Framebuffer *framebuffer = nullptr;
const RenderPass *actual_render_pass = nullptr;
const Vulkan::ImageView *framebuffer_attachments[VULKAN_NUM_ATTACHMENTS + 1] = {};
IndexState index_state = {};
VertexBindingState vbo = {};
ResourceBindings bindings;
VkDescriptorSet bindless_sets[VULKAN_NUM_DESCRIPTOR_SETS] = {};
VkDescriptorSet allocated_sets[VULKAN_NUM_DESCRIPTOR_SETS] = {};
Pipeline current_pipeline = {};
VkPipelineLayout current_pipeline_layout = VK_NULL_HANDLE;
VkSubpassContents current_contents = VK_SUBPASS_CONTENTS_INLINE;
unsigned thread_index = 0;
VkViewport viewport = {};
VkRect2D scissor = {};
CommandBufferDirtyFlags dirty = ~0u;
uint32_t dirty_sets_realloc = 0;
uint32_t dirty_sets_rebind = 0;
uint32_t dirty_vbos = 0;
uint32_t active_vbos = 0;
VkPipelineStageFlags2 uses_swapchain_in_stages = 0;
bool is_compute = true;
bool is_secondary = false;
bool is_ended = false;
bool framebuffer_is_multiview = false;
void set_dirty(CommandBufferDirtyFlags flags)
{
dirty |= flags;
}
CommandBufferDirtyFlags get_and_clear(CommandBufferDirtyFlags flags)
{
auto mask = dirty & flags;
dirty &= ~flags;
return mask;
}
DeferredPipelineCompile pipeline_state = {};
DynamicState dynamic_state = {};
#ifndef _MSC_VER
static_assert(sizeof(pipeline_state.static_state.words) >= sizeof(pipeline_state.static_state.state),
"Hashable pipeline state is not large enough!");
#endif
VkPipeline flush_render_state(bool synchronous);
VkPipeline flush_compute_state(bool synchronous);
void clear_render_state();
bool flush_graphics_pipeline(bool synchronous);
bool flush_compute_pipeline(bool synchronous);
void flush_descriptor_sets();
void begin_graphics();
void flush_descriptor_set(
uint32_t set, VkDescriptorSet *sets,
uint32_t &first_set, uint32_t &set_count,
uint32_t *dynamic_offsets, uint32_t &num_dynamic_offsets);
void push_descriptor_set(uint32_t set);
void rebind_descriptor_set(
uint32_t set, VkDescriptorSet *sets,
uint32_t &first_set, uint32_t &set_count,
uint32_t *dynamic_offsets, uint32_t &num_dynamic_offsets);
void flush_descriptor_binds(const VkDescriptorSet *sets,
uint32_t &first_set, uint32_t &set_count,
uint32_t *dynamic_offsets, uint32_t &num_dynamic_offsets);
void validate_descriptor_binds(uint32_t set);
void begin_compute();
void begin_context();
BufferBlock vbo_block;
BufferBlock ibo_block;
BufferBlock ubo_block;
BufferBlock staging_block;
void set_texture(unsigned set, unsigned binding, VkImageView float_view, VkImageView integer_view,
VkImageLayout layout,
uint64_t cookie);
void set_buffer_view_common(unsigned set, unsigned binding, const BufferView &view);
void init_viewport_scissor(const RenderPassInfo &info, const Framebuffer *framebuffer);
void init_surface_transform(const RenderPassInfo &info);
VkSurfaceTransformFlagBitsKHR current_framebuffer_surface_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
bool profiling = false;
std::string debug_channel_tag;
Vulkan::BufferHandle debug_channel_buffer;
DebugChannelInterface *debug_channel_interface = nullptr;
void bind_pipeline(VkPipelineBindPoint bind_point, VkPipeline pipeline, uint32_t active_dynamic_state);
static void update_hash_graphics_pipeline(DeferredPipelineCompile &compile, CompileMode mode, uint32_t *active_vbos);
static void update_hash_compute_pipeline(DeferredPipelineCompile &compile);
void set_surface_transform_specialization_constants();
void set_program_layout(const PipelineLayout *layout);
static bool setup_subgroup_size_control(Device &device, VkPipelineShaderStageCreateInfo &stage_info,
VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT &required_info,
VkShaderStageFlagBits stage,
bool full_group, unsigned min_size_log2, unsigned max_size_log2);
};
#ifdef GRANITE_VULKAN_SYSTEM_HANDLES
struct CommandBufferUtil
{
static void draw_fullscreen_quad(CommandBuffer &cmd, const std::string &vertex, const std::string &fragment,
const std::vector<std::pair<std::string, int>> &defines = {});
static void draw_fullscreen_quad_depth(CommandBuffer &cmd, const std::string &vertex, const std::string &fragment,
bool depth_test, bool depth_write, VkCompareOp depth_compare,
const std::vector<std::pair<std::string, int>> &defines = {});
static void set_fullscreen_quad_vertex_state(CommandBuffer &cmd);
static void set_quad_vertex_state(CommandBuffer &cmd);
static void setup_fullscreen_quad(CommandBuffer &cmd, const std::string &vertex, const std::string &fragment,
const std::vector<std::pair<std::string, int>> &defines = {},
bool depth_test = false, bool depth_write = false,
VkCompareOp depth_compare = VK_COMPARE_OP_ALWAYS);
static void draw_fullscreen_quad(CommandBuffer &cmd, unsigned instances = 1);
static void draw_quad(CommandBuffer &cmd, unsigned instances = 1);
};
#endif
using CommandBufferHandle = Util::IntrusivePtr<CommandBuffer>;
}

View File

@@ -0,0 +1,180 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "command_pool.hpp"
#include "device.hpp"
namespace Vulkan
{
CommandPool::CommandPool(Device *device_, uint32_t queue_family_index)
: device(device_), table(&device_->get_device_table())
{
VkCommandPoolCreateInfo info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
info.queueFamilyIndex = queue_family_index;
if (queue_family_index != VK_QUEUE_FAMILY_IGNORED)
table->vkCreateCommandPool(device->get_device(), &info, nullptr, &pool);
}
CommandPool::CommandPool(CommandPool &&other) noexcept
{
*this = std::move(other);
}
CommandPool &CommandPool::operator=(CommandPool &&other) noexcept
{
if (this != &other)
{
device = other.device;
table = other.table;
if (!buffers.empty())
table->vkFreeCommandBuffers(device->get_device(), pool, buffers.size(), buffers.data());
if (pool != VK_NULL_HANDLE)
table->vkDestroyCommandPool(device->get_device(), pool, nullptr);
pool = VK_NULL_HANDLE;
buffers.clear();
std::swap(pool, other.pool);
std::swap(buffers, other.buffers);
index = other.index;
other.index = 0;
#ifdef VULKAN_DEBUG
in_flight.clear();
std::swap(in_flight, other.in_flight);
#endif
}
return *this;
}
CommandPool::~CommandPool()
{
if (!buffers.empty())
table->vkFreeCommandBuffers(device->get_device(), pool, buffers.size(), buffers.data());
if (!secondary_buffers.empty())
table->vkFreeCommandBuffers(device->get_device(), pool, secondary_buffers.size(), secondary_buffers.data());
if (pool != VK_NULL_HANDLE)
table->vkDestroyCommandPool(device->get_device(), pool, nullptr);
}
void CommandPool::signal_submitted(VkCommandBuffer cmd)
{
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.find(cmd) != end(in_flight));
in_flight.erase(cmd);
#else
(void)cmd;
#endif
}
VkCommandBuffer CommandPool::request_secondary_command_buffer()
{
VK_ASSERT(pool != VK_NULL_HANDLE);
if (secondary_index < secondary_buffers.size())
{
auto ret = secondary_buffers[secondary_index++];
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.find(ret) == end(in_flight));
in_flight.insert(ret);
#endif
return ret;
}
else
{
VkCommandBuffer cmd;
VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
info.commandPool = pool;
info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
info.commandBufferCount = 1;
table->vkAllocateCommandBuffers(device->get_device(), &info, &cmd);
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.find(cmd) == end(in_flight));
in_flight.insert(cmd);
#endif
secondary_buffers.push_back(cmd);
secondary_index++;
return cmd;
}
}
VkCommandBuffer CommandPool::request_command_buffer()
{
VK_ASSERT(pool != VK_NULL_HANDLE);
if (index < buffers.size())
{
auto ret = buffers[index++];
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.find(ret) == end(in_flight));
in_flight.insert(ret);
#endif
return ret;
}
else
{
VkCommandBuffer cmd;
VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
info.commandPool = pool;
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
info.commandBufferCount = 1;
table->vkAllocateCommandBuffers(device->get_device(), &info, &cmd);
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.find(cmd) == end(in_flight));
in_flight.insert(cmd);
#endif
buffers.push_back(cmd);
index++;
return cmd;
}
}
void CommandPool::begin()
{
if (pool == VK_NULL_HANDLE)
return;
#ifdef VULKAN_DEBUG
VK_ASSERT(in_flight.empty());
#endif
if (index > 0 || secondary_index > 0)
table->vkResetCommandPool(device->get_device(), pool, 0);
index = 0;
secondary_index = 0;
}
void CommandPool::trim()
{
if (pool == VK_NULL_HANDLE)
return;
table->vkResetCommandPool(device->get_device(), pool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT);
if (!buffers.empty())
table->vkFreeCommandBuffers(device->get_device(), pool, buffers.size(), buffers.data());
if (!secondary_buffers.empty())
table->vkFreeCommandBuffers(device->get_device(), pool, secondary_buffers.size(), secondary_buffers.data());
buffers.clear();
secondary_buffers.clear();
table->vkTrimCommandPool(device->get_device(), pool, 0);
}
}

View File

@@ -0,0 +1,61 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include <unordered_set>
#include <vector>
namespace Vulkan
{
class Device;
class CommandPool
{
public:
CommandPool(Device *device, uint32_t queue_family_index);
~CommandPool();
CommandPool(CommandPool &&) noexcept;
CommandPool &operator=(CommandPool &&) noexcept;
CommandPool(const CommandPool &) = delete;
void operator=(const CommandPool &) = delete;
void begin();
void trim();
VkCommandBuffer request_command_buffer();
VkCommandBuffer request_secondary_command_buffer();
void signal_submitted(VkCommandBuffer cmd);
private:
Device *device;
const VolkDeviceTable *table;
VkCommandPool pool = VK_NULL_HANDLE;
std::vector<VkCommandBuffer> buffers;
std::vector<VkCommandBuffer> secondary_buffers;
#ifdef VULKAN_DEBUG
std::unordered_set<VkCommandBuffer> in_flight;
#endif
unsigned index = 0;
unsigned secondary_index = 0;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "logging.hpp"
#include "vulkan_common.hpp"
#include <memory>
#include <functional>
#ifdef GRANITE_VULKAN_FOSSILIZE
#include "cli/fossilize_feature_filter.hpp"
#endif
namespace Util
{
class TimelineTraceFile;
}
namespace Granite
{
class Filesystem;
class ThreadGroup;
class AssetManager;
}
namespace Vulkan
{
struct DeviceFeatures
{
bool supports_debug_utils = false;
bool supports_external_memory_host = false;
bool supports_surface_capabilities2 = false;
bool supports_full_screen_exclusive = false;
bool supports_conservative_rasterization = false;
bool supports_calibrated_timestamps = false;
bool supports_memory_budget = false;
bool supports_video_queue = false;
bool supports_driver_properties = false;
bool supports_video_decode_queue = false;
bool supports_video_decode_h264 = false;
bool supports_video_decode_h265 = false;
bool supports_astc_decode_mode = false;
bool supports_image_format_list = false;
bool supports_format_feature_flags2 = false;
bool supports_video_encode_queue = false;
bool supports_video_encode_h264 = false;
bool supports_video_encode_h265 = false;
bool supports_external = false;
bool supports_tooling_info = false;
bool supports_hdr_metadata = false;
bool supports_swapchain_colorspace = false;
bool supports_surface_maintenance1 = false;
bool supports_store_op_none = false;
bool supports_push_descriptor = false;
VkPhysicalDeviceFeatures enabled_features = {};
VkPhysicalDeviceVulkan11Features vk11_features = {};
VkPhysicalDeviceVulkan12Features vk12_features = {};
VkPhysicalDeviceVulkan13Features vk13_features = {};
VkPhysicalDeviceVulkan11Properties vk11_props = {};
VkPhysicalDeviceVulkan12Properties vk12_props = {};
VkPhysicalDeviceVulkan13Properties vk13_props = {};
// KHR
VkPhysicalDevicePerformanceQueryFeaturesKHR performance_query_features = {};
VkPhysicalDevicePresentIdFeaturesKHR present_id_features = {};
VkPhysicalDevicePresentWaitFeaturesKHR present_wait_features = {};
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric_features = {};
VkPhysicalDeviceVideoMaintenance1FeaturesKHR video_maintenance1_features = {};
// EXT
VkPhysicalDeviceExternalMemoryHostPropertiesEXT host_memory_properties = {};
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_rasterization_properties = {};
VkPhysicalDeviceMemoryPriorityFeaturesEXT memory_priority_features = {};
VkPhysicalDeviceASTCDecodeFeaturesEXT astc_decode_features = {};
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_features = {};
VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT pageable_device_local_memory_features = {};
VkPhysicalDeviceMeshShaderFeaturesEXT mesh_shader_features = {};
VkPhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
VkPhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8_features = {};
VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT rgba10x6_formats_features = {};
VkPhysicalDeviceImageCompressionControlFeaturesEXT image_compression_control_features = {};
VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT image_compression_control_swapchain_features = {};
// Vendor
VkPhysicalDeviceComputeShaderDerivativesFeaturesNV compute_shader_derivative_features = {};
VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV device_generated_commands_features = {};
VkPhysicalDeviceDeviceGeneratedCommandsComputeFeaturesNV device_generated_commands_compute_features = {};
VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV device_generated_commands_properties = {};
VkPhysicalDeviceDescriptorPoolOverallocationFeaturesNV descriptor_pool_overallocation_features = {};
// Fallback feature structs (Vulkan 1.1)
VkPhysicalDeviceHostQueryResetFeatures host_query_reset_features = {};
VkPhysicalDevice16BitStorageFeaturesKHR storage_16bit_features = {};
// Fallback feature structs (Vulkan 1.2)
VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8_features = {};
VkPhysicalDevice8BitStorageFeaturesKHR storage_8bit_features = {};
// Fallback feature structs (Vulkan 1.3)
VkPhysicalDeviceSubgroupSizeControlFeatures subgroup_size_control_features = {};
VkDriverId driver_id = {};
// References Vulkan::Context.
const VkPhysicalDeviceFeatures2 *pdf2 = nullptr;
const char * const * instance_extensions = nullptr;
uint32_t num_instance_extensions = 0;
const char * const * device_extensions = nullptr;
uint32_t num_device_extensions = 0;
uint32_t instance_api_core_version = VK_API_VERSION_1_1;
uint32_t device_api_core_version = VK_API_VERSION_1_1;
};
enum VendorID
{
VENDOR_ID_AMD = 0x1002,
VENDOR_ID_NVIDIA = 0x10de,
VENDOR_ID_INTEL = 0x8086,
VENDOR_ID_ARM = 0x13b5,
VENDOR_ID_QCOM = 0x5143
};
enum ContextCreationFlagBits
{
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT = 1 << 0,
CONTEXT_CREATION_ENABLE_VIDEO_DECODE_BIT = 1 << 1,
CONTEXT_CREATION_ENABLE_VIDEO_ENCODE_BIT = 1 << 2,
CONTEXT_CREATION_ENABLE_VIDEO_H264_BIT = 1 << 3,
CONTEXT_CREATION_ENABLE_VIDEO_H265_BIT = 1 << 4
};
using ContextCreationFlags = uint32_t;
struct QueueInfo
{
QueueInfo();
VkQueue queues[QUEUE_INDEX_COUNT] = {};
uint32_t family_indices[QUEUE_INDEX_COUNT];
uint32_t counts[QUEUE_INDEX_COUNT] = {};
uint32_t timestamp_valid_bits = 0;
};
struct InstanceFactory
{
virtual ~InstanceFactory() = default;
virtual VkInstance create_instance(const VkInstanceCreateInfo *info) = 0;
};
struct DeviceFactory
{
virtual ~DeviceFactory() = default;
virtual VkDevice create_device(VkPhysicalDevice gpu, const VkDeviceCreateInfo *info) = 0;
};
class CopiedApplicationInfo
{
public:
CopiedApplicationInfo();
const VkApplicationInfo &get_application_info() const;
void copy_assign(const VkApplicationInfo *info);
private:
std::string application;
std::string engine;
VkApplicationInfo app = {
VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "Granite", 0, "Granite", 0, VK_API_VERSION_1_1,
};
void set_default_app();
};
class Context
: public Util::IntrusivePtrEnabled<Context, std::default_delete<Context>, HandleCounter>
#ifdef GRANITE_VULKAN_FOSSILIZE
, public Fossilize::DeviceQueryInterface
#endif
{
public:
// If these interface are set, factory->create() calls are used instead of global vkCreateInstance and vkCreateDevice.
// For deeper API interop scenarios.
void set_instance_factory(InstanceFactory *factory);
void set_device_factory(DeviceFactory *factory);
// Only takes effect if profiles are enabled in build. (GRANITE_VULKAN_PROFILES)
// If profile is non-null, forces a specific profile.
// If not supported, initialization fails.
// If not set, ignore profiles.
// If strict is false, the profile should be seen as a baseline and Granite will augment features on top.
// If true, the profile is a strict limit for device functionality. For validation purposes.
void set_required_profile(const char *profile, bool strict);
// Call before initializing instances. app_info may be freed after returning.
// API_VERSION must be at least 1.1.
// By default, a Vulkan 1.1 instance is created.
void set_application_info(const VkApplicationInfo *app_info);
// Recommended interface.
// InstanceFactory can be used to override enabled instance layers and extensions.
// For simple WSI use, it is enough to just enable VK_KHR_surface and the platform.
bool init_instance(const char * const *instance_ext, uint32_t instance_ext_count,
ContextCreationFlags flags = 0);
// DeviceFactory can be used to override enabled device extensions.
// For simple WSI use, it is enough to just enable VK_KHR_swapchain.
bool init_device(VkPhysicalDevice gpu, VkSurfaceKHR surface_compat,
const char * const *device_ext, uint32_t device_ext_count,
ContextCreationFlags flags = 0);
// Simplified initialization which calls init_instance and init_device in succession with NULL GPU and surface.
// Provided for compat with older code.
bool init_instance_and_device(const char * const *instance_ext, uint32_t instance_ext_count,
const char * const *device_ext, uint32_t device_ext_count,
ContextCreationFlags flags = 0);
// Deprecated. For libretro Vulkan context negotiation v1.
// Use InstanceFactory and DeviceFactory for more advanced scenarios in v2.
bool init_device_from_instance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
const char **required_device_extensions,
unsigned num_required_device_extensions,
const VkPhysicalDeviceFeatures *required_features,
ContextCreationFlags flags = 0);
Context();
Context(const Context &) = delete;
void operator=(const Context &) = delete;
static bool init_loader(PFN_vkGetInstanceProcAddr addr, bool force_reload = false);
static PFN_vkGetInstanceProcAddr get_instance_proc_addr();
~Context();
VkInstance get_instance() const
{
return instance;
}
VkPhysicalDevice get_gpu() const
{
return gpu;
}
VkDevice get_device() const
{
return device;
}
const QueueInfo &get_queue_info() const
{
return queue_info;
}
const VkPhysicalDeviceProperties &get_gpu_props() const
{
return gpu_props;
}
const VkPhysicalDeviceMemoryProperties &get_mem_props() const
{
return mem_props;
}
void release_instance()
{
owned_instance = false;
}
void release_device()
{
owned_device = false;
}
const DeviceFeatures &get_enabled_device_features() const
{
return ext;
}
const VkApplicationInfo &get_application_info() const;
void notify_validation_error(const char *msg);
void set_notification_callback(std::function<void (const char *)> func);
void set_num_thread_indices(unsigned indices)
{
num_thread_indices = indices;
}
unsigned get_num_thread_indices() const
{
return num_thread_indices;
}
const VolkDeviceTable &get_device_table() const
{
return device_table;
}
struct SystemHandles
{
Util::TimelineTraceFile *timeline_trace_file = nullptr;
Granite::Filesystem *filesystem = nullptr;
Granite::ThreadGroup *thread_group = nullptr;
Granite::AssetManager *asset_manager = nullptr;
};
void set_system_handles(const SystemHandles &handles_)
{
handles = handles_;
}
const SystemHandles &get_system_handles() const
{
return handles;
}
#ifdef GRANITE_VULKAN_FOSSILIZE
const Fossilize::FeatureFilter &get_feature_filter() const
{
return feature_filter;
}
#endif
const VkPhysicalDeviceFeatures2 &get_physical_device_features() const
{
return pdf2;
}
private:
InstanceFactory *instance_factory = nullptr;
DeviceFactory *device_factory = nullptr;
VkDevice device = VK_NULL_HANDLE;
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice gpu = VK_NULL_HANDLE;
VolkDeviceTable device_table = {};
SystemHandles handles;
VkPhysicalDeviceProperties gpu_props = {};
VkPhysicalDeviceMemoryProperties mem_props = {};
CopiedApplicationInfo user_application_info;
QueueInfo queue_info;
unsigned num_thread_indices = 1;
bool create_instance(const char * const *instance_ext, uint32_t instance_ext_count, ContextCreationFlags flags);
bool create_device(VkPhysicalDevice gpu, VkSurfaceKHR surface,
const char * const *required_device_extensions, uint32_t num_required_device_extensions,
const VkPhysicalDeviceFeatures *required_features, ContextCreationFlags flags);
bool owned_instance = false;
bool owned_device = false;
DeviceFeatures ext;
VkPhysicalDeviceFeatures2 pdf2;
std::vector<const char *> enabled_device_extensions;
std::vector<const char *> enabled_instance_extensions;
std::string required_profile;
bool required_profile_strict = false;
#ifdef VULKAN_DEBUG
VkDebugUtilsMessengerEXT debug_messenger = VK_NULL_HANDLE;
bool force_no_validation = false;
#endif
std::function<void (const char *)> message_callback;
void destroy_instance();
void destroy_device();
bool physical_device_supports_surface_and_profile(VkPhysicalDevice candidate_gpu, VkSurfaceKHR surface) const;
#ifdef GRANITE_VULKAN_FOSSILIZE
Fossilize::FeatureFilter feature_filter;
bool format_is_supported(VkFormat format, VkFormatFeatureFlags features) override;
bool descriptor_set_layout_is_supported(const VkDescriptorSetLayoutCreateInfo *set_layout) override;
#endif
bool init_profile();
VkResult create_instance_from_profile(const VkInstanceCreateInfo &info, VkInstance *pInstance);
VkResult create_device_from_profile(const VkDeviceCreateInfo &info, VkDevice *pDevice);
VkApplicationInfo get_promoted_application_info() const;
};
using ContextHandle = Util::IntrusivePtr<Context>;
}

View File

@@ -0,0 +1,32 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "cookie.hpp"
#include "device.hpp"
namespace Vulkan
{
Cookie::Cookie(Device *device)
: cookie(device->allocate_cookie())
{
}
}

View File

@@ -0,0 +1,61 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include "hash.hpp"
#include "intrusive_hash_map.hpp"
namespace Vulkan
{
class Device;
class Cookie
{
public:
Cookie(Device *device);
uint64_t get_cookie() const
{
return cookie;
}
private:
uint64_t cookie;
};
template <typename T>
using HashedObject = Util::IntrusiveHashMapEnabled<T>;
class InternalSyncEnabled
{
public:
void set_internal_sync_object()
{
internal_sync = true;
}
protected:
bool internal_sync = false;
};
}

View File

@@ -0,0 +1,521 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "descriptor_set.hpp"
#include "device.hpp"
#include <vector>
using namespace Util;
namespace Vulkan
{
DescriptorSetAllocator::DescriptorSetAllocator(Hash hash, Device *device_, const DescriptorSetLayout &layout,
const uint32_t *stages_for_binds,
const ImmutableSampler * const *immutable_samplers)
: IntrusiveHashMapEnabled<DescriptorSetAllocator>(hash)
, device(device_)
, table(device_->get_device_table())
{
bindless = layout.array_size[0] == DescriptorSetLayout::UNSIZED_ARRAY;
if (!bindless)
{
unsigned count = device_->num_thread_indices * device_->per_frame.size();
per_thread_and_frame.resize(count);
}
if (bindless && !device->get_device_features().vk12_features.descriptorIndexing)
{
LOGE("Cannot support descriptor indexing on this device.\n");
return;
}
VkDescriptorSetLayoutCreateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
VkDescriptorSetLayoutBindingFlagsCreateInfoEXT flags = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT };
VkSampler vk_immutable_samplers[VULKAN_NUM_BINDINGS] = {};
std::vector<VkDescriptorSetLayoutBinding> bindings;
VkDescriptorBindingFlagsEXT binding_flags = 0;
if (bindless)
{
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT;
info.pNext = &flags;
flags.bindingCount = 1;
flags.pBindingFlags = &binding_flags;
binding_flags = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT |
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT |
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT;
}
for (unsigned i = 0; i < VULKAN_NUM_BINDINGS; i++)
{
auto stages = stages_for_binds[i];
if (stages == 0)
continue;
unsigned array_size = layout.array_size[i];
unsigned pool_array_size;
if (array_size == DescriptorSetLayout::UNSIZED_ARRAY)
{
array_size = VULKAN_NUM_BINDINGS_BINDLESS_VARYING;
pool_array_size = array_size;
}
else
pool_array_size = array_size * VULKAN_NUM_SETS_PER_POOL;
unsigned types = 0;
if (layout.sampled_image_mask & (1u << i))
{
if ((layout.immutable_sampler_mask & (1u << i)) && immutable_samplers && immutable_samplers[i])
vk_immutable_samplers[i] = immutable_samplers[i]->get_sampler().get_sampler();
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, array_size, stages,
vk_immutable_samplers[i] != VK_NULL_HANDLE ? &vk_immutable_samplers[i] : nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pool_array_size });
types++;
}
if (layout.sampled_texel_buffer_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, pool_array_size });
types++;
}
if (layout.storage_texel_buffer_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, pool_array_size });
types++;
}
if (layout.storage_image_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pool_array_size });
types++;
}
if (layout.uniform_buffer_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, pool_array_size });
types++;
}
if (layout.storage_buffer_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, pool_array_size });
types++;
}
if (layout.input_attachment_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, pool_array_size });
types++;
}
if (layout.separate_image_mask & (1u << i))
{
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, array_size, stages, nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, pool_array_size });
types++;
}
if (layout.sampler_mask & (1u << i))
{
if ((layout.immutable_sampler_mask & (1u << i)) && immutable_samplers && immutable_samplers[i])
vk_immutable_samplers[i] = immutable_samplers[i]->get_sampler().get_sampler();
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_SAMPLER, array_size, stages,
vk_immutable_samplers[i] != VK_NULL_HANDLE ? &vk_immutable_samplers[i] : nullptr });
pool_size.push_back({ VK_DESCRIPTOR_TYPE_SAMPLER, pool_array_size });
types++;
}
(void)types;
VK_ASSERT(types <= 1 && "Descriptor set aliasing!");
}
if (!bindings.empty())
{
info.bindingCount = bindings.size();
info.pBindings = bindings.data();
if (bindless && bindings.size() != 1)
{
LOGE("Using bindless but have bindingCount != 1.\n");
return;
}
}
#ifdef VULKAN_DEBUG
LOGI("Creating descriptor set layout.\n");
#endif
if (table.vkCreateDescriptorSetLayout(device->get_device(), &info, nullptr, &set_layout_pool) != VK_SUCCESS)
LOGE("Failed to create descriptor set layout.");
#ifdef GRANITE_VULKAN_FOSSILIZE
if (set_layout_pool)
device->register_descriptor_set_layout(set_layout_pool, get_hash(), info);
#endif
if (!bindless && device->get_device_features().supports_push_descriptor && !device->workarounds.broken_push_descriptors)
{
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
for (auto &b : bindings)
if (b.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC)
b.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
if (table.vkCreateDescriptorSetLayout(device->get_device(), &info, nullptr, &set_layout_push) != VK_SUCCESS)
LOGE("Failed to create descriptor set layout.");
#ifdef GRANITE_VULKAN_FOSSILIZE
if (set_layout_push)
device->register_descriptor_set_layout(set_layout_push, get_hash(), info);
#endif
}
}
void DescriptorSetAllocator::reset_bindless_pool(VkDescriptorPool pool)
{
table.vkResetDescriptorPool(device->get_device(), pool, 0);
}
VkDescriptorSet DescriptorSetAllocator::allocate_bindless_set(VkDescriptorPool pool, unsigned num_descriptors)
{
if (!pool || !bindless)
return VK_NULL_HANDLE;
VkDescriptorSetAllocateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
info.descriptorPool = pool;
info.descriptorSetCount = 1;
info.pSetLayouts = &set_layout_pool;
VkDescriptorSetVariableDescriptorCountAllocateInfoEXT count_info =
{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT };
uint32_t num_desc = num_descriptors;
count_info.descriptorSetCount = 1;
count_info.pDescriptorCounts = &num_desc;
info.pNext = &count_info;
VkDescriptorSet desc_set = VK_NULL_HANDLE;
if (table.vkAllocateDescriptorSets(device->get_device(), &info, &desc_set) != VK_SUCCESS)
return VK_NULL_HANDLE;
return desc_set;
}
VkDescriptorPool DescriptorSetAllocator::allocate_bindless_pool(unsigned num_sets, unsigned num_descriptors)
{
if (!bindless)
return VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
VkDescriptorPoolCreateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
info.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT;
info.maxSets = num_sets;
info.poolSizeCount = 1;
VkDescriptorPoolSize size = pool_size[0];
size.descriptorCount = num_descriptors;
info.pPoolSizes = &size;
if (table.vkCreateDescriptorPool(device->get_device(), &info, nullptr, &pool) != VK_SUCCESS)
{
LOGE("Failed to create descriptor pool.\n");
return VK_NULL_HANDLE;
}
return pool;
}
void DescriptorSetAllocator::begin_frame()
{
if (!bindless)
{
// This can only be called in a situation where no command buffers are alive,
// so we don't need to consider any locks here.
if (device->per_frame.size() * device->num_thread_indices != per_thread_and_frame.size())
per_thread_and_frame.resize(device->per_frame.size() * device->num_thread_indices);
// It would be safe to set all offsets to 0 here, but that's a little wasteful.
for (uint32_t i = 0; i < device->num_thread_indices; i++)
per_thread_and_frame[i * device->per_frame.size() + device->frame_context_index].offset = 0;
}
}
VkDescriptorSet DescriptorSetAllocator::request_descriptor_set(unsigned thread_index, unsigned frame_index)
{
VK_ASSERT(!bindless);
size_t flattened_index = thread_index * device->per_frame.size() + frame_index;
auto &state = per_thread_and_frame[flattened_index];
unsigned pool_index = state.offset / VULKAN_NUM_SETS_PER_POOL;
unsigned pool_offset = state.offset % VULKAN_NUM_SETS_PER_POOL;
if (pool_index >= state.pools.size())
{
Pool *pool = state.object_pool.allocate();
VkDescriptorPoolCreateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
info.maxSets = VULKAN_NUM_SETS_PER_POOL;
if (!pool_size.empty())
{
info.poolSizeCount = pool_size.size();
info.pPoolSizes = pool_size.data();
}
bool overallocation =
device->get_device_features().descriptor_pool_overallocation_features.descriptorPoolOverallocation ==
VK_TRUE;
if (overallocation)
{
// No point in allocating new pools if we can keep using the existing one.
info.flags |= VK_DESCRIPTOR_POOL_CREATE_ALLOW_OVERALLOCATION_POOLS_BIT_NV |
VK_DESCRIPTOR_POOL_CREATE_ALLOW_OVERALLOCATION_SETS_BIT_NV;
}
bool need_alloc = !overallocation || state.pools.empty();
pool->pool = VK_NULL_HANDLE;
if (need_alloc && table.vkCreateDescriptorPool(device->get_device(), &info, nullptr, &pool->pool) != VK_SUCCESS)
{
LOGE("Failed to create descriptor pool.\n");
state.object_pool.free(pool);
return VK_NULL_HANDLE;
}
VkDescriptorSetLayout layouts[VULKAN_NUM_SETS_PER_POOL];
std::fill(std::begin(layouts), std::end(layouts), set_layout_pool);
VkDescriptorSetAllocateInfo alloc = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
alloc.descriptorPool = pool->pool != VK_NULL_HANDLE ? pool->pool : state.pools.front()->pool;
alloc.descriptorSetCount = VULKAN_NUM_SETS_PER_POOL;
alloc.pSetLayouts = layouts;
if (table.vkAllocateDescriptorSets(device->get_device(), &alloc, pool->sets) != VK_SUCCESS)
LOGE("Failed to allocate descriptor sets.\n");
state.pools.push_back(pool);
}
VkDescriptorSet vk_set = state.pools[pool_index]->sets[pool_offset];
state.offset++;
return vk_set;
}
void DescriptorSetAllocator::clear()
{
for (auto &state : per_thread_and_frame)
{
for (auto *obj : state.pools)
{
table.vkDestroyDescriptorPool(device->get_device(), obj->pool, nullptr);
state.object_pool.free(obj);
}
state.pools.clear();
state.offset = 0;
state.object_pool = {};
}
}
DescriptorSetAllocator::~DescriptorSetAllocator()
{
table.vkDestroyDescriptorSetLayout(device->get_device(), set_layout_pool, nullptr);
table.vkDestroyDescriptorSetLayout(device->get_device(), set_layout_push, nullptr);
clear();
}
BindlessDescriptorPool::BindlessDescriptorPool(Device *device_, DescriptorSetAllocator *allocator_,
VkDescriptorPool pool, uint32_t num_sets, uint32_t num_desc)
: device(device_), allocator(allocator_), desc_pool(pool), total_sets(num_sets), total_descriptors(num_desc)
{
}
BindlessDescriptorPool::~BindlessDescriptorPool()
{
if (desc_pool)
{
if (internal_sync)
device->destroy_descriptor_pool_nolock(desc_pool);
else
device->destroy_descriptor_pool(desc_pool);
}
}
VkDescriptorSet BindlessDescriptorPool::get_descriptor_set() const
{
return desc_set;
}
void BindlessDescriptorPool::reset()
{
if (desc_pool != VK_NULL_HANDLE)
allocator->reset_bindless_pool(desc_pool);
desc_set = VK_NULL_HANDLE;
allocated_descriptor_count = 0;
allocated_sets = 0;
}
bool BindlessDescriptorPool::allocate_descriptors(unsigned count)
{
// Not all drivers will exhaust the pool for us, so make sure we don't allocate more than expected.
if (allocated_sets == total_sets)
return false;
if (allocated_descriptor_count + count > total_descriptors)
return false;
allocated_descriptor_count += count;
allocated_sets++;
desc_set = allocator->allocate_bindless_set(desc_pool, count);
infos.reserve(count);
write_count = 0;
return desc_set != VK_NULL_HANDLE;
}
void BindlessDescriptorPool::push_texture(const ImageView &view)
{
// TODO: Deal with integer view for depth-stencil images?
push_texture(view.get_float_view(), view.get_image().get_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
}
void BindlessDescriptorPool::push_texture_unorm(const ImageView &view)
{
push_texture(view.get_unorm_view(), view.get_image().get_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
}
void BindlessDescriptorPool::push_texture_srgb(const ImageView &view)
{
push_texture(view.get_srgb_view(), view.get_image().get_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
}
void BindlessDescriptorPool::push_texture(VkImageView view, VkImageLayout layout)
{
VK_ASSERT(write_count < infos.get_capacity());
auto &image_info = infos[write_count];
image_info = { VK_NULL_HANDLE, view, layout };
write_count++;
}
void BindlessDescriptorPool::update()
{
VkWriteDescriptorSet desc = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
desc.descriptorCount = write_count;
desc.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
desc.dstSet = desc_set;
desc.pImageInfo = infos.data();
desc.pBufferInfo = nullptr;
desc.pTexelBufferView = nullptr;
if (write_count)
{
auto &table = device->get_device_table();
table.vkUpdateDescriptorSets(device->get_device(), 1, &desc, 0, nullptr);
}
}
void BindlessDescriptorPoolDeleter::operator()(BindlessDescriptorPool *pool)
{
pool->device->handle_pool.bindless_descriptor_pool.free(pool);
}
unsigned BindlessAllocator::push(const ImageView &view)
{
auto ret = unsigned(views.size());
views.push_back(&view);
if (views.size() > VULKAN_NUM_BINDINGS_BINDLESS_VARYING)
{
LOGE("Exceeding maximum number of bindless resources per set (%u >= %u).\n",
unsigned(views.size()), VULKAN_NUM_BINDINGS_BINDLESS_VARYING);
}
return ret;
}
void BindlessAllocator::begin()
{
views.clear();
}
void BindlessAllocator::reset()
{
descriptor_pool.reset();
}
unsigned BindlessAllocator::get_next_offset() const
{
return unsigned(views.size());
}
void BindlessAllocator::reserve_max_resources_per_pool(unsigned set_count, unsigned descriptor_count)
{
max_sets_per_pool = std::max(max_sets_per_pool, set_count);
max_descriptors_per_pool = std::max(max_descriptors_per_pool, descriptor_count);
views.reserve(max_descriptors_per_pool);
}
void BindlessAllocator::set_bindless_resource_type(BindlessResourceType type)
{
resource_type = type;
}
VkDescriptorSet BindlessAllocator::commit(Device &device)
{
max_sets_per_pool = std::max(1u, max_sets_per_pool);
max_descriptors_per_pool = std::max<unsigned>(views.size(), max_descriptors_per_pool);
max_descriptors_per_pool = std::max<unsigned>(1u, max_descriptors_per_pool);
max_descriptors_per_pool = std::min(max_descriptors_per_pool, VULKAN_NUM_BINDINGS_BINDLESS_VARYING);
unsigned to_allocate = std::max<unsigned>(views.size(), 1u);
if (!descriptor_pool)
{
descriptor_pool = device.create_bindless_descriptor_pool(
resource_type, max_sets_per_pool, max_descriptors_per_pool);
}
if (!descriptor_pool->allocate_descriptors(to_allocate))
{
descriptor_pool = device.create_bindless_descriptor_pool(
resource_type, max_sets_per_pool, max_descriptors_per_pool);
if (!descriptor_pool->allocate_descriptors(to_allocate))
{
LOGE("Failed to allocate descriptors on a fresh descriptor pool!\n");
return VK_NULL_HANDLE;
}
}
for (size_t i = 0, n = views.size(); i < n; i++)
descriptor_pool->push_texture(*views[i]);
descriptor_pool->update();
return descriptor_pool->get_descriptor_set();
}
}

View File

@@ -0,0 +1,192 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "hash.hpp"
#include "object_pool.hpp"
#include "temporary_hashmap.hpp"
#include "vulkan_headers.hpp"
#include "sampler.hpp"
#include "limits.hpp"
#include "dynamic_array.hpp"
#include <utility>
#include <vector>
#include "cookie.hpp"
namespace Vulkan
{
class Device;
struct DescriptorSetLayout
{
uint32_t sampled_image_mask = 0;
uint32_t storage_image_mask = 0;
uint32_t uniform_buffer_mask = 0;
uint32_t storage_buffer_mask = 0;
uint32_t sampled_texel_buffer_mask = 0;
uint32_t storage_texel_buffer_mask = 0;
uint32_t input_attachment_mask = 0;
uint32_t sampler_mask = 0;
uint32_t separate_image_mask = 0;
uint32_t fp_mask = 0;
uint32_t immutable_sampler_mask = 0;
uint8_t array_size[VULKAN_NUM_BINDINGS] = {};
uint32_t padding = 0;
enum { UNSIZED_ARRAY = 0xff };
};
// Avoid -Wclass-memaccess warnings since we hash DescriptorSetLayout.
static const unsigned VULKAN_NUM_SETS_PER_POOL = 64;
static const unsigned VULKAN_DESCRIPTOR_RING_SIZE = 16;
class DescriptorSetAllocator;
class BindlessDescriptorPool;
class ImageView;
struct BindlessDescriptorPoolDeleter
{
void operator()(BindlessDescriptorPool *pool);
};
class BindlessDescriptorPool : public Util::IntrusivePtrEnabled<BindlessDescriptorPool, BindlessDescriptorPoolDeleter, HandleCounter>,
public InternalSyncEnabled
{
public:
friend struct BindlessDescriptorPoolDeleter;
explicit BindlessDescriptorPool(Device *device, DescriptorSetAllocator *allocator, VkDescriptorPool pool,
uint32_t total_sets, uint32_t total_descriptors);
~BindlessDescriptorPool();
void operator=(const BindlessDescriptorPool &) = delete;
BindlessDescriptorPool(const BindlessDescriptorPool &) = delete;
void reset();
bool allocate_descriptors(unsigned count);
VkDescriptorSet get_descriptor_set() const;
void push_texture(const ImageView &view);
void push_texture_unorm(const ImageView &view);
void push_texture_srgb(const ImageView &view);
void update();
private:
Device *device;
DescriptorSetAllocator *allocator;
VkDescriptorPool desc_pool;
VkDescriptorSet desc_set = VK_NULL_HANDLE;
uint32_t allocated_sets = 0;
uint32_t total_sets = 0;
uint32_t allocated_descriptor_count = 0;
uint32_t total_descriptors = 0;
void push_texture(VkImageView view, VkImageLayout layout);
Util::DynamicArray<VkDescriptorImageInfo> infos;
uint32_t write_count = 0;
};
using BindlessDescriptorPoolHandle = Util::IntrusivePtr<BindlessDescriptorPool>;
enum class BindlessResourceType
{
Image
};
class DescriptorSetAllocator : public HashedObject<DescriptorSetAllocator>
{
public:
DescriptorSetAllocator(Util::Hash hash, Device *device, const DescriptorSetLayout &layout,
const uint32_t *stages_for_bindings,
const ImmutableSampler * const *immutable_samplers);
~DescriptorSetAllocator();
void operator=(const DescriptorSetAllocator &) = delete;
DescriptorSetAllocator(const DescriptorSetAllocator &) = delete;
void begin_frame();
VkDescriptorSet request_descriptor_set(unsigned thread_index, unsigned frame_context);
VkDescriptorSetLayout get_layout_for_pool() const
{
return set_layout_pool;
}
VkDescriptorSetLayout get_layout_for_push() const
{
return set_layout_push;
}
void clear();
bool is_bindless() const
{
return bindless;
}
VkDescriptorPool allocate_bindless_pool(unsigned num_sets, unsigned num_descriptors);
VkDescriptorSet allocate_bindless_set(VkDescriptorPool pool, unsigned num_descriptors);
void reset_bindless_pool(VkDescriptorPool pool);
private:
Device *device;
const VolkDeviceTable &table;
VkDescriptorSetLayout set_layout_pool = VK_NULL_HANDLE;
VkDescriptorSetLayout set_layout_push = VK_NULL_HANDLE;
struct Pool
{
VkDescriptorPool pool;
VkDescriptorSet sets[VULKAN_NUM_SETS_PER_POOL];
};
struct PerThreadAndFrame
{
std::vector<Pool *> pools;
Util::ObjectPool<Pool> object_pool;
uint32_t offset = 0;
};
std::vector<PerThreadAndFrame> per_thread_and_frame;
std::vector<VkDescriptorPoolSize> pool_size;
bool bindless = false;
};
class BindlessAllocator
{
public:
void reserve_max_resources_per_pool(unsigned set_count, unsigned descriptor_count);
void set_bindless_resource_type(BindlessResourceType type);
void begin();
unsigned push(const ImageView &view);
VkDescriptorSet commit(Device &device);
unsigned get_next_offset() const;
void reset();
private:
BindlessDescriptorPoolHandle descriptor_pool;
unsigned max_sets_per_pool = 0;
unsigned max_descriptors_per_pool = 0;
BindlessResourceType resource_type = BindlessResourceType::Image;
std::vector<const ImageView *> views;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,911 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "buffer.hpp"
#include "command_buffer.hpp"
#include "command_pool.hpp"
#include "fence.hpp"
#include "fence_manager.hpp"
#include "image.hpp"
#include "memory_allocator.hpp"
#include "render_pass.hpp"
#include "sampler.hpp"
#include "semaphore.hpp"
#include "semaphore_manager.hpp"
#include "event_manager.hpp"
#include "shader.hpp"
#include "context.hpp"
#include "query_pool.hpp"
#include "buffer_pool.hpp"
#include "indirect_layout.hpp"
#include <memory>
#include <vector>
#include <functional>
#include <unordered_map>
#include <stdio.h>
#ifdef GRANITE_VULKAN_SYSTEM_HANDLES
#include "shader_manager.hpp"
#include "resource_manager.hpp"
#endif
#include <atomic>
#include <mutex>
#include <condition_variable>
#ifdef GRANITE_VULKAN_FOSSILIZE
#include "fossilize.hpp"
#endif
#include "quirks.hpp"
#include "small_vector.hpp"
namespace Util
{
class TimelineTraceFile;
}
namespace Granite
{
struct TaskGroup;
}
namespace Vulkan
{
enum class SwapchainRenderPass
{
ColorOnly,
Depth,
DepthStencil
};
struct InitialImageBuffer
{
BufferHandle buffer;
Util::SmallVector<VkBufferImageCopy, 32> blits;
};
struct HandlePool
{
VulkanObjectPool<Buffer> buffers;
VulkanObjectPool<Image> images;
VulkanObjectPool<LinearHostImage> linear_images;
VulkanObjectPool<ImageView> image_views;
VulkanObjectPool<BufferView> buffer_views;
VulkanObjectPool<Sampler> samplers;
VulkanObjectPool<FenceHolder> fences;
VulkanObjectPool<SemaphoreHolder> semaphores;
VulkanObjectPool<EventHolder> events;
VulkanObjectPool<QueryPoolResult> query;
VulkanObjectPool<CommandBuffer> command_buffers;
VulkanObjectPool<BindlessDescriptorPool> bindless_descriptor_pool;
VulkanObjectPool<DeviceAllocationOwner> allocations;
};
class DebugChannelInterface
{
public:
union Word
{
uint32_t u32;
int32_t s32;
float f32;
};
virtual void message(const std::string &tag, uint32_t code, uint32_t x, uint32_t y, uint32_t z,
uint32_t word_count, const Word *words) = 0;
};
namespace Helper
{
struct WaitSemaphores
{
Util::SmallVector<VkSemaphoreSubmitInfo> binary_waits;
Util::SmallVector<VkSemaphoreSubmitInfo> timeline_waits;
};
class BatchComposer
{
public:
enum { MaxSubmissions = 8 };
BatchComposer();
void add_wait_submissions(WaitSemaphores &sem);
void add_wait_semaphore(SemaphoreHolder &sem, VkPipelineStageFlags2 stage);
void add_wait_semaphore(VkSemaphore sem, VkPipelineStageFlags2 stage);
void add_signal_semaphore(VkSemaphore sem, VkPipelineStageFlags2 stage, uint64_t count);
void add_command_buffer(VkCommandBuffer cmd);
void begin_batch();
Util::SmallVector<VkSubmitInfo2, MaxSubmissions> &bake(int profiling_iteration = -1);
private:
Util::SmallVector<VkSubmitInfo2, MaxSubmissions> submits;
VkPerformanceQuerySubmitInfoKHR profiling_infos[Helper::BatchComposer::MaxSubmissions];
Util::SmallVector<VkSemaphoreSubmitInfo> waits[MaxSubmissions];
Util::SmallVector<VkSemaphoreSubmitInfo> signals[MaxSubmissions];
Util::SmallVector<VkCommandBufferSubmitInfo> cmds[MaxSubmissions];
unsigned submit_index = 0;
};
}
class Device
: public Util::IntrusivePtrEnabled<Device, std::default_delete<Device>, HandleCounter>
#ifdef GRANITE_VULKAN_FOSSILIZE
, public Fossilize::StateCreatorInterface
#endif
{
public:
// Device-based objects which need to poke at internal data structures when their lifetimes end.
// Don't want to expose a lot of internal guts to make this work.
friend class QueryPool;
friend struct QueryPoolResultDeleter;
friend class EventHolder;
friend struct EventHolderDeleter;
friend class SemaphoreHolder;
friend struct SemaphoreHolderDeleter;
friend class FenceHolder;
friend struct FenceHolderDeleter;
friend class Sampler;
friend struct SamplerDeleter;
friend class ImmutableSampler;
friend class ImmutableYcbcrConversion;
friend class Buffer;
friend struct BufferDeleter;
friend class BufferView;
friend struct BufferViewDeleter;
friend class ImageView;
friend struct ImageViewDeleter;
friend class Image;
friend struct ImageDeleter;
friend struct LinearHostImageDeleter;
friend class CommandBuffer;
friend struct CommandBufferDeleter;
friend class BindlessDescriptorPool;
friend struct BindlessDescriptorPoolDeleter;
friend class Program;
friend class WSI;
friend class Cookie;
friend class Framebuffer;
friend class PipelineLayout;
friend class FramebufferAllocator;
friend class RenderPass;
friend class Texture;
friend class DescriptorSetAllocator;
friend class Shader;
friend class ImageResourceHolder;
friend class DeviceAllocationOwner;
friend struct DeviceAllocationDeleter;
Device();
~Device();
// No move-copy.
void operator=(Device &&) = delete;
Device(Device &&) = delete;
// Only called by main thread, during setup phase.
void set_context(const Context &context);
// This is asynchronous in nature. See query_initialization_progress().
// Kicks off Fossilize and shader manager caching.
void begin_shader_caches();
// For debug or trivial applications, blocks until all shader cache work is done.
void wait_shader_caches();
void init_swapchain(const std::vector<VkImage> &swapchain_images, unsigned width, unsigned height, VkFormat format,
VkSurfaceTransformFlagBitsKHR transform, VkImageUsageFlags usage);
void set_swapchain_queue_family_support(uint32_t queue_family_support);
bool can_touch_swapchain_in_command_buffer(CommandBuffer::Type type) const;
void init_external_swapchain(const std::vector<ImageHandle> &swapchain_images);
void init_frame_contexts(unsigned count);
const VolkDeviceTable &get_device_table() const;
// Profiling
bool init_performance_counters(CommandBuffer::Type type, const std::vector<std::string> &names);
bool acquire_profiling();
void release_profiling();
void query_available_performance_counters(CommandBuffer::Type type,
uint32_t *count,
const VkPerformanceCounterKHR **counters,
const VkPerformanceCounterDescriptionKHR **desc);
ImageView &get_swapchain_view();
ImageView &get_swapchain_view(unsigned index);
unsigned get_num_swapchain_images() const;
unsigned get_num_frame_contexts() const;
unsigned get_swapchain_index() const;
unsigned get_current_frame_context() const;
size_t get_pipeline_cache_size();
bool get_pipeline_cache_data(uint8_t *data, size_t size);
bool init_pipeline_cache(const uint8_t *data, size_t size);
// Frame-pushing interface.
void next_frame_context();
// Normally, the main thread ensures forward progress of the frame context
// so that async tasks don't have to care about it,
// but in the case where async threads are continuously pumping Vulkan work
// in the background, they need to reclaim memory if WSI goes to sleep for a long period of time.
void next_frame_context_in_async_thread();
void set_enable_async_thread_frame_context(bool enable);
void wait_idle();
void end_frame_context();
// RenderDoc integration API for app-guided captures.
static bool init_renderdoc_capture();
// Calls next_frame_context() and begins a renderdoc capture.
void begin_renderdoc_capture();
// Calls next_frame_context() and ends the renderdoc capture.
void end_renderdoc_capture();
// Set names for objects for debuggers and profilers.
void set_name(const Buffer &buffer, const char *name);
void set_name(const Image &image, const char *name);
void set_name(const CommandBuffer &cmd, const char *name);
// Generic version.
void set_name(uint64_t object, VkObjectType type, const char *name);
// Submission interface, may be called from any thread at any time.
void flush_frame();
CommandBufferHandle request_command_buffer(CommandBuffer::Type type = CommandBuffer::Type::Generic);
CommandBufferHandle request_command_buffer_for_thread(unsigned thread_index, CommandBuffer::Type type = CommandBuffer::Type::Generic);
CommandBufferHandle request_profiled_command_buffer(CommandBuffer::Type type = CommandBuffer::Type::Generic);
CommandBufferHandle request_profiled_command_buffer_for_thread(unsigned thread_index, CommandBuffer::Type type = CommandBuffer::Type::Generic);
void submit(CommandBufferHandle &cmd, Fence *fence = nullptr,
unsigned semaphore_count = 0, Semaphore *semaphore = nullptr);
void submit_empty(CommandBuffer::Type type,
Fence *fence = nullptr,
SemaphoreHolder *semaphore = nullptr);
// Mark that there have been work submitted in this frame context outside our control
// that accesses resources Vulkan::Device owns.
void submit_external(CommandBuffer::Type type);
void submit_discard(CommandBufferHandle &cmd);
QueueIndices get_physical_queue_type(CommandBuffer::Type queue_type) const;
void register_time_interval(std::string tid, QueryPoolHandle start_ts, QueryPoolHandle end_ts,
const std::string &tag);
// Request shaders and programs. These objects are owned by the Device.
Shader *request_shader(const uint32_t *code, size_t size, const ResourceLayout *layout = nullptr);
Shader *request_shader_by_hash(Util::Hash hash);
Program *request_program(const uint32_t *task_data, size_t task_size,
const uint32_t *mesh_data, size_t mesh_size,
const uint32_t *fragment_data, size_t fragment_size,
const ResourceLayout *task_layout = nullptr,
const ResourceLayout *mesh_layout = nullptr,
const ResourceLayout *fragment_layout = nullptr);
Program *request_program(const uint32_t *vertex_data, size_t vertex_size,
const uint32_t *fragment_data, size_t fragment_size,
const ResourceLayout *vertex_layout = nullptr,
const ResourceLayout *fragment_layout = nullptr);
Program *request_program(const uint32_t *compute_data, size_t compute_size,
const ResourceLayout *layout = nullptr);
Program *request_program(Shader *task, Shader *mesh, Shader *fragment, const ImmutableSamplerBank *sampler_bank = nullptr);
Program *request_program(Shader *vertex, Shader *fragment, const ImmutableSamplerBank *sampler_bank = nullptr);
Program *request_program(Shader *compute, const ImmutableSamplerBank *sampler_bank = nullptr);
const IndirectLayout *request_indirect_layout(const IndirectLayoutToken *tokens,
uint32_t num_tokens, uint32_t stride);
const ImmutableYcbcrConversion *request_immutable_ycbcr_conversion(const VkSamplerYcbcrConversionCreateInfo &info);
const ImmutableSampler *request_immutable_sampler(const SamplerCreateInfo &info, const ImmutableYcbcrConversion *ycbcr);
// Map and unmap buffer objects.
void *map_host_buffer(const Buffer &buffer, MemoryAccessFlags access);
void unmap_host_buffer(const Buffer &buffer, MemoryAccessFlags access);
void *map_host_buffer(const Buffer &buffer, MemoryAccessFlags access, VkDeviceSize offset, VkDeviceSize length);
void unmap_host_buffer(const Buffer &buffer, MemoryAccessFlags access, VkDeviceSize offset, VkDeviceSize length);
void *map_linear_host_image(const LinearHostImage &image, MemoryAccessFlags access);
void unmap_linear_host_image_and_sync(const LinearHostImage &image, MemoryAccessFlags access);
// Create buffers and images.
BufferHandle create_buffer(const BufferCreateInfo &info, const void *initial = nullptr);
BufferHandle create_imported_host_buffer(const BufferCreateInfo &info, VkExternalMemoryHandleTypeFlagBits type, void *host_buffer);
ImageHandle create_image(const ImageCreateInfo &info, const ImageInitialData *initial = nullptr);
ImageHandle create_image_from_staging_buffer(const ImageCreateInfo &info, const InitialImageBuffer *buffer);
LinearHostImageHandle create_linear_host_image(const LinearHostImageCreateInfo &info);
// Does not create any default image views. Only wraps the VkImage
// as a non-owned handle for purposes of API interop.
ImageHandle wrap_image(const ImageCreateInfo &info, VkImage img);
DeviceAllocationOwnerHandle take_device_allocation_ownership(Image &image);
DeviceAllocationOwnerHandle allocate_memory(const MemoryAllocateInfo &info);
// Create staging buffers for images.
InitialImageBuffer create_image_staging_buffer(const ImageCreateInfo &info, const ImageInitialData *initial);
InitialImageBuffer create_image_staging_buffer(const TextureFormatLayout &layout);
// Create image view, buffer views and samplers.
ImageViewHandle create_image_view(const ImageViewCreateInfo &view_info);
BufferViewHandle create_buffer_view(const BufferViewCreateInfo &view_info);
SamplerHandle create_sampler(const SamplerCreateInfo &info);
BindlessDescriptorPoolHandle create_bindless_descriptor_pool(BindlessResourceType type,
unsigned num_sets, unsigned num_descriptors);
// Render pass helpers.
bool image_format_is_supported(VkFormat format, VkFormatFeatureFlags2KHR required, VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL) const;
void get_format_properties(VkFormat format, VkFormatProperties3KHR *properties) const;
bool get_image_format_properties(VkFormat format, VkImageType type, VkImageTiling tiling,
VkImageUsageFlags usage, VkImageCreateFlags flags,
const void *pNext,
VkImageFormatProperties2 *properties2) const;
VkFormat get_default_depth_stencil_format() const;
VkFormat get_default_depth_format() const;
ImageHandle get_transient_attachment(unsigned width, unsigned height, VkFormat format,
unsigned index = 0, unsigned samples = 1, unsigned layers = 1);
RenderPassInfo get_swapchain_render_pass(SwapchainRenderPass style);
// Semaphore API:
// Semaphores in Granite are abstracted to support both binary and timeline semaphores
// internally.
// In practice this means that semaphores behave like single-use binary semaphores,
// with one signal and one wait.
// A single semaphore handle is not reused for multiple submissions, and they must be recycled through
// the device. The intended use is device.submit(&sem), device.add_wait_semaphore(sem); dispose(sem);
// For timeline semaphores, the semaphore is just a proxy object which
// holds the internally owned VkSemaphore + timeline value and is otherwise lightweight.
//
// However, there are various use cases where we explicitly need semaphore objects:
// - Interoperate with other code that only accepts VkSemaphore.
// - Interoperate with external objects. We need to know whether to use binary or timeline.
// For timelines, we need to know which handle type to use (OPAQUE or ID3D12Fence).
// Binary external semaphore is always opaque with TEMPORARY semantics.
void add_wait_semaphore(CommandBuffer::Type type, Semaphore semaphore, VkPipelineStageFlags2 stages, bool flush);
// If transfer_ownership is set, Semaphore owns the VkSemaphore. Otherwise, application must
// free the semaphore when GPU usage of it is complete.
Semaphore request_semaphore(VkSemaphoreTypeKHR type, VkSemaphore handle = VK_NULL_HANDLE, bool transfer_ownership = false);
// Requests a binary or timeline semaphore that can be used to import/export.
// These semaphores cannot be used directly by add_wait_semaphore() and submit_empty().
// See request_timeline_semaphore_as_binary() for how to use timelines.
Semaphore request_semaphore_external(VkSemaphoreTypeKHR type,
VkExternalSemaphoreHandleTypeFlagBits handle_type);
// The created semaphore does not hold ownership of the VkSemaphore object.
// This is used when we want to wait on or signal an external timeline semaphore at a specific timeline value.
// We must collapse the timeline to a "binary" semaphore before we can call submit_empty or add_wait_semaphore().
Semaphore request_timeline_semaphore_as_binary(const SemaphoreHolder &holder, uint64_t value);
// A proxy semaphore which lets us grab a semaphore handle before we signal it.
// Move assignment can be used to move a payload.
// Mostly useful to deal better with render graph implementation.
// For time being however, we'll support moving the payload over to the proxy object.
Semaphore request_proxy_semaphore();
// For compat with existing code that uses this entry point.
inline Semaphore request_legacy_semaphore() { return request_semaphore(VK_SEMAPHORE_TYPE_BINARY_KHR); }
inline VkDevice get_device() const
{
return device;
}
inline VkPhysicalDevice get_physical_device() const
{
return gpu;
}
inline VkInstance get_instance() const
{
return instance;
}
inline const VkPhysicalDeviceMemoryProperties &get_memory_properties() const
{
return mem_props;
}
inline const VkPhysicalDeviceProperties &get_gpu_properties() const
{
return gpu_props;
}
void get_memory_budget(HeapBudget *budget);
const Sampler &get_stock_sampler(StockSampler sampler) const;
#ifdef GRANITE_VULKAN_SYSTEM_HANDLES
// To obtain ShaderManager, ShaderModules must be observed to be complete
// in query_initialization_progress().
ShaderManager &get_shader_manager();
ResourceManager &get_resource_manager();
#endif
// Useful for loading screens or otherwise figuring out
// when we can start rendering in a stable state.
enum class InitializationStage
{
CacheMaintenance,
// When this is done, shader modules and the shader manager have been populated.
// At this stage it is safe to use shaders in a configuration where we
// don't have SPIRV-Cross and/or shaderc to do on the fly compilation.
// For shipping configurations. We can still compile pipelines, but it may stutter.
ShaderModules,
// When this is done, pipelines should never stutter if Fossilize knows about the pipeline.
Pipelines
};
// 0 -> not started
// [1, 99] rough percentage of completion
// >= 100 done
unsigned query_initialization_progress(InitializationStage status) const;
// For some platforms, the device and queue might be shared, possibly across threads, so need some mechanism to
// lock the global device and queue.
void set_queue_lock(std::function<void ()> lock_callback,
std::function<void ()> unlock_callback);
// Alternative form, when we have to provide lock callbacks to external APIs.
void external_queue_lock();
void external_queue_unlock();
const ImplementationWorkarounds &get_workarounds() const
{
return workarounds;
}
const DeviceFeatures &get_device_features() const
{
return ext;
}
bool consumes_debug_markers() const
{
return debug_marker_sensitive;
}
bool swapchain_touched() const;
double convert_device_timestamp_delta(uint64_t start_ticks, uint64_t end_ticks) const;
// Writes a timestamp on host side, which is calibrated to the GPU timebase.
QueryPoolHandle write_calibrated_timestamp();
// A split version of VkEvent handling which lets us record a wait command before signal is recorded.
PipelineEvent begin_signal_event();
const Context::SystemHandles &get_system_handles() const
{
return system_handles;
}
void configure_default_geometry_samplers(float max_aniso, float lod_bias);
bool supports_subgroup_size_log2(bool subgroup_full_group,
uint8_t subgroup_minimum_size_log2,
uint8_t subgroup_maximum_size_log2,
VkShaderStageFlagBits stage = VK_SHADER_STAGE_COMPUTE_BIT) const;
const QueueInfo &get_queue_info() const;
void timestamp_log_reset();
void timestamp_log(const TimestampIntervalReportCallback &cb) const;
private:
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice gpu = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
const VolkDeviceTable *table = nullptr;
const Context *ctx = nullptr;
QueueInfo queue_info;
unsigned num_thread_indices = 1;
std::atomic_uint64_t cookie;
uint64_t allocate_cookie();
void bake_program(Program &program, const ImmutableSamplerBank *sampler_bank);
void merge_combined_resource_layout(CombinedResourceLayout &layout, const Program &program);
void request_vertex_block(BufferBlock &block, VkDeviceSize size);
void request_index_block(BufferBlock &block, VkDeviceSize size);
void request_uniform_block(BufferBlock &block, VkDeviceSize size);
void request_staging_block(BufferBlock &block, VkDeviceSize size);
QueryPoolHandle write_timestamp(VkCommandBuffer cmd, VkPipelineStageFlags2 stage);
void set_acquire_semaphore(unsigned index, Semaphore acquire);
Semaphore consume_release_semaphore();
VkQueue get_current_present_queue() const;
CommandBuffer::Type get_current_present_queue_type() const;
const PipelineLayout *request_pipeline_layout(const CombinedResourceLayout &layout,
const ImmutableSamplerBank *immutable_samplers);
DescriptorSetAllocator *request_descriptor_set_allocator(const DescriptorSetLayout &layout,
const uint32_t *stages_for_sets,
const ImmutableSampler * const *immutable_samplers);
const Framebuffer &request_framebuffer(const RenderPassInfo &info);
const RenderPass &request_render_pass(const RenderPassInfo &info, bool compatible);
VkPhysicalDeviceMemoryProperties mem_props;
VkPhysicalDeviceProperties gpu_props;
DeviceFeatures ext;
bool debug_marker_sensitive = false;
void init_stock_samplers();
void init_stock_sampler(StockSampler sampler, float max_aniso, float lod_bias);
void init_timeline_semaphores();
void deinit_timeline_semaphores();
uint64_t update_wrapped_device_timestamp(uint64_t ts);
int64_t convert_timestamp_to_absolute_nsec(const QueryPoolResult &handle);
Context::SystemHandles system_handles;
QueryPoolHandle write_timestamp_nolock(VkCommandBuffer cmd, VkPipelineStageFlags2 stage);
QueryPoolHandle write_calibrated_timestamp_nolock();
void register_time_interval_nolock(std::string tid, QueryPoolHandle start_ts, QueryPoolHandle end_ts,
const std::string &tag);
// Make sure this is deleted last.
HandlePool handle_pool;
// Calibrated timestamps.
void init_calibrated_timestamps();
void recalibrate_timestamps_fallback();
void recalibrate_timestamps();
bool resample_calibrated_timestamps();
VkTimeDomainEXT calibrated_time_domain = VK_TIME_DOMAIN_DEVICE_EXT;
int64_t calibrated_timestamp_device = 0;
int64_t calibrated_timestamp_host = 0;
int64_t calibrated_timestamp_device_accum = 0;
unsigned timestamp_calibration_counter = 0;
Vulkan::QueryPoolHandle frame_context_begin_ts;
struct Managers
{
DeviceAllocator memory;
FenceManager fence;
SemaphoreManager semaphore;
EventManager event;
BufferPool vbo, ibo, ubo, staging;
TimestampIntervalManager timestamps;
};
Managers managers;
struct
{
std::mutex memory_lock;
std::mutex lock;
std::condition_variable cond;
Util::RWSpinLock read_only_cache;
unsigned counter = 0;
bool async_frame_context = false;
} lock;
struct PerFrame
{
PerFrame(Device *device, unsigned index);
~PerFrame();
void operator=(const PerFrame &) = delete;
PerFrame(const PerFrame &) = delete;
void begin();
void trim_command_pools();
Device &device;
unsigned frame_index;
const VolkDeviceTable &table;
Managers &managers;
std::vector<CommandPool> cmd_pools[QUEUE_INDEX_COUNT];
VkSemaphore timeline_semaphores[QUEUE_INDEX_COUNT] = {};
uint64_t timeline_fences[QUEUE_INDEX_COUNT] = {};
QueryPool query_pool;
std::vector<BufferBlock> vbo_blocks;
std::vector<BufferBlock> ibo_blocks;
std::vector<BufferBlock> ubo_blocks;
std::vector<BufferBlock> staging_blocks;
std::vector<VkFence> wait_and_recycle_fences;
std::vector<DeviceAllocation> allocations;
std::vector<VkFramebuffer> destroyed_framebuffers;
std::vector<VkSampler> destroyed_samplers;
std::vector<VkImageView> destroyed_image_views;
std::vector<VkBufferView> destroyed_buffer_views;
std::vector<VkImage> destroyed_images;
std::vector<VkBuffer> destroyed_buffers;
std::vector<VkDescriptorPool> destroyed_descriptor_pools;
Util::SmallVector<CommandBufferHandle> submissions[QUEUE_INDEX_COUNT];
std::vector<VkSemaphore> recycled_semaphores;
std::vector<VkEvent> recycled_events;
std::vector<VkSemaphore> destroyed_semaphores;
std::vector<VkSemaphore> consumed_semaphores;
struct DebugChannel
{
DebugChannelInterface *iface;
std::string tag;
BufferHandle buffer;
};
std::vector<DebugChannel> debug_channels;
struct TimestampIntervalHandles
{
std::string tid;
QueryPoolHandle start_ts;
QueryPoolHandle end_ts;
TimestampInterval *timestamp_tag;
};
std::vector<TimestampIntervalHandles> timestamp_intervals;
bool in_destructor = false;
};
// The per frame structure must be destroyed after
// the hashmap data structures below, so it must be declared before.
std::vector<std::unique_ptr<PerFrame>> per_frame;
struct
{
Semaphore acquire;
Semaphore release;
std::vector<ImageHandle> swapchain;
VkQueue present_queue = VK_NULL_HANDLE;
Vulkan::CommandBuffer::Type present_queue_type = {};
uint32_t queue_family_support_mask = 0;
unsigned index = 0;
bool consumed = false;
} wsi;
bool can_touch_swapchain_in_command_buffer(QueueIndices physical_type) const;
struct QueueData
{
Util::SmallVector<Semaphore> wait_semaphores;
Util::SmallVector<VkPipelineStageFlags2> wait_stages;
bool need_fence = false;
VkSemaphore timeline_semaphore = VK_NULL_HANDLE;
uint64_t current_timeline = 0;
PerformanceQueryPool performance_query_pool;
} queue_data[QUEUE_INDEX_COUNT];
struct InternalFence
{
VkFence fence;
VkSemaphore timeline;
uint64_t value;
};
void submit_queue(QueueIndices physical_type, InternalFence *fence,
SemaphoreHolder *external_semaphore = nullptr,
unsigned semaphore_count = 0,
Semaphore *semaphore = nullptr,
int profiled_iteration = -1);
PerFrame &frame()
{
VK_ASSERT(frame_context_index < per_frame.size());
VK_ASSERT(per_frame[frame_context_index]);
return *per_frame[frame_context_index];
}
const PerFrame &frame() const
{
VK_ASSERT(frame_context_index < per_frame.size());
VK_ASSERT(per_frame[frame_context_index]);
return *per_frame[frame_context_index];
}
unsigned frame_context_index = 0;
uint32_t find_memory_type(BufferDomain domain, uint32_t mask) const;
uint32_t find_memory_type(ImageDomain domain, uint32_t mask) const;
uint32_t find_memory_type(uint32_t required, uint32_t mask) const;
bool memory_type_is_device_optimal(uint32_t type) const;
bool memory_type_is_host_visible(uint32_t type) const;
const ImmutableSampler *samplers[static_cast<unsigned>(StockSampler::Count)] = {};
VulkanCache<PipelineLayout> pipeline_layouts;
VulkanCache<DescriptorSetAllocator> descriptor_set_allocators;
VulkanCache<RenderPass> render_passes;
VulkanCache<Shader> shaders;
VulkanCache<Program> programs;
VulkanCache<ImmutableSampler> immutable_samplers;
VulkanCache<ImmutableYcbcrConversion> immutable_ycbcr_conversions;
VulkanCache<IndirectLayout> indirect_layouts;
FramebufferAllocator framebuffer_allocator;
TransientAttachmentAllocator transient_allocator;
VkPipelineCache pipeline_cache = VK_NULL_HANDLE;
void init_pipeline_cache();
void flush_pipeline_cache();
PerformanceQueryPool &get_performance_query_pool(QueueIndices physical_type);
void clear_wait_semaphores();
void submit_staging(CommandBufferHandle &cmd, bool flush);
PipelineEvent request_pipeline_event();
std::function<void ()> queue_lock_callback;
std::function<void ()> queue_unlock_callback;
void flush_frame(QueueIndices physical_type);
void submit_empty_inner(QueueIndices type, InternalFence *fence,
SemaphoreHolder *external_semaphore,
unsigned semaphore_count,
Semaphore *semaphore);
void collect_wait_semaphores(QueueData &data, Helper::WaitSemaphores &semaphores);
void emit_queue_signals(Helper::BatchComposer &composer,
SemaphoreHolder *external_semaphore,
VkSemaphore sem, uint64_t timeline, InternalFence *fence,
unsigned semaphore_count, Semaphore *semaphores);
VkResult submit_batches(Helper::BatchComposer &composer, VkQueue queue, VkFence fence,
int profiling_iteration = -1);
VkResult queue_submit(VkQueue queue, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence);
void destroy_buffer(VkBuffer buffer);
void destroy_image(VkImage image);
void destroy_image_view(VkImageView view);
void destroy_buffer_view(VkBufferView view);
void destroy_sampler(VkSampler sampler);
void destroy_framebuffer(VkFramebuffer framebuffer);
void destroy_semaphore(VkSemaphore semaphore);
void consume_semaphore(VkSemaphore semaphore);
void recycle_semaphore(VkSemaphore semaphore);
void destroy_event(VkEvent event);
void free_memory(const DeviceAllocation &alloc);
void reset_fence(VkFence fence, bool observed_wait);
void destroy_descriptor_pool(VkDescriptorPool desc_pool);
void destroy_buffer_nolock(VkBuffer buffer);
void destroy_image_nolock(VkImage image);
void destroy_image_view_nolock(VkImageView view);
void destroy_buffer_view_nolock(VkBufferView view);
void destroy_sampler_nolock(VkSampler sampler);
void destroy_framebuffer_nolock(VkFramebuffer framebuffer);
void destroy_semaphore_nolock(VkSemaphore semaphore);
void consume_semaphore_nolock(VkSemaphore semaphore);
void recycle_semaphore_nolock(VkSemaphore semaphore);
void destroy_event_nolock(VkEvent event);
void free_memory_nolock(const DeviceAllocation &alloc);
void destroy_descriptor_pool_nolock(VkDescriptorPool desc_pool);
void reset_fence_nolock(VkFence fence, bool observed_wait);
void flush_frame_nolock();
CommandBufferHandle request_command_buffer_nolock(unsigned thread_index, CommandBuffer::Type type, bool profiled);
void submit_discard_nolock(CommandBufferHandle &cmd);
void submit_nolock(CommandBufferHandle cmd, Fence *fence,
unsigned semaphore_count, Semaphore *semaphore);
void submit_empty_nolock(QueueIndices physical_type, Fence *fence,
SemaphoreHolder *semaphore, int profiling_iteration);
void add_wait_semaphore_nolock(QueueIndices type, Semaphore semaphore,
VkPipelineStageFlags2 stages, bool flush);
void request_vertex_block_nolock(BufferBlock &block, VkDeviceSize size);
void request_index_block_nolock(BufferBlock &block, VkDeviceSize size);
void request_uniform_block_nolock(BufferBlock &block, VkDeviceSize size);
void request_staging_block_nolock(BufferBlock &block, VkDeviceSize size);
CommandBufferHandle request_secondary_command_buffer_for_thread(unsigned thread_index,
const Framebuffer *framebuffer,
unsigned subpass,
CommandBuffer::Type type = CommandBuffer::Type::Generic);
void add_frame_counter_nolock();
void decrement_frame_counter_nolock();
void submit_secondary(CommandBuffer &primary, CommandBuffer &secondary);
void wait_idle_nolock();
void end_frame_nolock();
void add_debug_channel_buffer(DebugChannelInterface *iface, std::string tag, BufferHandle buffer);
void parse_debug_channel(const PerFrame::DebugChannel &channel);
Fence request_legacy_fence();
#ifdef GRANITE_VULKAN_SYSTEM_HANDLES
ShaderManager shader_manager;
ResourceManager resource_manager;
void init_shader_manager_cache();
void flush_shader_manager_cache();
#endif
#ifdef GRANITE_VULKAN_FOSSILIZE
bool enqueue_create_sampler(Fossilize::Hash hash, const VkSamplerCreateInfo *create_info, VkSampler *sampler) override;
bool enqueue_create_descriptor_set_layout(Fossilize::Hash hash, const VkDescriptorSetLayoutCreateInfo *create_info, VkDescriptorSetLayout *layout) override;
bool enqueue_create_pipeline_layout(Fossilize::Hash hash, const VkPipelineLayoutCreateInfo *create_info, VkPipelineLayout *layout) override;
bool enqueue_create_shader_module(Fossilize::Hash hash, const VkShaderModuleCreateInfo *create_info, VkShaderModule *module) override;
bool enqueue_create_render_pass(Fossilize::Hash hash, const VkRenderPassCreateInfo *create_info, VkRenderPass *render_pass) override;
bool enqueue_create_render_pass2(Fossilize::Hash hash, const VkRenderPassCreateInfo2 *create_info, VkRenderPass *render_pass) override;
bool enqueue_create_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo *create_info, VkPipeline *pipeline) override;
bool enqueue_create_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo *create_info, VkPipeline *pipeline) override;
bool enqueue_create_raytracing_pipeline(Fossilize::Hash hash, const VkRayTracingPipelineCreateInfoKHR *create_info, VkPipeline *pipeline) override;
bool fossilize_replay_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info);
bool fossilize_replay_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info);
void replay_tag_simple(Fossilize::ResourceTag tag);
void register_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo &info);
void register_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo &info);
void register_render_pass(VkRenderPass render_pass, Fossilize::Hash hash, const VkRenderPassCreateInfo2KHR &info);
void register_descriptor_set_layout(VkDescriptorSetLayout layout, Fossilize::Hash hash, const VkDescriptorSetLayoutCreateInfo &info);
void register_pipeline_layout(VkPipelineLayout layout, Fossilize::Hash hash, const VkPipelineLayoutCreateInfo &info);
void register_shader_module(VkShaderModule module, Fossilize::Hash hash, const VkShaderModuleCreateInfo &info);
void register_sampler(VkSampler sampler, Fossilize::Hash hash, const VkSamplerCreateInfo &info);
void register_sampler_ycbcr_conversion(VkSamplerYcbcrConversion ycbcr, const VkSamplerYcbcrConversionCreateInfo &info);
struct RecorderState;
std::unique_ptr<RecorderState> recorder_state;
struct ReplayerState;
std::unique_ptr<ReplayerState> replayer_state;
void promote_write_cache_to_readonly() const;
void promote_readonly_db_from_assets() const;
void init_pipeline_state(const Fossilize::FeatureFilter &filter,
const VkPhysicalDeviceFeatures2 &pdf2,
const VkApplicationInfo &application_info);
void flush_pipeline_state();
void block_until_shader_module_ready();
void block_until_pipeline_ready();
#endif
ImplementationWorkarounds workarounds;
void init_workarounds();
void fill_buffer_sharing_indices(VkBufferCreateInfo &create_info, uint32_t *sharing_indices);
bool allocate_image_memory(DeviceAllocation *allocation, const ImageCreateInfo &info,
VkImage image, VkImageTiling tiling);
void promote_read_write_caches_to_read_only();
};
// A fairly complex helper used for async queue readbacks.
// Typically used for things like headless backend which emulates WSI through readbacks + encode.
struct OwnershipTransferInfo
{
CommandBuffer::Type old_queue;
CommandBuffer::Type new_queue;
VkImageLayout old_image_layout;
VkImageLayout new_image_layout;
VkPipelineStageFlags2 dst_pipeline_stage;
VkAccessFlags2 dst_access;
};
// For an image which was last accessed in old_queue, requests a command buffer
// for new_queue. Commands will be enqueued as necessary in new_queue to ensure that a complete ownership
// transfer has taken place.
// If queue family for old_queue differs from new_queue, a release barrier is enqueued in old_queue.
// In new_queue we perform either an acquire barrier or a simple pipeline barrier to change layout if required.
// If semaphore is a valid handle, it will be waited on in either old_queue to perform release barrier
// or new_queue depending on what is required.
// If the image uses CONCURRENT sharing mode, acquire/release barriers are skipped.
CommandBufferHandle request_command_buffer_with_ownership_transfer(
Device &device,
const Vulkan::Image &image,
const OwnershipTransferInfo &info,
const Vulkan::Semaphore &semaphore);
using DeviceHandle = Util::IntrusivePtr<Device>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "device.hpp"
#include "thread_group.hpp"
namespace Vulkan
{
struct Device::RecorderState
{
RecorderState();
~RecorderState();
std::unique_ptr<Fossilize::DatabaseInterface> db;
Fossilize::StateRecorder recorder;
std::atomic_bool recorder_ready;
};
static constexpr unsigned NumTasks = 4;
struct Device::ReplayerState
{
ReplayerState();
~ReplayerState();
std::vector<Fossilize::Hash> module_hashes;
std::vector<Fossilize::Hash> graphics_hashes;
std::vector<Fossilize::Hash> compute_hashes;
Fossilize::StateReplayer base_replayer;
Fossilize::StateReplayer graphics_replayer;
Fossilize::StateReplayer compute_replayer;
Fossilize::FeatureFilter *feature_filter = nullptr;
std::unique_ptr<Fossilize::DatabaseInterface> db;
Granite::TaskGroupHandle complete;
Granite::TaskGroupHandle module_ready;
Granite::TaskGroupHandle pipeline_ready;
std::vector<std::pair<Fossilize::Hash, VkGraphicsPipelineCreateInfo *>> graphics_pipelines;
std::vector<std::pair<Fossilize::Hash, VkComputePipelineCreateInfo *>> compute_pipelines;
struct
{
std::atomic_uint32_t pipelines;
std::atomic_uint32_t modules;
std::atomic_uint32_t prepare;
uint32_t num_pipelines = 0;
uint32_t num_modules = 0;
} progress;
};
}

View File

@@ -0,0 +1,72 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "event_manager.hpp"
#include "device.hpp"
namespace Vulkan
{
EventManager::~EventManager()
{
if (!workaround)
for (auto &event : events)
table->vkDestroyEvent(device->get_device(), event, nullptr);
}
void EventManager::recycle(VkEvent event)
{
if (!workaround && event != VK_NULL_HANDLE)
{
table->vkResetEvent(device->get_device(), event);
events.push_back(event);
}
}
VkEvent EventManager::request_cleared_event()
{
if (workaround)
{
// Can't use reinterpret_cast because of MSVC.
return (VkEvent) ++workaround_counter;
}
else if (events.empty())
{
VkEvent event;
VkEventCreateInfo info = { VK_STRUCTURE_TYPE_EVENT_CREATE_INFO };
table->vkCreateEvent(device->get_device(), &info, nullptr, &event);
return event;
}
else
{
auto event = events.back();
events.pop_back();
return event;
}
}
void EventManager::init(Device *device_)
{
device = device_;
table = &device->get_device_table();
workaround = device_->get_workarounds().emulate_event_as_pipeline_barrier;
}
}

View File

@@ -0,0 +1,47 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include <vector>
namespace Vulkan
{
class Device;
class EventManager
{
public:
void init(Device *device);
~EventManager();
VkEvent request_cleared_event();
void recycle(VkEvent event);
private:
Device *device = nullptr;
const VolkDeviceTable *table = nullptr;
std::vector<VkEvent> events;
uint64_t workaround_counter = 0;
bool workaround = false;
};
}

View File

@@ -0,0 +1,124 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "fence.hpp"
#include "device.hpp"
namespace Vulkan
{
FenceHolder::~FenceHolder()
{
if (fence != VK_NULL_HANDLE)
{
if (internal_sync)
device->reset_fence_nolock(fence, observed_wait);
else
device->reset_fence(fence, observed_wait);
}
}
const VkFence &FenceHolder::get_fence() const
{
return fence;
}
void FenceHolder::wait()
{
auto &table = device->get_device_table();
// Waiting for the same VkFence in parallel is not allowed, and there seems to be some shenanigans on Intel
// when waiting for a timeline semaphore in parallel with same value as well.
std::lock_guard<std::mutex> holder{lock};
if (observed_wait)
return;
if (timeline_value != 0)
{
VK_ASSERT(timeline_semaphore);
VkSemaphoreWaitInfo info = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO };
info.semaphoreCount = 1;
info.pSemaphores = &timeline_semaphore;
info.pValues = &timeline_value;
if (table.vkWaitSemaphores(device->get_device(), &info, UINT64_MAX) != VK_SUCCESS)
LOGE("Failed to wait for timeline semaphore!\n");
else
observed_wait = true;
}
else
{
if (table.vkWaitForFences(device->get_device(), 1, &fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS)
LOGE("Failed to wait for fence!\n");
else
observed_wait = true;
}
}
bool FenceHolder::wait_timeout(uint64_t timeout)
{
bool ret;
auto &table = device->get_device_table();
// Waiting for the same VkFence in parallel is not allowed, and there seems to be some shenanigans on Intel
// when waiting for a timeline semaphore in parallel with same value as well.
std::lock_guard<std::mutex> holder{lock};
if (observed_wait)
return true;
if (timeline_value != 0)
{
VK_ASSERT(timeline_semaphore);
if (timeout == 0)
{
uint64_t current_value = 0;
ret = table.vkGetSemaphoreCounterValue(device->get_device(), timeline_semaphore, &current_value) == VK_SUCCESS &&
current_value >= timeline_value;
}
else
{
VkSemaphoreWaitInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO};
info.semaphoreCount = 1;
info.pSemaphores = &timeline_semaphore;
info.pValues = &timeline_value;
ret = table.vkWaitSemaphores(device->get_device(), &info, timeout) == VK_SUCCESS;
}
}
else
{
if (timeout == 0)
ret = table.vkGetFenceStatus(device->get_device(), fence) == VK_SUCCESS;
else
ret = table.vkWaitForFences(device->get_device(), 1, &fence, VK_TRUE, timeout) == VK_SUCCESS;
}
if (ret)
observed_wait = true;
return ret;
}
void FenceHolderDeleter::operator()(Vulkan::FenceHolder *fence)
{
fence->device->handle_pool.fences.free(fence);
}
}

View File

@@ -0,0 +1,81 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_common.hpp"
#include "vulkan_headers.hpp"
#include "object_pool.hpp"
#include "cookie.hpp"
#include <mutex>
namespace Vulkan
{
class Device;
class FenceHolder;
struct FenceHolderDeleter
{
void operator()(FenceHolder *fence);
};
class FenceHolder : public Util::IntrusivePtrEnabled<FenceHolder, FenceHolderDeleter, HandleCounter>, public InternalSyncEnabled
{
public:
friend struct FenceHolderDeleter;
friend class WSI;
~FenceHolder();
void wait();
bool wait_timeout(uint64_t nsec);
private:
friend class Util::ObjectPool<FenceHolder>;
FenceHolder(Device *device_, VkFence fence_)
: device(device_),
fence(fence_),
timeline_semaphore(VK_NULL_HANDLE),
timeline_value(0)
{
}
FenceHolder(Device *device_, uint64_t value, VkSemaphore timeline_semaphore_)
: device(device_),
fence(VK_NULL_HANDLE),
timeline_semaphore(timeline_semaphore_),
timeline_value(value)
{
VK_ASSERT(value > 0);
}
const VkFence &get_fence() const;
Device *device;
VkFence fence;
VkSemaphore timeline_semaphore;
uint64_t timeline_value;
bool observed_wait = false;
std::mutex lock;
};
using Fence = Util::IntrusivePtr<FenceHolder>;
}

View File

@@ -0,0 +1,61 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "fence_manager.hpp"
#include "device.hpp"
namespace Vulkan
{
void FenceManager::init(Device *device_)
{
device = device_;
table = &device->get_device_table();
}
VkFence FenceManager::request_cleared_fence()
{
if (!fences.empty())
{
auto ret = fences.back();
fences.pop_back();
return ret;
}
else
{
VkFence fence;
VkFenceCreateInfo info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
table->vkCreateFence(device->get_device(), &info, nullptr, &fence);
return fence;
}
}
void FenceManager::recycle_fence(VkFence fence)
{
fences.push_back(fence);
}
FenceManager::~FenceManager()
{
for (auto &fence : fences)
table->vkDestroyFence(device->get_device(), fence, nullptr);
}
}

View File

@@ -0,0 +1,45 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include <vector>
namespace Vulkan
{
class Device;
class FenceManager
{
public:
void init(Device *device);
~FenceManager();
VkFence request_cleared_fence();
void recycle_fence(VkFence fence);
private:
Device *device = nullptr;
const VolkDeviceTable *table = nullptr;
std::vector<VkFence> fences;
};
}

View File

@@ -0,0 +1,386 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "texture/texture_format.hpp"
namespace Vulkan
{
enum class FormatCompressionType
{
Uncompressed,
BC,
ETC,
ASTC
};
static inline FormatCompressionType format_compression_type(VkFormat format)
{
switch (format)
{
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
case VK_FORMAT_BC2_SRGB_BLOCK:
case VK_FORMAT_BC2_UNORM_BLOCK:
case VK_FORMAT_BC3_SRGB_BLOCK:
case VK_FORMAT_BC3_UNORM_BLOCK:
case VK_FORMAT_BC4_UNORM_BLOCK:
case VK_FORMAT_BC4_SNORM_BLOCK:
case VK_FORMAT_BC5_UNORM_BLOCK:
case VK_FORMAT_BC5_SNORM_BLOCK:
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK:
case VK_FORMAT_BC7_UNORM_BLOCK:
return FormatCompressionType::BC;
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
return FormatCompressionType::ETC;
#define astc_fmt(w, h) \
case VK_FORMAT_ASTC_##w##x##h##_UNORM_BLOCK: \
case VK_FORMAT_ASTC_##w##x##h##_SRGB_BLOCK: \
case VK_FORMAT_ASTC_##w##x##h##_SFLOAT_BLOCK_EXT
astc_fmt(4, 4):
astc_fmt(5, 4):
astc_fmt(5, 5):
astc_fmt(6, 5):
astc_fmt(6, 6):
astc_fmt(8, 5):
astc_fmt(8, 6):
astc_fmt(8, 8):
astc_fmt(10, 5):
astc_fmt(10, 6):
astc_fmt(10, 8):
astc_fmt(10, 10):
astc_fmt(12, 10):
astc_fmt(12, 12):
return FormatCompressionType::ASTC;
#undef astc_fmt
default:
return FormatCompressionType::Uncompressed;
}
}
static inline bool format_is_compressed_hdr(VkFormat format)
{
switch (format)
{
#define astc_fmt(w, h) case VK_FORMAT_ASTC_##w##x##h##_SFLOAT_BLOCK_EXT
astc_fmt(4, 4):
astc_fmt(5, 4):
astc_fmt(5, 5):
astc_fmt(6, 5):
astc_fmt(6, 6):
astc_fmt(8, 5):
astc_fmt(8, 6):
astc_fmt(8, 8):
astc_fmt(10, 5):
astc_fmt(10, 6):
astc_fmt(10, 8):
astc_fmt(10, 10):
astc_fmt(12, 10):
astc_fmt(12, 12):
#undef astc_fmt
return true;
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
return true;
default:
return false;
}
}
static inline bool format_is_srgb(VkFormat format)
{
switch (format)
{
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_R8_SRGB:
case VK_FORMAT_R8G8_SRGB:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_SRGB:
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
case VK_FORMAT_BC2_SRGB_BLOCK:
case VK_FORMAT_BC3_SRGB_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
return true;
default:
return false;
}
}
static inline bool format_has_depth_aspect(VkFormat format)
{
switch (format)
{
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
static inline bool format_has_stencil_aspect(VkFormat format)
{
switch (format)
{
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
case VK_FORMAT_S8_UINT:
return true;
default:
return false;
}
}
static inline bool format_has_depth_or_stencil_aspect(VkFormat format)
{
return format_has_depth_aspect(format) || format_has_stencil_aspect(format);
}
static inline VkImageAspectFlags format_to_aspect_mask(VkFormat format)
{
switch (format)
{
case VK_FORMAT_UNDEFINED:
return 0;
case VK_FORMAT_S8_UINT:
return VK_IMAGE_ASPECT_STENCIL_BIT;
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return VK_IMAGE_ASPECT_STENCIL_BIT | VK_IMAGE_ASPECT_DEPTH_BIT;
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_X8_D24_UNORM_PACK32:
return VK_IMAGE_ASPECT_DEPTH_BIT;
default:
return VK_IMAGE_ASPECT_COLOR_BIT;
}
}
static inline void format_align_dim(VkFormat format, uint32_t &width, uint32_t &height)
{
uint32_t align_width, align_height;
TextureFormatLayout::format_block_dim(format, align_width, align_height);
width = ((width + align_width - 1) / align_width) * align_width;
height = ((height + align_height - 1) / align_height) * align_height;
}
static inline void format_num_blocks(VkFormat format, uint32_t &width, uint32_t &height)
{
uint32_t align_width, align_height;
TextureFormatLayout::format_block_dim(format, align_width, align_height);
width = (width + align_width - 1) / align_width;
height = (height + align_height - 1) / align_height;
}
static inline VkDeviceSize format_get_layer_size(VkFormat format, VkImageAspectFlags aspect, unsigned width, unsigned height, unsigned depth)
{
uint32_t blocks_x = width;
uint32_t blocks_y = height;
format_num_blocks(format, blocks_x, blocks_y);
format_align_dim(format, width, height);
VkDeviceSize size = VkDeviceSize(TextureFormatLayout::format_block_size(format, aspect)) * depth * blocks_x * blocks_y;
return size;
}
static inline unsigned format_ycbcr_num_planes(VkFormat format)
{
switch (format)
{
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
return 3;
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
return 2;
default:
return 1;
}
}
static inline void format_ycbcr_downsample_dimensions(VkFormat format, VkImageAspectFlags aspect, uint32_t &width, uint32_t &height)
{
if (aspect == VK_IMAGE_ASPECT_PLANE_0_BIT)
return;
switch (format)
{
#define fmt(x, sub0, sub1) \
case VK_FORMAT_##x: \
width >>= sub0; \
height >>= sub1; \
break
fmt(G8_B8_R8_3PLANE_420_UNORM, 1, 1);
fmt(G8_B8R8_2PLANE_420_UNORM, 1, 1);
fmt(G8_B8_R8_3PLANE_422_UNORM, 1, 0);
fmt(G8_B8R8_2PLANE_422_UNORM, 1, 0);
fmt(G8_B8_R8_3PLANE_444_UNORM, 0, 0);
fmt(G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, 1, 1);
fmt(G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, 1, 0);
fmt(G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, 0, 0);
fmt(G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, 1, 1);
fmt(G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, 1, 0);
fmt(G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, 1, 1);
fmt(G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, 1, 0);
fmt(G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, 0, 0);
fmt(G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, 1, 1);
fmt(G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, 1, 0);
fmt(G16_B16_R16_3PLANE_420_UNORM, 1, 1);
fmt(G16_B16_R16_3PLANE_422_UNORM, 1, 0);
fmt(G16_B16_R16_3PLANE_444_UNORM, 0, 0);
fmt(G16_B16R16_2PLANE_420_UNORM, 1, 1);
fmt(G16_B16R16_2PLANE_422_UNORM, 1, 0);
default:
break;
}
#undef fmt
}
static inline bool format_supports_storage_image_read_write_without_format(VkFormat format)
{
/* from https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#formats-without-shader-storage-format */
static const VkFormat supported_formats[] =
{
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_UINT,
VK_FORMAT_R8G8B8A8_SINT,
VK_FORMAT_R32_UINT,
VK_FORMAT_R32_SINT,
VK_FORMAT_R32_SFLOAT,
VK_FORMAT_R32G32_UINT,
VK_FORMAT_R32G32_SINT,
VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R32G32B32A32_UINT,
VK_FORMAT_R32G32B32A32_SINT,
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_FORMAT_R16G16B16A16_UINT,
VK_FORMAT_R16G16B16A16_SINT,
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R16G16_SFLOAT,
VK_FORMAT_B10G11R11_UFLOAT_PACK32,
VK_FORMAT_R16_SFLOAT,
VK_FORMAT_R16G16B16A16_UNORM,
VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_FORMAT_R16G16_UNORM,
VK_FORMAT_R8G8_UNORM,
VK_FORMAT_R16_UNORM,
VK_FORMAT_R8_UNORM,
VK_FORMAT_R16G16B16A16_SNORM,
VK_FORMAT_R16G16_SNORM,
VK_FORMAT_R8G8_SNORM,
VK_FORMAT_R16_SNORM,
VK_FORMAT_R8_SNORM,
VK_FORMAT_R16G16_SINT,
VK_FORMAT_R8G8_SINT,
VK_FORMAT_R16_SINT,
VK_FORMAT_R8_SINT,
VK_FORMAT_A2B10G10R10_UINT_PACK32,
VK_FORMAT_R16G16_UINT,
VK_FORMAT_R8G8_UINT,
VK_FORMAT_R16_UINT,
VK_FORMAT_R8_UINT,
};
for (auto fmt : supported_formats)
if (fmt == format)
return true;
return false;
}
}

View File

@@ -0,0 +1,244 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "image.hpp"
#include "device.hpp"
#include "buffer.hpp"
namespace Vulkan
{
ImageView::ImageView(Device *device_, VkImageView view_, const ImageViewCreateInfo &info_)
: Cookie(device_)
, device(device_)
, view(view_)
, info(info_)
{
}
VkImageView ImageView::get_render_target_view(unsigned layer) const
{
// Transient images just have one layer.
if (info.image->get_create_info().domain == ImageDomain::Transient)
return view;
VK_ASSERT(layer < get_create_info().layers);
if (render_target_views.empty())
return view;
else
{
VK_ASSERT(layer < render_target_views.size());
return render_target_views[layer];
}
}
ImageView::~ImageView()
{
if (internal_sync)
{
device->destroy_image_view_nolock(view);
if (depth_view != VK_NULL_HANDLE)
device->destroy_image_view_nolock(depth_view);
if (stencil_view != VK_NULL_HANDLE)
device->destroy_image_view_nolock(stencil_view);
if (unorm_view != VK_NULL_HANDLE)
device->destroy_image_view_nolock(unorm_view);
if (srgb_view != VK_NULL_HANDLE)
device->destroy_image_view_nolock(srgb_view);
for (auto &v : render_target_views)
device->destroy_image_view_nolock(v);
}
else
{
device->destroy_image_view(view);
if (depth_view != VK_NULL_HANDLE)
device->destroy_image_view(depth_view);
if (stencil_view != VK_NULL_HANDLE)
device->destroy_image_view(stencil_view);
if (unorm_view != VK_NULL_HANDLE)
device->destroy_image_view(unorm_view);
if (srgb_view != VK_NULL_HANDLE)
device->destroy_image_view(srgb_view);
for (auto &v : render_target_views)
device->destroy_image_view(v);
}
}
unsigned ImageView::get_view_width() const
{
return info.image->get_width(info.base_level);
}
unsigned ImageView::get_view_height() const
{
return info.image->get_height(info.base_level);
}
unsigned ImageView::get_view_depth() const
{
return info.image->get_depth(info.base_level);
}
Image::Image(Device *device_, VkImage image_, VkImageView default_view, const DeviceAllocation &alloc_,
const ImageCreateInfo &create_info_, VkImageViewType view_type)
: Cookie(device_)
, device(device_)
, image(image_)
, alloc(alloc_)
, create_info(create_info_)
{
if (default_view != VK_NULL_HANDLE)
{
ImageViewCreateInfo info;
info.image = this;
info.view_type = view_type;
info.format = create_info.format;
info.base_level = 0;
info.levels = create_info.levels;
info.base_layer = 0;
info.layers = create_info.layers;
view = ImageViewHandle(device->handle_pool.image_views.allocate(device, default_view, info));
}
}
DeviceAllocation Image::take_allocation_ownership()
{
DeviceAllocation ret = {};
std::swap(ret, alloc);
return ret;
}
ExternalHandle Image::export_handle()
{
return alloc.export_handle(*device);
}
void Image::disown_image()
{
owns_image = false;
}
void Image::disown_memory_allocation()
{
owns_memory_allocation = false;
}
Image::~Image()
{
if (owns_image)
{
if (internal_sync)
device->destroy_image_nolock(image);
else
device->destroy_image(image);
}
if (alloc.get_memory() && owns_memory_allocation)
{
if (internal_sync)
device->free_memory_nolock(alloc);
else
device->free_memory(alloc);
}
}
const Buffer &LinearHostImage::get_host_visible_buffer() const
{
return *cpu_image;
}
bool LinearHostImage::need_staging_copy() const
{
return gpu_image->get_create_info().domain != ImageDomain::LinearHostCached &&
gpu_image->get_create_info().domain != ImageDomain::LinearHost;
}
const DeviceAllocation &LinearHostImage::get_host_visible_allocation() const
{
return need_staging_copy() ? cpu_image->get_allocation() : gpu_image->get_allocation();
}
const ImageView &LinearHostImage::get_view() const
{
return gpu_image->get_view();
}
const Image &LinearHostImage::get_image() const
{
return *gpu_image;
}
size_t LinearHostImage::get_offset() const
{
return row_offset;
}
size_t LinearHostImage::get_row_pitch_bytes() const
{
return row_pitch;
}
VkPipelineStageFlags2 LinearHostImage::get_used_pipeline_stages() const
{
return stages;
}
LinearHostImage::LinearHostImage(Device *device_, ImageHandle gpu_image_, BufferHandle cpu_image_, VkPipelineStageFlags2 stages_)
: device(device_), gpu_image(std::move(gpu_image_)), cpu_image(std::move(cpu_image_)), stages(stages_)
{
if (gpu_image->get_create_info().domain == ImageDomain::LinearHostCached ||
gpu_image->get_create_info().domain == ImageDomain::LinearHost)
{
VkImageSubresource sub = {};
sub.aspectMask = format_to_aspect_mask(gpu_image->get_format());
VkSubresourceLayout layout;
auto &table = device_->get_device_table();
table.vkGetImageSubresourceLayout(device->get_device(), gpu_image->get_image(), &sub, &layout);
row_pitch = layout.rowPitch;
row_offset = layout.offset;
}
else
{
row_pitch = gpu_image->get_width() * TextureFormatLayout::format_block_size(gpu_image->get_format(),
format_to_aspect_mask(gpu_image->get_format()));
row_offset = 0;
}
}
void ImageViewDeleter::operator()(ImageView *view)
{
view->device->handle_pool.image_views.free(view);
}
void ImageDeleter::operator()(Image *image)
{
image->device->handle_pool.images.free(image);
}
void LinearHostImageDeleter::operator()(LinearHostImage *image)
{
image->device->handle_pool.linear_images.free(image);
}
}

View File

@@ -0,0 +1,581 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "cookie.hpp"
#include "format.hpp"
#include "vulkan_common.hpp"
#include "memory_allocator.hpp"
#include "vulkan_headers.hpp"
#include <algorithm>
namespace Vulkan
{
class Device;
static inline uint32_t image_num_miplevels(const VkExtent3D &extent)
{
uint32_t size = std::max<uint32_t>(std::max<uint32_t>(extent.width, extent.height), extent.depth);
return Util::floor_log2(size) + 1;
}
static inline VkFormatFeatureFlags image_usage_to_features(VkImageUsageFlags usage)
{
VkFormatFeatureFlags flags = 0;
if (usage & VK_IMAGE_USAGE_SAMPLED_BIT)
flags |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
if (usage & VK_IMAGE_USAGE_STORAGE_BIT)
flags |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
flags |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;
if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
flags |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
return flags;
}
struct ImageInitialData
{
const void *data;
unsigned row_length;
unsigned image_height;
};
enum ImageMiscFlagBits
{
IMAGE_MISC_GENERATE_MIPS_BIT = 1 << 0,
IMAGE_MISC_FORCE_ARRAY_BIT = 1 << 1,
IMAGE_MISC_MUTABLE_SRGB_BIT = 1 << 2,
IMAGE_MISC_CONCURRENT_QUEUE_GRAPHICS_BIT = 1 << 3,
IMAGE_MISC_CONCURRENT_QUEUE_ASYNC_COMPUTE_BIT = 1 << 4,
IMAGE_MISC_CONCURRENT_QUEUE_ASYNC_TRANSFER_BIT = 1 << 6,
IMAGE_MISC_CONCURRENT_QUEUE_VIDEO_DECODE_BIT = 1 << 7,
IMAGE_MISC_VERIFY_FORMAT_FEATURE_SAMPLED_LINEAR_FILTER_BIT = 1 << 8,
IMAGE_MISC_LINEAR_IMAGE_IGNORE_DEVICE_LOCAL_BIT = 1 << 9,
IMAGE_MISC_FORCE_NO_DEDICATED_BIT = 1 << 10,
IMAGE_MISC_NO_DEFAULT_VIEWS_BIT = 1 << 11,
IMAGE_MISC_EXTERNAL_MEMORY_BIT = 1 << 12,
IMAGE_MISC_CONCURRENT_QUEUE_VIDEO_ENCODE_BIT = 1 << 13,
IMAGE_MISC_CONCURRENT_QUEUE_VIDEO_DUPLEX =
IMAGE_MISC_CONCURRENT_QUEUE_VIDEO_DECODE_BIT |
IMAGE_MISC_CONCURRENT_QUEUE_VIDEO_ENCODE_BIT,
};
using ImageMiscFlags = uint32_t;
enum ImageViewMiscFlagBits
{
IMAGE_VIEW_MISC_FORCE_ARRAY_BIT = 1 << 0
};
using ImageViewMiscFlags = uint32_t;
class Image;
class ImmutableYcbcrConversion;
struct ImageViewCreateInfo
{
const Image *image = nullptr;
VkFormat format = VK_FORMAT_UNDEFINED;
unsigned base_level = 0;
unsigned levels = VK_REMAINING_MIP_LEVELS;
unsigned base_layer = 0;
unsigned layers = VK_REMAINING_ARRAY_LAYERS;
VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_MAX_ENUM;
ImageViewMiscFlags misc = 0;
VkComponentMapping swizzle = {
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
};
VkImageAspectFlags aspect = 0;
const ImmutableYcbcrConversion *ycbcr_conversion = nullptr;
};
class ImageView;
struct ImageViewDeleter
{
void operator()(ImageView *view);
};
class ImageView : public Util::IntrusivePtrEnabled<ImageView, ImageViewDeleter, HandleCounter>,
public Cookie, public InternalSyncEnabled
{
public:
friend struct ImageViewDeleter;
ImageView(Device *device, VkImageView view, const ImageViewCreateInfo &info);
~ImageView();
void set_alt_views(VkImageView depth, VkImageView stencil)
{
VK_ASSERT(depth_view == VK_NULL_HANDLE);
VK_ASSERT(stencil_view == VK_NULL_HANDLE);
depth_view = depth;
stencil_view = stencil;
}
void set_render_target_views(std::vector<VkImageView> views)
{
VK_ASSERT(render_target_views.empty());
render_target_views = std::move(views);
}
void set_unorm_view(VkImageView view_)
{
VK_ASSERT(unorm_view == VK_NULL_HANDLE);
unorm_view = view_;
}
void set_srgb_view(VkImageView view_)
{
VK_ASSERT(srgb_view == VK_NULL_HANDLE);
srgb_view = view_;
}
// By default, gets a combined view which includes all aspects in the image.
// This would be used mostly for render targets.
VkImageView get_view() const
{
return view;
}
VkImageView get_render_target_view(unsigned layer) const;
// Gets an image view which only includes floating point domains.
// Takes effect when we want to sample from an image which is Depth/Stencil,
// but we only want to sample depth.
VkImageView get_float_view() const
{
return depth_view != VK_NULL_HANDLE ? depth_view : view;
}
// Gets an image view which only includes integer domains.
// Takes effect when we want to sample from an image which is Depth/Stencil,
// but we only want to sample stencil.
VkImageView get_integer_view() const
{
return stencil_view != VK_NULL_HANDLE ? stencil_view : view;
}
VkImageView get_unorm_view() const
{
return unorm_view;
}
VkImageView get_srgb_view() const
{
return srgb_view;
}
VkFormat get_format() const
{
return info.format;
}
const Image &get_image() const
{
return *info.image;
}
const ImageViewCreateInfo &get_create_info() const
{
return info;
}
unsigned get_view_width() const;
unsigned get_view_height() const;
unsigned get_view_depth() const;
private:
Device *device;
VkImageView view;
std::vector<VkImageView> render_target_views;
VkImageView depth_view = VK_NULL_HANDLE;
VkImageView stencil_view = VK_NULL_HANDLE;
VkImageView unorm_view = VK_NULL_HANDLE;
VkImageView srgb_view = VK_NULL_HANDLE;
ImageViewCreateInfo info;
};
using ImageViewHandle = Util::IntrusivePtr<ImageView>;
enum class ImageDomain
{
Physical,
Transient,
LinearHostCached,
LinearHost
};
struct ImageCreateInfo
{
ImageDomain domain = ImageDomain::Physical;
unsigned width = 0;
unsigned height = 0;
unsigned depth = 1;
unsigned levels = 1;
VkFormat format = VK_FORMAT_UNDEFINED;
VkImageType type = VK_IMAGE_TYPE_2D;
unsigned layers = 1;
VkImageUsageFlags usage = 0;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
VkImageCreateFlags flags = 0;
ImageMiscFlags misc = 0;
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL;
VkComponentMapping swizzle = {
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
};
const DeviceAllocation **memory_aliases = nullptr;
unsigned num_memory_aliases = 0;
const ImmutableYcbcrConversion *ycbcr_conversion = nullptr;
void *pnext = nullptr;
ExternalHandle external;
static ImageCreateInfo immutable_image(const TextureFormatLayout &layout)
{
Vulkan::ImageCreateInfo info;
info.width = layout.get_width();
info.height = layout.get_height();
info.type = layout.get_image_type();
info.depth = layout.get_depth();
info.format = layout.get_format();
info.layers = layout.get_layers();
info.levels = layout.get_levels();
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
info.initial_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.domain = ImageDomain::Physical;
return info;
}
static ImageCreateInfo immutable_2d_image(unsigned width, unsigned height, VkFormat format, bool mipmapped = false)
{
ImageCreateInfo info;
info.width = width;
info.height = height;
info.depth = 1;
info.levels = mipmapped ? 0u : 1u;
info.format = format;
info.type = VK_IMAGE_TYPE_2D;
info.layers = 1;
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.flags = 0;
info.misc = mipmapped ? unsigned(IMAGE_MISC_GENERATE_MIPS_BIT) : 0u;
info.initial_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
return info;
}
static ImageCreateInfo
immutable_3d_image(unsigned width, unsigned height, unsigned depth, VkFormat format, bool mipmapped = false)
{
ImageCreateInfo info = immutable_2d_image(width, height, format, mipmapped);
info.depth = depth;
info.type = VK_IMAGE_TYPE_3D;
return info;
}
static ImageCreateInfo render_target(unsigned width, unsigned height, VkFormat format)
{
ImageCreateInfo info;
info.width = width;
info.height = height;
info.depth = 1;
info.levels = 1;
info.format = format;
info.type = VK_IMAGE_TYPE_2D;
info.layers = 1;
info.usage = (format_has_depth_or_stencil_aspect(format) ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT :
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.flags = 0;
info.misc = 0;
info.initial_layout = format_has_depth_or_stencil_aspect(format) ?
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
return info;
}
static ImageCreateInfo transient_render_target(unsigned width, unsigned height, VkFormat format)
{
ImageCreateInfo info;
info.domain = ImageDomain::Transient;
info.width = width;
info.height = height;
info.depth = 1;
info.levels = 1;
info.format = format;
info.type = VK_IMAGE_TYPE_2D;
info.layers = 1;
info.usage = (format_has_depth_or_stencil_aspect(format) ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT :
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) |
VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.flags = 0;
info.misc = 0;
info.initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
return info;
}
static uint32_t compute_view_formats(const ImageCreateInfo &info, VkFormat *formats)
{
if ((info.misc & IMAGE_MISC_MUTABLE_SRGB_BIT) == 0)
return 0;
switch (info.format)
{
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
formats[0] = VK_FORMAT_R8G8B8A8_UNORM;
formats[1] = VK_FORMAT_R8G8B8A8_SRGB;
return 2;
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
formats[0] = VK_FORMAT_B8G8R8A8_UNORM;
formats[1] = VK_FORMAT_B8G8R8A8_SRGB;
return 2;
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
formats[0] = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
formats[1] = VK_FORMAT_A8B8G8R8_SRGB_PACK32;
return 2;
default:
return 0;
}
}
};
class Image;
struct ImageDeleter
{
void operator()(Image *image);
};
enum class Layout
{
Optimal,
General
};
class Image : public Util::IntrusivePtrEnabled<Image, ImageDeleter, HandleCounter>,
public Cookie, public InternalSyncEnabled
{
public:
friend struct ImageDeleter;
~Image();
Image(Image &&) = delete;
Image &operator=(Image &&) = delete;
const ImageView &get_view() const
{
VK_ASSERT(view);
return *view;
}
ImageView &get_view()
{
VK_ASSERT(view);
return *view;
}
VkImage get_image() const
{
return image;
}
VkFormat get_format() const
{
return create_info.format;
}
uint32_t get_width(uint32_t lod = 0) const
{
return std::max<uint32_t>(1u, create_info.width >> lod);
}
uint32_t get_height(uint32_t lod = 0) const
{
return std::max<uint32_t>(1u, create_info.height >> lod);
}
uint32_t get_depth(uint32_t lod = 0) const
{
return std::max<uint32_t>(1u, create_info.depth >> lod);
}
const ImageCreateInfo &get_create_info() const
{
return create_info;
}
VkImageLayout get_layout(VkImageLayout optimal) const
{
return layout_type == Layout::Optimal ? optimal : VK_IMAGE_LAYOUT_GENERAL;
}
Layout get_layout_type() const
{
return layout_type;
}
void set_layout(Layout layout)
{
layout_type = layout;
}
bool is_swapchain_image() const
{
return swapchain_layout != VK_IMAGE_LAYOUT_UNDEFINED;
}
VkImageLayout get_swapchain_layout() const
{
return swapchain_layout;
}
void set_swapchain_layout(VkImageLayout layout)
{
swapchain_layout = layout;
}
const DeviceAllocation &get_allocation() const
{
return alloc;
}
void disown_image();
void disown_memory_allocation();
DeviceAllocation take_allocation_ownership();
void set_surface_transform(VkSurfaceTransformFlagBitsKHR transform)
{
surface_transform = transform;
if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
{
const VkImageUsageFlags safe_usage_flags =
VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
if ((create_info.usage & ~safe_usage_flags) != 0)
{
LOGW("Using surface transform for non-pure render target image (usage: %u). This can lead to weird results.\n",
create_info.usage);
}
}
}
VkSurfaceTransformFlagBitsKHR get_surface_transform() const
{
return surface_transform;
}
ExternalHandle export_handle();
private:
friend class Util::ObjectPool<Image>;
Image(Device *device, VkImage image, VkImageView default_view, const DeviceAllocation &alloc,
const ImageCreateInfo &info, VkImageViewType view_type);
Device *device;
VkImage image;
ImageViewHandle view;
DeviceAllocation alloc;
ImageCreateInfo create_info;
Layout layout_type = Layout::Optimal;
VkImageLayout swapchain_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkSurfaceTransformFlagBitsKHR surface_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
bool owns_image = true;
bool owns_memory_allocation = true;
};
using ImageHandle = Util::IntrusivePtr<Image>;
class LinearHostImage;
struct LinearHostImageDeleter
{
void operator()(LinearHostImage *image);
};
class Buffer;
enum LinearHostImageCreateInfoFlagBits
{
LINEAR_HOST_IMAGE_HOST_CACHED_BIT = 1 << 0,
LINEAR_HOST_IMAGE_REQUIRE_LINEAR_FILTER_BIT = 1 << 1,
LINEAR_HOST_IMAGE_IGNORE_DEVICE_LOCAL_BIT = 1 << 2
};
using LinearHostImageCreateInfoFlags = uint32_t;
struct LinearHostImageCreateInfo
{
unsigned width = 0;
unsigned height = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
VkImageUsageFlags usage = 0;
VkPipelineStageFlags2 stages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
LinearHostImageCreateInfoFlags flags = 0;
};
// Special image type which supports direct CPU mapping.
// Useful optimization for UMA implementations of Vulkan where we don't necessarily need
// to perform staging copies. It gracefully falls back to staging buffer as needed.
// Only usage flag SAMPLED_BIT is currently supported.
class LinearHostImage : public Util::IntrusivePtrEnabled<LinearHostImage, LinearHostImageDeleter, HandleCounter>
{
public:
friend struct LinearHostImageDeleter;
size_t get_row_pitch_bytes() const;
size_t get_offset() const;
const ImageView &get_view() const;
const Image &get_image() const;
const DeviceAllocation &get_host_visible_allocation() const;
const Buffer &get_host_visible_buffer() const;
bool need_staging_copy() const;
VkPipelineStageFlags2 get_used_pipeline_stages() const;
private:
friend class Util::ObjectPool<LinearHostImage>;
LinearHostImage(Device *device, ImageHandle gpu_image, Util::IntrusivePtr<Buffer> cpu_image,
VkPipelineStageFlags2 stages);
Device *device;
ImageHandle gpu_image;
Util::IntrusivePtr<Buffer> cpu_image;
VkPipelineStageFlags2 stages;
size_t row_pitch;
size_t row_offset;
};
using LinearHostImageHandle = Util::IntrusivePtr<LinearHostImage>;
}

View File

@@ -0,0 +1,108 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "indirect_layout.hpp"
#include "device.hpp"
#include "small_vector.hpp"
namespace Vulkan
{
IndirectLayout::IndirectLayout(Device *device_, const IndirectLayoutToken *tokens, uint32_t num_tokens, uint32_t stride)
: device(device_)
{
VkIndirectCommandsLayoutCreateInfoNV info = { VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_CREATE_INFO_NV };
info.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
info.pStreamStrides = &stride;
info.streamCount = 1;
Util::SmallVector<VkIndirectCommandsLayoutTokenNV, 8> nv_tokens;
nv_tokens.reserve(num_tokens);
for (uint32_t i = 0; i < num_tokens; i++)
{
VkIndirectCommandsLayoutTokenNV token = { VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_TOKEN_NV };
switch (tokens[i].type)
{
case IndirectLayoutToken::Type::VBO:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_NV;
token.vertexBindingUnit = tokens[i].data.vbo.binding;
break;
case IndirectLayoutToken::Type::IBO:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_NV;
break;
case IndirectLayoutToken::Type::PushConstant:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_NV;
token.pushconstantSize = tokens[i].data.push.range;
token.pushconstantOffset = tokens[i].data.push.offset;
token.pushconstantPipelineLayout = tokens[i].data.push.layout->get_layout();
token.pushconstantShaderStageFlags = tokens[i].data.push.layout->get_resource_layout().push_constant_range.stageFlags;
break;
case IndirectLayoutToken::Type::Draw:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_NV;
break;
case IndirectLayoutToken::Type::DrawIndexed:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_NV;
break;
case IndirectLayoutToken::Type::Shader:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_SHADER_GROUP_NV;
break;
case IndirectLayoutToken::Type::MeshTasks:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_NV;
break;
case IndirectLayoutToken::Type::Dispatch:
token.tokenType = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_NV;
info.pipelineBindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
break;
default:
LOGE("Invalid token type.\n");
break;
}
token.offset = tokens[i].offset;
nv_tokens.push_back(token);
}
info.pTokens = nv_tokens.data();
info.tokenCount = num_tokens;
bind_point = info.pipelineBindPoint;
auto &table = device->get_device_table();
if (table.vkCreateIndirectCommandsLayoutNV(device->get_device(), &info, nullptr, &layout) != VK_SUCCESS)
{
LOGE("Failed to create indirect layout.\n");
}
}
IndirectLayout::~IndirectLayout()
{
device->get_device_table().vkDestroyIndirectCommandsLayoutNV(device->get_device(), layout, nullptr);
}
}

View File

@@ -0,0 +1,91 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "vulkan_common.hpp"
#include "cookie.hpp"
namespace Vulkan
{
class Device;
class PipelineLayout;
struct IndirectLayoutToken
{
enum class Type
{
Invalid = 0,
Shader,
PushConstant,
VBO,
IBO,
Draw,
DrawIndexed,
MeshTasks,
Dispatch
};
Type type = Type::Invalid;
uint32_t offset = 0;
union
{
struct
{
uint32_t offset;
uint32_t range;
const PipelineLayout *layout;
} push;
struct
{
uint32_t binding;
} vbo;
} data = {};
};
class IndirectLayout : public HashedObject<IndirectLayout>
{
public:
IndirectLayout(Device *device, const IndirectLayoutToken *token, uint32_t num_tokens, uint32_t stride);
~IndirectLayout();
VkIndirectCommandsLayoutNV get_layout() const
{
return layout;
}
VkPipelineBindPoint get_bind_point() const
{
return bind_point;
}
private:
friend class Device;
Device *device;
VkIndirectCommandsLayoutNV layout;
VkPipelineBindPoint bind_point;
};
}

View File

@@ -0,0 +1,40 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Vulkan
{
constexpr unsigned VULKAN_NUM_DESCRIPTOR_SETS = 4;
constexpr unsigned VULKAN_NUM_DYNAMIC_UBOS = 8; // Vulkan min-spec
constexpr unsigned VULKAN_NUM_BINDINGS = 32;
constexpr unsigned VULKAN_NUM_BINDINGS_BINDLESS_VARYING = 16 * 1024;
constexpr unsigned VULKAN_NUM_ATTACHMENTS = 8;
constexpr unsigned VULKAN_NUM_VERTEX_ATTRIBS = 16;
constexpr unsigned VULKAN_NUM_VERTEX_BUFFERS = 4;
constexpr unsigned VULKAN_PUSH_CONSTANT_SIZE = 128;
constexpr unsigned VULKAN_MAX_UBO_SIZE = 64 * 1024;
constexpr unsigned VULKAN_NUM_USER_SPEC_CONSTANTS = 8;
constexpr unsigned VULKAN_NUM_INTERNAL_SPEC_CONSTANTS = 4;
constexpr unsigned VULKAN_NUM_TOTAL_SPEC_CONSTANTS =
VULKAN_NUM_USER_SPEC_CONSTANTS + VULKAN_NUM_INTERNAL_SPEC_CONSTANTS;
}

View File

@@ -0,0 +1,825 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "memory_allocator.hpp"
#include "timeline_trace_file.hpp"
#include "device.hpp"
#include <algorithm>
#ifndef _WIN32
#include <unistd.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace Vulkan
{
static bool allocation_mode_supports_bda(AllocationMode mode)
{
switch (mode)
{
case AllocationMode::LinearDevice:
case AllocationMode::LinearHostMappable:
case AllocationMode::LinearDeviceHighPriority:
return true;
default:
break;
}
return false;
}
void DeviceAllocation::free_immediate()
{
if (!alloc)
return;
alloc->free(heap, mask);
alloc = nullptr;
base = VK_NULL_HANDLE;
mask = 0;
offset = 0;
}
ExternalHandle DeviceAllocation::export_handle(Device &device)
{
ExternalHandle h;
if (exportable_types == 0)
{
LOGE("Cannot export from this allocation.\n");
return h;
}
auto &table = device.get_device_table();
#ifdef _WIN32
VkMemoryGetWin32HandleInfoKHR handle_info = { VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR };
handle_info.handleType = static_cast<VkExternalMemoryHandleTypeFlagBits>(exportable_types);
handle_info.memory = base;
h.memory_handle_type = handle_info.handleType;
if (table.vkGetMemoryWin32HandleKHR(device.get_device(), &handle_info, &h.handle) != VK_SUCCESS)
{
LOGE("Failed to export memory handle.\n");
h.handle = nullptr;
}
#else
VkMemoryGetFdInfoKHR fd_info = { VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR };
fd_info.handleType = static_cast<VkExternalMemoryHandleTypeFlagBits>(exportable_types);
fd_info.memory = base;
h.memory_handle_type = fd_info.handleType;
if (table.vkGetMemoryFdKHR(device.get_device(), &fd_info, &h.handle) != VK_SUCCESS)
{
LOGE("Failed to export memory handle.\n");
h.handle = -1;
}
#endif
return h;
}
void DeviceAllocation::free_immediate(DeviceAllocator &allocator)
{
if (alloc)
free_immediate();
else if (base)
{
allocator.internal_free_no_recycle(size, memory_type, base);
base = VK_NULL_HANDLE;
}
}
void DeviceAllocation::free_global(DeviceAllocator &allocator, uint32_t size_, uint32_t memory_type_)
{
if (base)
{
allocator.internal_free(size_, memory_type_, mode, base, host_base != nullptr);
base = VK_NULL_HANDLE;
mask = 0;
offset = 0;
}
}
void ClassAllocator::prepare_allocation(DeviceAllocation *alloc, Util::IntrusiveList<MiniHeap>::Iterator heap_itr,
const Util::SuballocationResult &suballoc)
{
auto &heap = *heap_itr;
alloc->heap = heap_itr;
alloc->base = heap.allocation.base;
alloc->offset = suballoc.offset + heap.allocation.offset;
alloc->mask = suballoc.mask;
alloc->size = suballoc.size;
if (heap.allocation.host_base)
alloc->host_base = heap.allocation.host_base + suballoc.offset;
VK_ASSERT(heap.allocation.mode == global_allocator_mode);
VK_ASSERT(heap.allocation.memory_type == memory_type);
alloc->mode = global_allocator_mode;
alloc->memory_type = memory_type;
alloc->alloc = this;
}
static inline bool mode_request_host_mapping(AllocationMode mode)
{
// LinearHostMapping will always work. LinearDevice ones will speculatively work on UMA.
return mode == AllocationMode::LinearHostMappable ||
mode == AllocationMode::LinearDevice ||
mode == AllocationMode::LinearDeviceHighPriority;
}
bool ClassAllocator::allocate_backing_heap(DeviceAllocation *alloc)
{
uint32_t alloc_size = sub_block_size * Util::LegionAllocator::NumSubBlocks;
if (parent)
{
return parent->allocate(alloc_size, alloc);
}
else
{
alloc->offset = 0;
alloc->host_base = nullptr;
alloc->mode = global_allocator_mode;
alloc->memory_type = memory_type;
return global_allocator->internal_allocate(
alloc_size, memory_type, global_allocator_mode, &alloc->base,
mode_request_host_mapping(global_allocator_mode) ? &alloc->host_base : nullptr,
VK_OBJECT_TYPE_DEVICE, 0, nullptr);
}
}
void ClassAllocator::free_backing_heap(DeviceAllocation *allocation)
{
assert(allocation->mode == global_allocator_mode);
assert(allocation->memory_type == memory_type);
// Our mini-heap is completely freed, free to higher level allocator.
if (parent)
allocation->free_immediate();
else
allocation->free_global(*global_allocator, sub_block_size * Util::LegionAllocator::NumSubBlocks, memory_type);
}
bool Allocator::allocate_global(uint32_t size, AllocationMode mode, DeviceAllocation *alloc)
{
// Fall back to global allocation, do not recycle.
alloc->host_base = nullptr;
if (!global_allocator->internal_allocate(
size, memory_type, mode, &alloc->base,
mode_request_host_mapping(mode) ? &alloc->host_base : nullptr,
VK_OBJECT_TYPE_DEVICE, 0, nullptr))
{
return false;
}
alloc->mode = mode;
alloc->alloc = nullptr;
alloc->memory_type = memory_type;
alloc->size = size;
return true;
}
bool Allocator::allocate_dedicated(uint32_t size, AllocationMode mode, DeviceAllocation *alloc,
VkObjectType type, uint64_t object, ExternalHandle *external)
{
// Fall back to global allocation, do not recycle.
alloc->host_base = nullptr;
if (!global_allocator->internal_allocate(
size, memory_type, mode, &alloc->base,
mode_request_host_mapping(mode) ? &alloc->host_base : nullptr,
type, object, external))
{
return false;
}
alloc->mode = mode;
alloc->alloc = nullptr;
alloc->memory_type = memory_type;
alloc->size = size;
// If we imported memory instead, do not allow handle export.
if (external && !(*external))
alloc->exportable_types = external->memory_handle_type;
return true;
}
DeviceAllocation DeviceAllocation::make_imported_allocation(VkDeviceMemory memory, VkDeviceSize size,
uint32_t memory_type)
{
DeviceAllocation alloc = {};
alloc.base = memory;
alloc.offset = 0;
alloc.size = size;
alloc.memory_type = memory_type;
return alloc;
}
bool Allocator::allocate(uint32_t size, uint32_t alignment, AllocationMode mode, DeviceAllocation *alloc)
{
for (auto &c : classes)
{
auto &suballocator = c[unsigned(mode)];
// Find a suitable class to allocate from.
if (size <= suballocator.get_max_allocation_size())
{
if (alignment > suballocator.get_block_alignment())
{
size_t padded_size = size + (alignment - suballocator.get_block_alignment());
if (padded_size <= suballocator.get_max_allocation_size())
size = padded_size;
else
continue;
}
bool ret = suballocator.allocate(size, alloc);
if (ret)
{
uint32_t aligned_offset = (alloc->offset + alignment - 1) & ~(alignment - 1);
if (alloc->host_base)
alloc->host_base += aligned_offset - alloc->offset;
alloc->offset = aligned_offset;
VK_ASSERT(alloc->mode == mode);
VK_ASSERT(alloc->memory_type == memory_type);
}
return ret;
}
}
if (!allocate_global(size, mode, alloc))
return false;
VK_ASSERT(alloc->mode == mode);
VK_ASSERT(alloc->memory_type == memory_type);
return true;
}
Allocator::Allocator(Util::ObjectPool<MiniHeap> &object_pool)
{
for (int i = 0; i < Util::ecast(MemoryClass::Count) - 1; i++)
for (int j = 0; j < Util::ecast(AllocationMode::Count); j++)
classes[i][j].set_parent(&classes[i + 1][j]);
for (auto &c : classes)
for (auto &m : c)
m.set_object_pool(&object_pool);
for (int j = 0; j < Util::ecast(AllocationMode::Count); j++)
{
auto mode = static_cast<AllocationMode>(j);
// 128 chunk
get_class_allocator(MemoryClass::Small, mode).set_sub_block_size(128);
// 4k chunk
get_class_allocator(MemoryClass::Medium, mode).set_sub_block_size(
128 * Util::LegionAllocator::NumSubBlocks); // 4K
// 128k chunk
get_class_allocator(MemoryClass::Large, mode).set_sub_block_size(
128 * Util::LegionAllocator::NumSubBlocks *
Util::LegionAllocator::NumSubBlocks);
// 2M chunk
get_class_allocator(MemoryClass::Huge, mode).set_sub_block_size(
64 * Util::LegionAllocator::NumSubBlocks * Util::LegionAllocator::NumSubBlocks *
Util::LegionAllocator::NumSubBlocks);
}
}
void DeviceAllocator::init(Device *device_)
{
device = device_;
table = &device->get_device_table();
mem_props = device->get_memory_properties();
const auto &props = device->get_gpu_properties();
atom_alignment = props.limits.nonCoherentAtomSize;
heaps.clear();
allocators.clear();
heaps.resize(mem_props.memoryHeapCount);
allocators.reserve(mem_props.memoryTypeCount);
for (unsigned i = 0; i < mem_props.memoryTypeCount; i++)
{
allocators.emplace_back(new Allocator(object_pool));
allocators.back()->set_global_allocator(this, i);
}
HeapBudget budgets[VK_MAX_MEMORY_HEAPS];
get_memory_budget(budgets);
// Figure out if we have a PCI-e BAR heap.
// We need to be very careful with our budget (usually 128 MiB out of 256 MiB) on these heaps
// since they can lead to instability if overused.
VkMemoryPropertyFlags combined_allowed_flags[VK_MAX_MEMORY_HEAPS] = {};
for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++)
{
uint32_t heap_index = mem_props.memoryTypes[i].heapIndex;
combined_allowed_flags[heap_index] |= mem_props.memoryTypes[i].propertyFlags;
}
bool has_host_only_heap = false;
bool has_device_only_heap = false;
VkDeviceSize host_heap_size = 0;
VkDeviceSize device_heap_size = 0;
const VkMemoryPropertyFlags pinned_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
for (uint32_t i = 0; i < mem_props.memoryHeapCount; i++)
{
if ((combined_allowed_flags[i] & pinned_flags) == VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
{
has_host_only_heap = true;
host_heap_size = (std::max)(host_heap_size, mem_props.memoryHeaps[i].size);
}
else if ((combined_allowed_flags[i] & pinned_flags) == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
{
has_device_only_heap = true;
device_heap_size = (std::max)(device_heap_size, mem_props.memoryHeaps[i].size);
}
}
// If we have ReBAR enabled, we generally won't find DEVICE only and HOST only heaps.
// Budget criticalness should only be considered if we have the default small BAR heap (256 MiB).
if (has_host_only_heap && has_device_only_heap)
{
for (uint32_t i = 0; i < mem_props.memoryHeapCount; i++)
{
if ((combined_allowed_flags[i] & pinned_flags) == pinned_flags &&
mem_props.memoryHeaps[i].size < host_heap_size &&
mem_props.memoryHeaps[i].size < device_heap_size)
{
memory_heap_is_budget_critical[i] = true;
}
}
}
}
bool DeviceAllocator::allocate_generic_memory(uint32_t size, uint32_t alignment, AllocationMode mode,
uint32_t memory_type, DeviceAllocation *alloc)
{
return allocators[memory_type]->allocate(size, alignment, mode, alloc);
}
bool DeviceAllocator::allocate_buffer_memory(uint32_t size, uint32_t alignment, AllocationMode mode,
uint32_t memory_type, VkBuffer buffer,
DeviceAllocation *alloc, ExternalHandle *external)
{
if (mode == AllocationMode::External)
{
return allocators[memory_type]->allocate_dedicated(
size, mode, alloc,
VK_OBJECT_TYPE_BUFFER, (uint64_t)buffer, external);
}
else
{
return allocate_generic_memory(size, alignment, mode, memory_type, alloc);
}
}
bool DeviceAllocator::allocate_image_memory(uint32_t size, uint32_t alignment, AllocationMode mode, uint32_t memory_type,
VkImage image, bool force_no_dedicated, DeviceAllocation *alloc,
ExternalHandle *external)
{
if (force_no_dedicated)
{
VK_ASSERT(mode != AllocationMode::External && !external);
return allocate_generic_memory(size, alignment, mode, memory_type, alloc);
}
VkImageMemoryRequirementsInfo2 info = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2 };
info.image = image;
VkMemoryDedicatedRequirements dedicated_req = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS };
VkMemoryRequirements2 mem_req = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 };
mem_req.pNext = &dedicated_req;
table->vkGetImageMemoryRequirements2(device->get_device(), &info, &mem_req);
if (dedicated_req.prefersDedicatedAllocation ||
dedicated_req.requiresDedicatedAllocation ||
mode == AllocationMode::External)
{
return allocators[memory_type]->allocate_dedicated(
size, mode, alloc, VK_OBJECT_TYPE_IMAGE, (uint64_t)image, external);
}
else
{
return allocate_generic_memory(size, alignment, mode, memory_type, alloc);
}
}
void DeviceAllocator::Heap::garbage_collect(Device *device_)
{
auto &table_ = device_->get_device_table();
for (auto &block : blocks)
{
table_.vkFreeMemory(device_->get_device(), block.memory, nullptr);
size -= block.size;
}
blocks.clear();
}
DeviceAllocator::~DeviceAllocator()
{
for (auto &heap : heaps)
heap.garbage_collect(device);
}
void DeviceAllocator::internal_free(uint32_t size, uint32_t memory_type, AllocationMode mode, VkDeviceMemory memory, bool is_mapped)
{
if (is_mapped)
table->vkUnmapMemory(device->get_device(), memory);
auto &heap = heaps[mem_props.memoryTypes[memory_type].heapIndex];
VK_ASSERT(mode != AllocationMode::Count);
heap.blocks.push_back({ memory, size, memory_type, mode });
if (memory_heap_is_budget_critical[mem_props.memoryTypes[memory_type].heapIndex])
heap.garbage_collect(device);
}
void DeviceAllocator::internal_free_no_recycle(uint32_t size, uint32_t memory_type, VkDeviceMemory memory)
{
auto &heap = heaps[mem_props.memoryTypes[memory_type].heapIndex];
table->vkFreeMemory(device->get_device(), memory, nullptr);
heap.size -= size;
}
void DeviceAllocator::garbage_collect()
{
for (auto &heap : heaps)
heap.garbage_collect(device);
}
void *DeviceAllocator::map_memory(const DeviceAllocation &alloc, MemoryAccessFlags flags,
VkDeviceSize offset, VkDeviceSize length)
{
VkDeviceSize base_offset = offset;
// This will only happen if the memory type is device local only, which we cannot possibly map.
if (!alloc.host_base)
return nullptr;
if ((flags & MEMORY_ACCESS_READ_BIT) &&
!(mem_props.memoryTypes[alloc.memory_type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
{
offset += alloc.offset;
VkDeviceSize end_offset = offset + length;
offset &= ~(atom_alignment - 1);
length = end_offset - offset;
VkDeviceSize size = (length + atom_alignment - 1) & ~(atom_alignment - 1);
// Have to invalidate cache here.
const VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, alloc.base, offset, size,
};
table->vkInvalidateMappedMemoryRanges(device->get_device(), 1, &range);
}
return alloc.host_base + base_offset;
}
void DeviceAllocator::unmap_memory(const DeviceAllocation &alloc, MemoryAccessFlags flags,
VkDeviceSize offset, VkDeviceSize length)
{
// This will only happen if the memory type is device local only, which we cannot possibly map.
if (!alloc.host_base)
return;
if ((flags & MEMORY_ACCESS_WRITE_BIT) &&
!(mem_props.memoryTypes[alloc.memory_type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
{
offset += alloc.offset;
VkDeviceSize end_offset = offset + length;
offset &= ~(atom_alignment - 1);
length = end_offset - offset;
VkDeviceSize size = (length + atom_alignment - 1) & ~(atom_alignment - 1);
// Have to flush caches here.
const VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, alloc.base, offset, size,
};
table->vkFlushMappedMemoryRanges(device->get_device(), 1, &range);
}
}
void DeviceAllocator::get_memory_budget_nolock(HeapBudget *heap_budgets)
{
uint32_t num_heaps = mem_props.memoryHeapCount;
if (device->get_device_features().supports_memory_budget)
{
VkPhysicalDeviceMemoryProperties2 props =
{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2 };
VkPhysicalDeviceMemoryBudgetPropertiesEXT budget_props =
{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT };
if (device->get_device_features().supports_memory_budget)
props.pNext = &budget_props;
vkGetPhysicalDeviceMemoryProperties2(device->get_physical_device(), &props);
for (uint32_t i = 0; i < num_heaps; i++)
{
auto &heap = heap_budgets[i];
heap.max_size = mem_props.memoryHeaps[i].size;
heap.budget_size = budget_props.heapBudget[i];
heap.device_usage = budget_props.heapUsage[i];
heap.tracked_usage = heaps[i].size;
}
}
else
{
for (uint32_t i = 0; i < num_heaps; i++)
{
auto &heap = heap_budgets[i];
heap.max_size = mem_props.memoryHeaps[i].size;
// Allow 75%.
heap.budget_size = heap.max_size - (heap.max_size / 4);
heap.tracked_usage = heaps[i].size;
heap.device_usage = heaps[i].size;
}
}
}
void DeviceAllocator::get_memory_budget(HeapBudget *heap_budgets)
{
get_memory_budget_nolock(heap_budgets);
}
bool DeviceAllocator::internal_allocate(
uint32_t size, uint32_t memory_type, AllocationMode mode,
VkDeviceMemory *memory, uint8_t **host_memory,
VkObjectType object_type, uint64_t dedicated_object, ExternalHandle *external)
{
uint32_t heap_index = mem_props.memoryTypes[memory_type].heapIndex;
auto &heap = heaps[heap_index];
// Naive searching is fine here as vkAllocate blocks are *huge* and we won't have many of them.
auto itr = end(heap.blocks);
if (dedicated_object == 0 && !external)
{
itr = find_if(begin(heap.blocks), end(heap.blocks),
[=](const Allocation &alloc) { return size == alloc.size && memory_type == alloc.type && mode == alloc.mode; });
}
bool host_visible = (mem_props.memoryTypes[memory_type].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0 &&
host_memory != nullptr;
// Found previously used block.
if (itr != end(heap.blocks))
{
*memory = itr->memory;
if (host_visible)
{
if (table->vkMapMemory(device->get_device(), itr->memory, 0, VK_WHOLE_SIZE,
0, reinterpret_cast<void **>(host_memory)) != VK_SUCCESS)
return false;
}
heap.blocks.erase(itr);
return true;
}
// Don't bother checking against budgets on external memory.
// It's not very meaningful.
if (!external)
{
HeapBudget budgets[VK_MAX_MEMORY_HEAPS];
get_memory_budget_nolock(budgets);
#ifdef VULKAN_DEBUG
LOGI("Allocating %.1f MiB on heap #%u (mode #%u), before allocating budget: (%.1f MiB / %.1f MiB) [%.1f / %.1f].\n",
double(size) / double(1024 * 1024), heap_index, unsigned(mode),
double(budgets[heap_index].device_usage) / double(1024 * 1024),
double(budgets[heap_index].budget_size) / double(1024 * 1024),
double(budgets[heap_index].tracked_usage) / double(1024 * 1024),
double(budgets[heap_index].max_size) / double(1024 * 1024));
#endif
const auto log_heap_index = [&]()
{
LOGW(" Size: %u MiB.\n", unsigned(size / (1024 * 1024)));
LOGW(" Device usage: %u MiB.\n", unsigned(budgets[heap_index].device_usage / (1024 * 1024)));
LOGW(" Tracked usage: %u MiB.\n", unsigned(budgets[heap_index].tracked_usage / (1024 * 1024)));
LOGW(" Budget size: %u MiB.\n", unsigned(budgets[heap_index].budget_size / (1024 * 1024)));
LOGW(" Max size: %u MiB.\n", unsigned(budgets[heap_index].max_size / (1024 * 1024)));
};
// If we're going to blow out the budget, we should recycle a bit.
if (budgets[heap_index].device_usage + size >= budgets[heap_index].budget_size)
{
LOGW("Will exceed memory budget, cleaning up ...\n");
log_heap_index();
heap.garbage_collect(device);
}
get_memory_budget_nolock(budgets);
if (budgets[heap_index].device_usage + size >= budgets[heap_index].budget_size)
{
LOGW("Even after garbage collection, we will exceed budget ...\n");
if (memory_heap_is_budget_critical[heap_index])
return false;
log_heap_index();
}
}
VkMemoryAllocateInfo info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, size, memory_type };
VkMemoryDedicatedAllocateInfo dedicated = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO };
VkExportMemoryAllocateInfo export_info = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO };
VkMemoryPriorityAllocateInfoEXT priority_info = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT };
VkMemoryAllocateFlagsInfo flags_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO };
#ifdef _WIN32
VkImportMemoryWin32HandleInfoKHR import_info = { VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR };
#else
VkImportMemoryFdInfoKHR import_info = { VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR };
#endif
if (dedicated_object != 0)
{
if (object_type == VK_OBJECT_TYPE_IMAGE)
dedicated.image = (VkImage)dedicated_object;
else if (object_type == VK_OBJECT_TYPE_BUFFER)
dedicated.buffer = (VkBuffer)dedicated_object;
info.pNext = &dedicated;
}
if (external)
{
VK_ASSERT(dedicated_object);
if (bool(*external))
{
import_info.handleType = external->memory_handle_type;
import_info.pNext = info.pNext;
info.pNext = &import_info;
#ifdef _WIN32
import_info.handle = external->handle;
#else
import_info.fd = external->handle;
#endif
}
else
{
export_info.handleTypes = external->memory_handle_type;
export_info.pNext = info.pNext;
info.pNext = &export_info;
}
}
// Don't bother with memory priority on external objects.
if (device->get_device_features().memory_priority_features.memoryPriority && !external)
{
switch (mode)
{
case AllocationMode::LinearDeviceHighPriority:
case AllocationMode::OptimalRenderTarget:
priority_info.priority = 1.0f;
break;
case AllocationMode::LinearDevice:
case AllocationMode::OptimalResource:
priority_info.priority = 0.5f;
break;
default:
priority_info.priority = 0.0f;
break;
}
priority_info.pNext = info.pNext;
info.pNext = &priority_info;
}
if (device->get_device_features().vk12_features.bufferDeviceAddress &&
allocation_mode_supports_bda(mode))
{
flags_info.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
flags_info.pNext = info.pNext;
info.pNext = &flags_info;
}
VkDeviceMemory device_memory;
VkResult res;
{
GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file, "vkAllocateMemory");
res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory);
}
// If we're importing, make sure we consume the native handle.
if (external && bool(*external) &&
ExternalHandle::memory_handle_type_imports_by_reference(external->memory_handle_type))
{
#ifdef _WIN32
::CloseHandle(external->handle);
#else
::close(external->handle);
#endif
}
if (res == VK_SUCCESS)
{
heap.size += size;
*memory = device_memory;
if (host_visible)
{
if (table->vkMapMemory(device->get_device(), device_memory, 0, VK_WHOLE_SIZE,
0, reinterpret_cast<void **>(host_memory)) != VK_SUCCESS)
{
table->vkFreeMemory(device->get_device(), device_memory, nullptr);
heap.size -= size;
return false;
}
}
return true;
}
else
{
// Look through our heap and see if there are blocks of other types we can free.
auto block_itr = begin(heap.blocks);
while (res != VK_SUCCESS && itr != end(heap.blocks))
{
table->vkFreeMemory(device->get_device(), block_itr->memory, nullptr);
heap.size -= block_itr->size;
{
GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file,
"vkAllocateMemory");
res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory);
}
++block_itr;
}
heap.blocks.erase(begin(heap.blocks), block_itr);
if (res == VK_SUCCESS)
{
heap.size += size;
*memory = device_memory;
if (host_visible)
{
if (table->vkMapMemory(device->get_device(), device_memory, 0, size, 0, reinterpret_cast<void **>(host_memory)) !=
VK_SUCCESS)
{
table->vkFreeMemory(device->get_device(), device_memory, nullptr);
heap.size -= size;
return false;
}
}
return true;
}
else
return false;
}
}
DeviceAllocationOwner::DeviceAllocationOwner(Device *device_, const DeviceAllocation &alloc_)
: device(device_), alloc(alloc_)
{
}
DeviceAllocationOwner::~DeviceAllocationOwner()
{
if (alloc.get_memory())
device->free_memory(alloc);
}
const DeviceAllocation &DeviceAllocationOwner::get_allocation() const
{
return alloc;
}
void DeviceAllocationDeleter::operator()(DeviceAllocationOwner *owner)
{
owner->device->handle_pool.allocations.free(owner);
}
}

View File

@@ -0,0 +1,300 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "intrusive.hpp"
#include "object_pool.hpp"
#include "intrusive_list.hpp"
#include "vulkan_headers.hpp"
#include "logging.hpp"
#include "bitops.hpp"
#include "enum_cast.hpp"
#include "vulkan_common.hpp"
#include "arena_allocator.hpp"
#include <assert.h>
#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <vector>
namespace Vulkan
{
class Device;
enum class MemoryClass : uint8_t
{
Small = 0,
Medium,
Large,
Huge,
Count
};
enum class AllocationMode : uint8_t
{
LinearHostMappable = 0,
LinearDevice,
LinearDeviceHighPriority,
OptimalResource,
OptimalRenderTarget,
External,
Count
};
enum MemoryAccessFlag : uint32_t
{
MEMORY_ACCESS_WRITE_BIT = 1,
MEMORY_ACCESS_READ_BIT = 2,
MEMORY_ACCESS_READ_WRITE_BIT = MEMORY_ACCESS_WRITE_BIT | MEMORY_ACCESS_READ_BIT
};
using MemoryAccessFlags = uint32_t;
struct DeviceAllocation;
class DeviceAllocator;
class ClassAllocator;
class DeviceAllocator;
class Allocator;
class Device;
using MiniHeap = Util::LegionHeap<DeviceAllocation>;
struct DeviceAllocation
{
friend class Util::ArenaAllocator<ClassAllocator, DeviceAllocation>;
friend class ClassAllocator;
friend class Allocator;
friend class DeviceAllocator;
friend class Device;
friend class ImageResourceHolder;
public:
inline VkDeviceMemory get_memory() const
{
return base;
}
inline bool allocation_is_global() const
{
return !alloc && base;
}
inline uint32_t get_offset() const
{
return offset;
}
inline uint32_t get_size() const
{
return size;
}
inline uint32_t get_mask() const
{
return mask;
}
inline bool is_host_allocation() const
{
return host_base != nullptr;
}
static DeviceAllocation make_imported_allocation(VkDeviceMemory memory, VkDeviceSize size, uint32_t memory_type);
ExternalHandle export_handle(Device &device);
private:
VkDeviceMemory base = VK_NULL_HANDLE;
uint8_t *host_base = nullptr;
ClassAllocator *alloc = nullptr;
Util::IntrusiveList<MiniHeap>::Iterator heap = {};
uint32_t offset = 0;
uint32_t mask = 0;
uint32_t size = 0;
VkExternalMemoryHandleTypeFlags exportable_types = 0;
AllocationMode mode = AllocationMode::Count;
uint8_t memory_type = 0;
void free_global(DeviceAllocator &allocator, uint32_t size, uint32_t memory_type);
void free_immediate();
void free_immediate(DeviceAllocator &allocator);
};
class DeviceAllocationOwner;
struct DeviceAllocationDeleter
{
void operator()(DeviceAllocationOwner *owner);
};
class DeviceAllocationOwner : public Util::IntrusivePtrEnabled<DeviceAllocationOwner, DeviceAllocationDeleter, HandleCounter>
{
public:
friend class Util::ObjectPool<DeviceAllocationOwner>;
friend struct DeviceAllocationDeleter;
~DeviceAllocationOwner();
const DeviceAllocation &get_allocation() const;
private:
DeviceAllocationOwner(Device *device, const DeviceAllocation &alloc);
Device *device;
DeviceAllocation alloc;
};
using DeviceAllocationOwnerHandle = Util::IntrusivePtr<DeviceAllocationOwner>;
struct MemoryAllocateInfo
{
VkMemoryRequirements requirements = {};
VkMemoryPropertyFlags required_properties = 0;
AllocationMode mode = {};
};
class ClassAllocator : public Util::ArenaAllocator<ClassAllocator, DeviceAllocation>
{
public:
friend class Util::ArenaAllocator<ClassAllocator, DeviceAllocation>;
inline void set_global_allocator(DeviceAllocator *allocator, AllocationMode mode, uint32_t memory_type_)
{
global_allocator = allocator;
global_allocator_mode = mode;
memory_type = memory_type_;
}
inline void set_parent(ClassAllocator *allocator)
{
parent = allocator;
}
private:
ClassAllocator *parent = nullptr;
uint32_t memory_type = 0;
DeviceAllocator *global_allocator = nullptr;
AllocationMode global_allocator_mode = AllocationMode::Count;
// Implements curious recurring template pattern calls.
bool allocate_backing_heap(DeviceAllocation *allocation);
void free_backing_heap(DeviceAllocation *allocation);
void prepare_allocation(DeviceAllocation *allocation, Util::IntrusiveList<MiniHeap>::Iterator heap_itr,
const Util::SuballocationResult &suballoc);
};
class Allocator
{
public:
explicit Allocator(Util::ObjectPool<MiniHeap> &object_pool);
void operator=(const Allocator &) = delete;
Allocator(const Allocator &) = delete;
bool allocate(uint32_t size, uint32_t alignment, AllocationMode mode, DeviceAllocation *alloc);
bool allocate_global(uint32_t size, AllocationMode mode, DeviceAllocation *alloc);
bool allocate_dedicated(uint32_t size, AllocationMode mode, DeviceAllocation *alloc,
VkObjectType object_type, uint64_t object, ExternalHandle *external);
inline ClassAllocator &get_class_allocator(MemoryClass clazz, AllocationMode mode)
{
return classes[unsigned(clazz)][unsigned(mode)];
}
static void free(DeviceAllocation *alloc)
{
alloc->free_immediate();
}
void set_global_allocator(DeviceAllocator *allocator, uint32_t memory_type_)
{
memory_type = memory_type_;
for (auto &sub : classes)
for (int i = 0; i < Util::ecast(AllocationMode::Count); i++)
sub[i].set_global_allocator(allocator, AllocationMode(i), memory_type);
global_allocator = allocator;
}
private:
ClassAllocator classes[Util::ecast(MemoryClass::Count)][Util::ecast(AllocationMode::Count)];
DeviceAllocator *global_allocator = nullptr;
uint32_t memory_type = 0;
};
struct HeapBudget
{
VkDeviceSize max_size;
VkDeviceSize budget_size;
VkDeviceSize tracked_usage;
VkDeviceSize device_usage;
};
class DeviceAllocator
{
public:
void init(Device *device);
~DeviceAllocator();
bool allocate_generic_memory(uint32_t size, uint32_t alignment, AllocationMode mode, uint32_t memory_type,
DeviceAllocation *alloc);
bool allocate_buffer_memory(uint32_t size, uint32_t alignment, AllocationMode mode, uint32_t memory_type,
VkBuffer buffer, DeviceAllocation *alloc, ExternalHandle *external);
bool allocate_image_memory(uint32_t size, uint32_t alignment, AllocationMode mode, uint32_t memory_type,
VkImage image, bool force_no_dedicated, DeviceAllocation *alloc, ExternalHandle *external);
void garbage_collect();
void *map_memory(const DeviceAllocation &alloc, MemoryAccessFlags flags, VkDeviceSize offset, VkDeviceSize length);
void unmap_memory(const DeviceAllocation &alloc, MemoryAccessFlags flags, VkDeviceSize offset, VkDeviceSize length);
void get_memory_budget(HeapBudget *heaps);
bool internal_allocate(uint32_t size, uint32_t memory_type, AllocationMode mode,
VkDeviceMemory *memory, uint8_t **host_memory,
VkObjectType object_type, uint64_t dedicated_object, ExternalHandle *external);
void internal_free(uint32_t size, uint32_t memory_type, AllocationMode mode, VkDeviceMemory memory, bool is_mapped);
void internal_free_no_recycle(uint32_t size, uint32_t memory_type, VkDeviceMemory memory);
private:
Util::ObjectPool<MiniHeap> object_pool;
std::vector<std::unique_ptr<Allocator>> allocators;
Device *device = nullptr;
const VolkDeviceTable *table = nullptr;
VkPhysicalDeviceMemoryProperties mem_props;
VkDeviceSize atom_alignment = 1;
struct Allocation
{
VkDeviceMemory memory;
uint32_t size;
uint32_t type;
AllocationMode mode;
};
struct Heap
{
uint64_t size = 0;
std::vector<Allocation> blocks;
void garbage_collect(Device *device);
};
std::vector<Heap> heaps;
bool memory_heap_is_budget_critical[VK_MAX_MEMORY_HEAPS] = {};
void get_memory_budget_nolock(HeapBudget *heaps);
};
}

View File

@@ -0,0 +1,43 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "pipeline_event.hpp"
#include "device.hpp"
namespace Vulkan
{
EventHolder::~EventHolder()
{
if (event)
{
if (internal_sync)
device->destroy_event_nolock(event);
else
device->destroy_event(event);
}
}
void EventHolderDeleter::operator()(Vulkan::EventHolder *event)
{
event->device->handle_pool.events.free(event);
}
}

View File

@@ -0,0 +1,67 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "vulkan_common.hpp"
#include "cookie.hpp"
#include "object_pool.hpp"
namespace Vulkan
{
class Device;
class EventHolder;
struct EventHolderDeleter
{
void operator()(EventHolder *event);
};
class EventHolder : public Util::IntrusivePtrEnabled<EventHolder, EventHolderDeleter, HandleCounter>,
public InternalSyncEnabled
{
public:
friend struct EventHolderDeleter;
~EventHolder();
const VkEvent &get_event() const
{
return event;
}
private:
friend class Util::ObjectPool<EventHolder>;
EventHolder(Device *device_, VkEvent event_)
: device(device_)
, event(event_)
{
}
Device *device;
VkEvent event;
};
using PipelineEvent = Util::IntrusivePtr<EventHolder>;
}

View File

@@ -0,0 +1,528 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "query_pool.hpp"
#include "device.hpp"
#include <utility>
namespace Vulkan
{
static const char *storage_to_str(VkPerformanceCounterStorageKHR storage)
{
switch (storage)
{
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR:
return "float32";
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR:
return "float64";
case VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR:
return "int32";
case VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR:
return "int64";
case VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR:
return "uint32";
case VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR:
return "uint64";
default:
return "???";
}
}
static const char *scope_to_str(VkPerformanceCounterScopeKHR scope)
{
switch (scope)
{
case VK_QUERY_SCOPE_COMMAND_BUFFER_KHR:
return "command buffer";
case VK_QUERY_SCOPE_RENDER_PASS_KHR:
return "render pass";
case VK_QUERY_SCOPE_COMMAND_KHR:
return "command";
default:
return "???";
}
}
static const char *unit_to_str(VkPerformanceCounterUnitKHR unit)
{
switch (unit)
{
case VK_PERFORMANCE_COUNTER_UNIT_AMPS_KHR:
return "A";
case VK_PERFORMANCE_COUNTER_UNIT_BYTES_KHR:
return "bytes";
case VK_PERFORMANCE_COUNTER_UNIT_BYTES_PER_SECOND_KHR:
return "bytes / second";
case VK_PERFORMANCE_COUNTER_UNIT_CYCLES_KHR:
return "cycles";
case VK_PERFORMANCE_COUNTER_UNIT_GENERIC_KHR:
return "units";
case VK_PERFORMANCE_COUNTER_UNIT_HERTZ_KHR:
return "Hz";
case VK_PERFORMANCE_COUNTER_UNIT_KELVIN_KHR:
return "K";
case VK_PERFORMANCE_COUNTER_UNIT_NANOSECONDS_KHR:
return "ns";
case VK_PERFORMANCE_COUNTER_UNIT_PERCENTAGE_KHR:
return "%";
case VK_PERFORMANCE_COUNTER_UNIT_VOLTS_KHR:
return "V";
case VK_PERFORMANCE_COUNTER_UNIT_WATTS_KHR:
return "W";
default:
return "???";
}
}
void PerformanceQueryPool::log_available_counters(const VkPerformanceCounterKHR *counters,
const VkPerformanceCounterDescriptionKHR *descs,
uint32_t count)
{
for (uint32_t i = 0; i < count; i++)
{
LOGI(" %s: %s\n", descs[i].name, descs[i].description);
LOGI(" Storage: %s\n", storage_to_str(counters[i].storage));
LOGI(" Scope: %s\n", scope_to_str(counters[i].scope));
LOGI(" Unit: %s\n", unit_to_str(counters[i].unit));
}
}
void PerformanceQueryPool::init_device(Device *device_, uint32_t queue_family_index_)
{
device = device_;
queue_family_index = queue_family_index_;
if (!device->get_device_features().performance_query_features.performanceCounterQueryPools)
return;
uint32_t num_counters = 0;
if (vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(
device->get_physical_device(),
queue_family_index,
&num_counters,
nullptr, nullptr) != VK_SUCCESS)
{
LOGE("Failed to enumerate performance counters.\n");
return;
}
counters.resize(num_counters, { VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_KHR });
counter_descriptions.resize(num_counters, { VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_DESCRIPTION_KHR });
if (vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(
device->get_physical_device(),
queue_family_index,
&num_counters,
counters.data(), counter_descriptions.data()) != VK_SUCCESS)
{
LOGE("Failed to enumerate performance counters.\n");
return;
}
}
PerformanceQueryPool::~PerformanceQueryPool()
{
if (pool)
device->get_device_table().vkDestroyQueryPool(device->get_device(), pool, nullptr);
}
void PerformanceQueryPool::begin_command_buffer(VkCommandBuffer cmd)
{
if (!pool)
return;
auto &table = device->get_device_table();
table.vkResetQueryPoolEXT(device->get_device(), pool, 0, 1);
table.vkCmdBeginQuery(cmd, pool, 0, 0);
VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
table.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &barrier, 0, nullptr, 0, nullptr);
}
void PerformanceQueryPool::end_command_buffer(VkCommandBuffer cmd)
{
if (!pool)
return;
auto &table = device->get_device_table();
VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
table.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &barrier, 0, nullptr, 0, nullptr);
table.vkCmdEndQuery(cmd, pool, 0);
}
void PerformanceQueryPool::report()
{
if (pool == VK_NULL_HANDLE)
{
LOGE("No query pool is set up.\n");
return;
}
auto &table = device->get_device_table();
if (table.vkGetQueryPoolResults(device->get_device(), pool,
0, 1,
results.size() * sizeof(VkPerformanceCounterResultKHR),
results.data(),
sizeof(VkPerformanceCounterResultKHR),
VK_QUERY_RESULT_WAIT_BIT) != VK_SUCCESS)
{
LOGE("Getting performance counters did not succeed.\n");
}
size_t num_counters = results.size();
LOGI("\n=== Profiling result ===\n");
for (size_t i = 0; i < num_counters; i++)
{
auto &counter = counters[active_indices[i]];
auto &desc = counter_descriptions[active_indices[i]];
switch (counter.storage)
{
case VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR:
LOGI(" %s (%s): %d %s\n", desc.name, desc.description, results[i].int32, unit_to_str(counter.unit));
break;
case VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR:
LOGI(" %s (%s): %lld %s\n", desc.name, desc.description, static_cast<long long>(results[i].int64), unit_to_str(counter.unit));
break;
case VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR:
LOGI(" %s (%s): %u %s\n", desc.name, desc.description, results[i].uint32, unit_to_str(counter.unit));
break;
case VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR:
LOGI(" %s (%s): %llu %s\n", desc.name, desc.description, static_cast<long long>(results[i].uint64), unit_to_str(counter.unit));
break;
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR:
LOGI(" %s (%s): %g %s\n", desc.name, desc.description, results[i].float32, unit_to_str(counter.unit));
break;
case VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR:
LOGI(" %s (%s): %g %s\n", desc.name, desc.description, results[i].float64, unit_to_str(counter.unit));
break;
default:
break;
}
}
LOGI("================================\n\n");
}
uint32_t PerformanceQueryPool::get_num_counters() const
{
return uint32_t(counters.size());
}
const VkPerformanceCounterKHR *PerformanceQueryPool::get_available_counters() const
{
return counters.data();
}
const VkPerformanceCounterDescriptionKHR *PerformanceQueryPool::get_available_counter_descs() const
{
return counter_descriptions.data();
}
bool PerformanceQueryPool::init_counters(const std::vector<std::string> &counter_names)
{
if (!device->get_device_features().performance_query_features.performanceCounterQueryPools)
{
LOGE("Device does not support VK_KHR_performance_query.\n");
return false;
}
if (!device->get_device_features().vk12_features.hostQueryReset)
{
LOGE("Device does not support host query reset.\n");
return false;
}
auto &table = device->get_device_table();
if (pool)
table.vkDestroyQueryPool(device->get_device(), pool, nullptr);
pool = VK_NULL_HANDLE;
VkQueryPoolPerformanceCreateInfoKHR performance_info = { VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_CREATE_INFO_KHR };
VkQueryPoolCreateInfo info = { VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
info.pNext = &performance_info;
info.queryType = VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR;
info.queryCount = 1;
active_indices.clear();
for (auto &name : counter_names)
{
auto itr = find_if(begin(counter_descriptions), end(counter_descriptions), [&](const VkPerformanceCounterDescriptionKHR &desc) {
return name == desc.name;
});
if (itr != end(counter_descriptions))
{
LOGI("Found counter %s: %s\n", itr->name, itr->description);
active_indices.push_back(itr - begin(counter_descriptions));
}
}
if (active_indices.empty())
{
LOGW("No performance counters were enabled.\n");
return false;
}
performance_info.queueFamilyIndex = queue_family_index;
performance_info.counterIndexCount = active_indices.size();
performance_info.pCounterIndices = active_indices.data();
results.resize(active_indices.size());
uint32_t num_passes = 0;
vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(device->get_physical_device(),
&performance_info, &num_passes);
if (num_passes != 1)
{
LOGE("Implementation requires %u passes to query performance counters. Cannot create query pool.\n",
num_passes);
return false;
}
if (table.vkCreateQueryPool(device->get_device(), &info, nullptr, &pool) != VK_SUCCESS)
{
LOGE("Failed to create performance query pool.\n");
return false;
}
return true;
}
QueryPool::QueryPool(Device *device_)
: device(device_)
, table(device_->get_device_table())
{
supports_timestamp = device->get_gpu_properties().limits.timestampComputeAndGraphics &&
device->get_device_features().vk12_features.hostQueryReset;
// Ignore timestampValidBits and friends for now.
if (supports_timestamp)
add_pool();
}
QueryPool::~QueryPool()
{
for (auto &pool : pools)
table.vkDestroyQueryPool(device->get_device(), pool.pool, nullptr);
}
void QueryPool::begin()
{
for (unsigned i = 0; i <= pool_index; i++)
{
if (i >= pools.size())
continue;
auto &pool = pools[i];
if (pool.index == 0)
continue;
table.vkGetQueryPoolResults(device->get_device(), pool.pool,
0, pool.index,
pool.index * sizeof(uint64_t),
pool.query_results.data(),
sizeof(uint64_t),
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
for (unsigned j = 0; j < pool.index; j++)
pool.cookies[j]->signal_timestamp_ticks(pool.query_results[j]);
table.vkResetQueryPool(device->get_device(), pool.pool, 0, pool.index);
}
pool_index = 0;
for (auto &pool : pools)
pool.index = 0;
}
void QueryPool::add_pool()
{
VkQueryPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
pool_info.queryType = VK_QUERY_TYPE_TIMESTAMP;
pool_info.queryCount = 64;
Pool pool;
table.vkCreateQueryPool(device->get_device(), &pool_info, nullptr, &pool.pool);
pool.size = pool_info.queryCount;
pool.index = 0;
pool.query_results.resize(pool.size);
pool.cookies.resize(pool.size);
table.vkResetQueryPool(device->get_device(), pool.pool, 0, pool.size);
pools.push_back(std::move(pool));
}
QueryPoolHandle QueryPool::write_timestamp(VkCommandBuffer cmd, VkPipelineStageFlags2 stage)
{
if (!supports_timestamp)
{
LOGI("Timestamps are not supported on this implementation.\n");
return {};
}
VK_ASSERT((stage & (stage - 1)) == 0);
if (pools[pool_index].index >= pools[pool_index].size)
pool_index++;
if (pool_index >= pools.size())
add_pool();
auto &pool = pools[pool_index];
auto cookie = QueryPoolHandle(device->handle_pool.query.allocate(device, true));
pool.cookies[pool.index] = cookie;
if (device->get_device_features().vk13_features.synchronization2)
table.vkCmdWriteTimestamp2(cmd, stage, pool.pool, pool.index);
else
{
table.vkCmdWriteTimestamp(cmd, static_cast<VkPipelineStageFlagBits>(convert_vk_src_stage2(stage)),
pool.pool, pool.index);
}
pool.index++;
return cookie;
}
void QueryPoolResultDeleter::operator()(QueryPoolResult *query)
{
query->device->handle_pool.query.free(query);
}
void TimestampInterval::mark_end_of_frame_context()
{
if (total_time > 0.0)
total_frame_iterations++;
}
uint64_t TimestampInterval::get_total_accumulations() const
{
return total_accumulations;
}
uint64_t TimestampInterval::get_total_frame_iterations() const
{
return total_frame_iterations;
}
double TimestampInterval::get_total_time() const
{
return total_time;
}
void TimestampInterval::accumulate_time(double t)
{
total_time += t;
total_accumulations++;
}
double TimestampInterval::get_time_per_iteration() const
{
if (total_frame_iterations)
return total_time / double(total_frame_iterations);
else
return 0.0;
}
double TimestampInterval::get_time_per_accumulation() const
{
if (total_accumulations)
return total_time / double(total_accumulations);
else
return 0.0;
}
const std::string &TimestampInterval::get_tag() const
{
return tag;
}
void TimestampInterval::reset()
{
total_time = 0.0;
total_accumulations = 0;
total_frame_iterations = 0;
}
TimestampInterval::TimestampInterval(std::string tag_)
: tag(std::move(tag_))
{
}
TimestampInterval *TimestampIntervalManager::get_timestamp_tag(const char *tag)
{
Util::Hasher h;
h.string(tag);
return timestamps.emplace_yield(h.get(), tag);
}
void TimestampIntervalManager::mark_end_of_frame_context()
{
for (auto &timestamp : timestamps)
timestamp.mark_end_of_frame_context();
}
void TimestampIntervalManager::reset()
{
for (auto &timestamp : timestamps)
timestamp.reset();
}
void TimestampIntervalManager::log_simple(const TimestampIntervalReportCallback &func) const
{
for (auto &timestamp : timestamps)
{
if (timestamp.get_total_frame_iterations())
{
TimestampIntervalReport report = {};
report.time_per_accumulation = timestamp.get_time_per_accumulation();
report.time_per_frame_context = timestamp.get_time_per_iteration();
report.accumulations_per_frame_context =
double(timestamp.get_total_accumulations()) / double(timestamp.get_total_frame_iterations());
if (func)
{
func(timestamp.get_tag(), report);
}
else
{
LOGI("Timestamp tag report: %s\n", timestamp.get_tag().c_str());
LOGI(" %.3f ms / iteration\n", 1000.0 * report.time_per_accumulation);
LOGI(" %.3f ms / frame context\n", 1000.0 * report.time_per_frame_context);
LOGI(" %.3f iterations / frame context\n", report.accumulations_per_frame_context);
}
}
}
}
}

View File

@@ -0,0 +1,186 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "vulkan_common.hpp"
#include "object_pool.hpp"
#include <functional>
namespace Vulkan
{
class Device;
class PerformanceQueryPool
{
public:
void init_device(Device *device, uint32_t queue_family_index);
~PerformanceQueryPool();
bool init_counters(const std::vector<std::string> &enable_counter_names);
void begin_command_buffer(VkCommandBuffer cmd);
void end_command_buffer(VkCommandBuffer cmd);
void report();
uint32_t get_num_counters() const;
const VkPerformanceCounterKHR *get_available_counters() const;
const VkPerformanceCounterDescriptionKHR *get_available_counter_descs() const;
static void log_available_counters(const VkPerformanceCounterKHR *counters,
const VkPerformanceCounterDescriptionKHR *descs,
uint32_t count);
private:
Device *device = nullptr;
uint32_t queue_family_index = 0;
VkQueryPool pool = VK_NULL_HANDLE;
std::vector<VkPerformanceCounterResultKHR> results;
std::vector<VkPerformanceCounterKHR> counters;
std::vector<VkPerformanceCounterDescriptionKHR> counter_descriptions;
std::vector<uint32_t> active_indices;
};
class QueryPoolResult;
struct QueryPoolResultDeleter
{
void operator()(QueryPoolResult *query);
};
class QueryPoolResult : public Util::IntrusivePtrEnabled<QueryPoolResult, QueryPoolResultDeleter, HandleCounter>
{
public:
friend struct QueryPoolResultDeleter;
void signal_timestamp_ticks(uint64_t ticks)
{
timestamp_ticks = ticks;
has_timestamp = true;
}
uint64_t get_timestamp_ticks() const
{
return timestamp_ticks;
}
bool is_signalled() const
{
return has_timestamp;
}
bool is_device_timebase() const
{
return device_timebase;
}
private:
friend class Util::ObjectPool<QueryPoolResult>;
explicit QueryPoolResult(Device *device_, bool device_timebase_)
: device(device_), device_timebase(device_timebase_)
{}
Device *device;
uint64_t timestamp_ticks = 0;
bool has_timestamp = false;
bool device_timebase = false;
};
using QueryPoolHandle = Util::IntrusivePtr<QueryPoolResult>;
class QueryPool
{
public:
explicit QueryPool(Device *device);
~QueryPool();
void begin();
QueryPoolHandle write_timestamp(VkCommandBuffer cmd, VkPipelineStageFlags2 stage);
private:
Device *device;
const VolkDeviceTable &table;
struct Pool
{
VkQueryPool pool = VK_NULL_HANDLE;
std::vector<uint64_t> query_results;
std::vector<QueryPoolHandle> cookies;
unsigned index = 0;
unsigned size = 0;
};
std::vector<Pool> pools;
unsigned pool_index = 0;
void add_pool();
bool supports_timestamp = false;
};
class TimestampInterval : public Util::IntrusiveHashMapEnabled<TimestampInterval>
{
public:
explicit TimestampInterval(std::string tag);
void accumulate_time(double t);
double get_time_per_iteration() const;
double get_time_per_accumulation() const;
const std::string &get_tag() const;
void mark_end_of_frame_context();
double get_total_time() const;
uint64_t get_total_frame_iterations() const;
uint64_t get_total_accumulations() const;
void reset();
private:
std::string tag;
double total_time = 0.0;
uint64_t total_frame_iterations = 0;
uint64_t total_accumulations = 0;
};
struct TimestampIntervalReport
{
double time_per_accumulation;
double time_per_frame_context;
double accumulations_per_frame_context;
};
using TimestampIntervalReportCallback = std::function<void (const std::string &, const TimestampIntervalReport &)>;
class TimestampIntervalManager
{
public:
TimestampInterval *get_timestamp_tag(const char *tag);
void mark_end_of_frame_context();
void reset();
void log_simple(const TimestampIntervalReportCallback &func = {}) const;
private:
Util::IntrusiveHashMap<TimestampInterval> timestamps;
};
}

View File

@@ -0,0 +1,51 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Vulkan
{
struct ImplementationQuirks
{
bool instance_deferred_lights = true;
bool merge_subpasses = true;
bool use_transient_color = true;
bool use_transient_depth_stencil = true;
bool queue_wait_on_submission = false;
bool use_async_compute_post = true;
bool render_graph_force_single_queue = false;
bool force_no_subgroups = false;
bool force_no_subgroup_shuffle = false;
bool force_no_subgroup_size_control = false;
static ImplementationQuirks &get();
};
struct ImplementationWorkarounds
{
bool emulate_event_as_pipeline_barrier = false;
bool broken_pipeline_cache_control = false;
bool force_host_cached = false;
bool force_sync1_access = false;
bool broken_push_descriptors = false;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,270 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "cookie.hpp"
#include "hash.hpp"
#include "image.hpp"
#include "intrusive.hpp"
#include "limits.hpp"
#include "object_pool.hpp"
#include "temporary_hashmap.hpp"
#include "vulkan_headers.hpp"
namespace Vulkan
{
enum RenderPassOp
{
RENDER_PASS_OP_CLEAR_DEPTH_STENCIL_BIT = 1 << 0,
RENDER_PASS_OP_LOAD_DEPTH_STENCIL_BIT = 1 << 1,
RENDER_PASS_OP_STORE_DEPTH_STENCIL_BIT = 1 << 2,
RENDER_PASS_OP_DEPTH_STENCIL_READ_ONLY_BIT = 1 << 3,
RENDER_PASS_OP_ENABLE_TRANSIENT_STORE_BIT = 1 << 4,
RENDER_PASS_OP_ENABLE_TRANSIENT_LOAD_BIT = 1 << 5,
RENDER_PASS_OP_PRESERVE_DEPTH_STENCIL_BIT = 1 << 6
};
using RenderPassOpFlags = uint32_t;
class ImageView;
struct RenderPassInfo
{
const ImageView *color_attachments[VULKAN_NUM_ATTACHMENTS];
const ImageView *depth_stencil = nullptr;
unsigned num_color_attachments = 0;
RenderPassOpFlags op_flags = 0;
uint32_t clear_attachments = 0;
uint32_t load_attachments = 0;
uint32_t store_attachments = 0;
uint32_t base_layer = 0;
uint32_t num_layers = 1;
// Render area will be clipped to the actual framebuffer.
VkRect2D render_area = { { 0, 0 }, { UINT32_MAX, UINT32_MAX } };
VkClearColorValue clear_color[VULKAN_NUM_ATTACHMENTS] = {};
VkClearDepthStencilValue clear_depth_stencil = { 1.0f, 0 };
enum class DepthStencil
{
None,
ReadOnly,
ReadWrite
};
struct Subpass
{
uint32_t color_attachments[VULKAN_NUM_ATTACHMENTS];
uint32_t input_attachments[VULKAN_NUM_ATTACHMENTS];
uint32_t resolve_attachments[VULKAN_NUM_ATTACHMENTS];
unsigned num_color_attachments = 0;
unsigned num_input_attachments = 0;
unsigned num_resolve_attachments = 0;
DepthStencil depth_stencil_mode = DepthStencil::ReadWrite;
};
// If 0/nullptr, assume a default subpass.
const Subpass *subpasses = nullptr;
unsigned num_subpasses = 0;
};
class RenderPass : public HashedObject<RenderPass>, public NoCopyNoMove
{
public:
struct SubpassInfo
{
VkAttachmentReference2 color_attachments[VULKAN_NUM_ATTACHMENTS];
unsigned num_color_attachments;
VkAttachmentReference2 input_attachments[VULKAN_NUM_ATTACHMENTS];
unsigned num_input_attachments;
VkAttachmentReference2 depth_stencil_attachment;
unsigned samples;
};
RenderPass(Util::Hash hash, Device *device, const RenderPassInfo &info);
RenderPass(Util::Hash hash, Device *device, const VkRenderPassCreateInfo2 &create_info);
~RenderPass();
unsigned get_num_subpasses() const
{
return unsigned(subpasses_info.size());
}
VkRenderPass get_render_pass() const
{
return render_pass;
}
uint32_t get_sample_count(unsigned subpass) const
{
VK_ASSERT(subpass < subpasses_info.size());
return subpasses_info[subpass].samples;
}
unsigned get_num_color_attachments(unsigned subpass) const
{
VK_ASSERT(subpass < subpasses_info.size());
return subpasses_info[subpass].num_color_attachments;
}
unsigned get_num_input_attachments(unsigned subpass) const
{
VK_ASSERT(subpass < subpasses_info.size());
return subpasses_info[subpass].num_input_attachments;
}
const VkAttachmentReference2 &get_color_attachment(unsigned subpass, unsigned index) const
{
VK_ASSERT(subpass < subpasses_info.size());
VK_ASSERT(index < subpasses_info[subpass].num_color_attachments);
return subpasses_info[subpass].color_attachments[index];
}
const VkAttachmentReference2 &get_input_attachment(unsigned subpass, unsigned index) const
{
VK_ASSERT(subpass < subpasses_info.size());
VK_ASSERT(index < subpasses_info[subpass].num_input_attachments);
return subpasses_info[subpass].input_attachments[index];
}
bool has_depth(unsigned subpass) const
{
VK_ASSERT(subpass < subpasses_info.size());
return subpasses_info[subpass].depth_stencil_attachment.attachment != VK_ATTACHMENT_UNUSED &&
format_has_depth_aspect(depth_stencil);
}
bool has_stencil(unsigned subpass) const
{
VK_ASSERT(subpass < subpasses_info.size());
return subpasses_info[subpass].depth_stencil_attachment.attachment != VK_ATTACHMENT_UNUSED &&
format_has_stencil_aspect(depth_stencil);
}
private:
Device *device;
VkRenderPass render_pass = VK_NULL_HANDLE;
VkFormat color_attachments[VULKAN_NUM_ATTACHMENTS] = {};
VkFormat depth_stencil = VK_FORMAT_UNDEFINED;
std::vector<SubpassInfo> subpasses_info;
void setup_subpasses(const VkRenderPassCreateInfo2 &create_info);
};
class Framebuffer : public Cookie, public NoCopyNoMove, public InternalSyncEnabled
{
public:
Framebuffer(Device *device, const RenderPass &rp, const RenderPassInfo &info);
~Framebuffer();
VkFramebuffer get_framebuffer() const
{
return framebuffer;
}
static unsigned setup_raw_views(VkImageView *views, const RenderPassInfo &info);
static void compute_dimensions(const RenderPassInfo &info, uint32_t &width, uint32_t &height);
static void compute_attachment_dimensions(const RenderPassInfo &info, unsigned index, uint32_t &width, uint32_t &height);
uint32_t get_width() const
{
return width;
}
uint32_t get_height() const
{
return height;
}
const RenderPass &get_compatible_render_pass() const
{
return render_pass;
}
private:
Device *device;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
const RenderPass &render_pass;
RenderPassInfo info;
uint32_t width = 0;
uint32_t height = 0;
};
static const unsigned VULKAN_FRAMEBUFFER_RING_SIZE = 16;
class FramebufferAllocator
{
public:
explicit FramebufferAllocator(Device *device);
Framebuffer &request_framebuffer(const RenderPassInfo &info);
void begin_frame();
void clear();
private:
struct FramebufferNode : Util::TemporaryHashmapEnabled<FramebufferNode>,
Util::IntrusiveListEnabled<FramebufferNode>,
Framebuffer
{
FramebufferNode(Device *device_, const RenderPass &rp, const RenderPassInfo &info_)
: Framebuffer(device_, rp, info_)
{
set_internal_sync_object();
}
};
Device *device;
Util::TemporaryHashmap<FramebufferNode, VULKAN_FRAMEBUFFER_RING_SIZE, false> framebuffers;
std::mutex lock;
};
class TransientAttachmentAllocator
{
public:
TransientAttachmentAllocator(Device *device_)
: device(device_)
{
}
ImageHandle request_attachment(unsigned width, unsigned height, VkFormat format,
unsigned index = 0, unsigned samples = 1, unsigned layers = 1);
void begin_frame();
void clear();
private:
struct TransientNode : Util::TemporaryHashmapEnabled<TransientNode>, Util::IntrusiveListEnabled<TransientNode>
{
explicit TransientNode(ImageHandle handle_)
: handle(std::move(handle_))
{
}
ImageHandle handle;
};
Device *device;
Util::TemporaryHashmap<TransientNode, VULKAN_FRAMEBUFFER_RING_SIZE, false> attachments;
std::mutex lock;
};
}

View File

@@ -0,0 +1,127 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "device.hpp"
#include "renderdoc_app.h"
#include <mutex>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace Vulkan
{
static std::mutex module_lock;
#ifdef _WIN32
static HMODULE renderdoc_module;
#else
static void *renderdoc_module;
#endif
static RENDERDOC_API_1_0_0 *renderdoc_api;
bool Device::init_renderdoc_capture()
{
std::lock_guard<std::mutex> holder{module_lock};
if (renderdoc_module)
return true;
#ifdef _WIN32
renderdoc_module = GetModuleHandleA("renderdoc.dll");
#elif defined(ANDROID)
renderdoc_module = dlopen("libVkLayer_GLES_RenderDoc.so", RTLD_NOW | RTLD_NOLOAD);
#else
renderdoc_module = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
#endif
if (!renderdoc_module)
{
LOGE("Failed to load RenderDoc, make sure RenderDoc started the application in capture mode.\n");
return false;
}
#ifdef _WIN32
// Workaround GCC warning about FARPROC mismatch.
auto *gpa = GetProcAddress(renderdoc_module, "RENDERDOC_GetAPI");
pRENDERDOC_GetAPI func;
memcpy(&func, &gpa, sizeof(func));
if (!func)
{
LOGE("Failed to load RENDERDOC_GetAPI function.\n");
return false;
}
#else
auto *func = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(renderdoc_module, "RENDERDOC_GetAPI"));
if (!func)
{
LOGE("Failed to load RENDERDOC_GetAPI function.\n");
return false;
}
#endif
if (!func(eRENDERDOC_API_Version_1_0_0, reinterpret_cast<void **>(&renderdoc_api)))
{
LOGE("Failed to obtain RenderDoc 1.0.0 API.\n");
return false;
}
else
{
int major, minor, patch;
renderdoc_api->GetAPIVersion(&major, &minor, &patch);
LOGI("Initialized RenderDoc API %d.%d.%d.\n", major, minor, patch);
}
return true;
}
void Device::begin_renderdoc_capture()
{
std::lock_guard<std::mutex> holder{module_lock};
if (!renderdoc_api)
{
LOGE("RenderDoc API is not loaded, cannot trigger capture.\n");
return;
}
next_frame_context();
LOGI("Starting RenderDoc frame capture.\n");
renderdoc_api->StartFrameCapture(nullptr, nullptr);
}
void Device::end_renderdoc_capture()
{
std::lock_guard<std::mutex> holder{module_lock};
if (!renderdoc_api)
{
LOGE("RenderDoc API is not loaded, cannot trigger capture.\n");
return;
}
next_frame_context();
renderdoc_api->EndFrameCapture(nullptr, nullptr);
LOGI("Ended RenderDoc frame capture.\n");
}
}

View File

@@ -0,0 +1,154 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "sampler.hpp"
#include "device.hpp"
namespace Vulkan
{
Sampler::Sampler(Device *device_, VkSampler sampler_, const SamplerCreateInfo &info, bool immutable_)
: Cookie(device_)
, device(device_)
, sampler(sampler_)
, create_info(info)
, immutable(immutable_)
{
}
Sampler::~Sampler()
{
if (sampler)
{
if (immutable)
device->get_device_table().vkDestroySampler(device->get_device(), sampler, nullptr);
else if (internal_sync)
device->destroy_sampler_nolock(sampler);
else
device->destroy_sampler(sampler);
}
}
void SamplerDeleter::operator()(Sampler *sampler)
{
sampler->device->handle_pool.samplers.free(sampler);
}
SamplerCreateInfo Sampler::fill_sampler_info(const VkSamplerCreateInfo &info)
{
SamplerCreateInfo sampler_info = {};
sampler_info.mag_filter = info.magFilter;
sampler_info.min_filter = info.minFilter;
sampler_info.mipmap_mode = info.mipmapMode;
sampler_info.address_mode_u = info.addressModeU;
sampler_info.address_mode_v = info.addressModeV;
sampler_info.address_mode_w = info.addressModeW;
sampler_info.mip_lod_bias = info.mipLodBias;
sampler_info.anisotropy_enable = info.anisotropyEnable;
sampler_info.max_anisotropy = info.maxAnisotropy;
sampler_info.compare_enable = info.compareEnable;
sampler_info.compare_op = info.compareOp;
sampler_info.min_lod = info.minLod;
sampler_info.max_lod = info.maxLod;
sampler_info.border_color = info.borderColor;
sampler_info.unnormalized_coordinates = info.unnormalizedCoordinates;
return sampler_info;
}
VkSamplerCreateInfo Sampler::fill_vk_sampler_info(const SamplerCreateInfo &sampler_info)
{
VkSamplerCreateInfo info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
info.magFilter = sampler_info.mag_filter;
info.minFilter = sampler_info.min_filter;
info.mipmapMode = sampler_info.mipmap_mode;
info.addressModeU = sampler_info.address_mode_u;
info.addressModeV = sampler_info.address_mode_v;
info.addressModeW = sampler_info.address_mode_w;
info.mipLodBias = sampler_info.mip_lod_bias;
info.anisotropyEnable = sampler_info.anisotropy_enable;
info.maxAnisotropy = sampler_info.max_anisotropy;
info.compareEnable = sampler_info.compare_enable;
info.compareOp = sampler_info.compare_op;
info.minLod = sampler_info.min_lod;
info.maxLod = sampler_info.max_lod;
info.borderColor = sampler_info.border_color;
info.unnormalizedCoordinates = sampler_info.unnormalized_coordinates;
return info;
}
ImmutableSampler::ImmutableSampler(Util::Hash hash, Device *device_, const SamplerCreateInfo &sampler_info,
const ImmutableYcbcrConversion *ycbcr_)
: HashedObject<ImmutableSampler>(hash), device(device_), ycbcr(ycbcr_)
{
VkSamplerYcbcrConversionInfo conv_info = { VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO };
auto info = Sampler::fill_vk_sampler_info(sampler_info);
if (ycbcr)
{
conv_info.conversion = ycbcr->get_conversion();
info.pNext = &conv_info;
}
#ifdef VULKAN_DEBUG
LOGI("Creating immutable sampler.\n");
#endif
VkSampler vk_sampler = VK_NULL_HANDLE;
if (device->get_device_table().vkCreateSampler(device->get_device(), &info, nullptr, &vk_sampler) != VK_SUCCESS)
LOGE("Failed to create sampler.\n");
#ifdef GRANITE_VULKAN_FOSSILIZE
device->register_sampler(vk_sampler, hash, info);
#endif
sampler = SamplerHandle(device->handle_pool.samplers.allocate(device, vk_sampler, sampler_info, true));
}
ImmutableYcbcrConversion::ImmutableYcbcrConversion(Util::Hash hash, Device *device_,
const VkSamplerYcbcrConversionCreateInfo &info)
: HashedObject<ImmutableYcbcrConversion>(hash), device(device_)
{
if (device->get_device_features().vk11_features.samplerYcbcrConversion)
{
if (device->get_device_table().vkCreateSamplerYcbcrConversion(device->get_device(), &info, nullptr,
&conversion) != VK_SUCCESS)
{
LOGE("Failed to create YCbCr conversion.\n");
}
else
{
#ifdef GRANITE_VULKAN_FOSSILIZE
device->register_sampler_ycbcr_conversion(conversion, info);
#endif
}
}
else
LOGE("Ycbcr conversion is not supported on this device.\n");
}
ImmutableYcbcrConversion::~ImmutableYcbcrConversion()
{
if (conversion)
device->get_device_table().vkDestroySamplerYcbcrConversion(device->get_device(), conversion, nullptr);
}
}

View File

@@ -0,0 +1,146 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "cookie.hpp"
#include "vulkan_common.hpp"
#include "vulkan_headers.hpp"
#include "object_pool.hpp"
namespace Vulkan
{
enum class StockSampler
{
NearestClamp,
LinearClamp,
TrilinearClamp,
NearestWrap,
LinearWrap,
TrilinearWrap,
NearestShadow,
LinearShadow,
DefaultGeometryFilterClamp,
DefaultGeometryFilterWrap,
Count
};
struct SamplerCreateInfo
{
VkFilter mag_filter;
VkFilter min_filter;
VkSamplerMipmapMode mipmap_mode;
VkSamplerAddressMode address_mode_u;
VkSamplerAddressMode address_mode_v;
VkSamplerAddressMode address_mode_w;
float mip_lod_bias;
VkBool32 anisotropy_enable;
float max_anisotropy;
VkBool32 compare_enable;
VkCompareOp compare_op;
float min_lod;
float max_lod;
VkBorderColor border_color;
VkBool32 unnormalized_coordinates;
};
class Sampler;
struct SamplerDeleter
{
void operator()(Sampler *sampler);
};
class Sampler : public Util::IntrusivePtrEnabled<Sampler, SamplerDeleter, HandleCounter>,
public Cookie, public InternalSyncEnabled
{
public:
friend struct SamplerDeleter;
~Sampler();
VkSampler get_sampler() const
{
return sampler;
}
const SamplerCreateInfo &get_create_info() const
{
return create_info;
}
static VkSamplerCreateInfo fill_vk_sampler_info(const SamplerCreateInfo &sampler_info);
static SamplerCreateInfo fill_sampler_info(const VkSamplerCreateInfo &sampler_info);
private:
friend class Util::ObjectPool<Sampler>;
Sampler(Device *device, VkSampler sampler, const SamplerCreateInfo &info, bool immutable);
Device *device;
VkSampler sampler;
SamplerCreateInfo create_info;
bool immutable;
};
using SamplerHandle = Util::IntrusivePtr<Sampler>;
class ImmutableYcbcrConversion : public HashedObject<ImmutableYcbcrConversion>
{
public:
ImmutableYcbcrConversion(Util::Hash hash, Device *device,
const VkSamplerYcbcrConversionCreateInfo &info);
~ImmutableYcbcrConversion();
void operator=(const ImmutableYcbcrConversion &) = delete;
ImmutableYcbcrConversion(const ImmutableYcbcrConversion &) = delete;
VkSamplerYcbcrConversion get_conversion() const
{
return conversion;
}
private:
Device *device;
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
class ImmutableSampler : public HashedObject<ImmutableSampler>
{
public:
ImmutableSampler(Util::Hash hash, Device *device,
const SamplerCreateInfo &info,
const ImmutableYcbcrConversion *ycbcr);
void operator=(const ImmutableSampler &) = delete;
ImmutableSampler(const ImmutableSampler &) = delete;
const Sampler &get_sampler() const
{
return *sampler;
}
VkSamplerYcbcrConversion get_ycbcr_conversion() const
{
return ycbcr ? ycbcr->get_conversion() : VK_NULL_HANDLE;
}
private:
Device *device;
const ImmutableYcbcrConversion *ycbcr;
SamplerHandle sampler;
};
}

View File

@@ -0,0 +1,244 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "semaphore.hpp"
#include "device.hpp"
#ifndef _WIN32
#include <unistd.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace Vulkan
{
SemaphoreHolder::~SemaphoreHolder()
{
recycle_semaphore();
}
void SemaphoreHolder::recycle_semaphore()
{
if (!owned)
return;
VK_ASSERT(semaphore);
if (internal_sync)
{
if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE || external_compatible_features)
{
device->destroy_semaphore_nolock(semaphore);
}
else if (is_signalled())
{
// We can't just destroy a semaphore if we don't know who signals it (e.g. WSI).
// Have to consume it by waiting then recycle.
if (signal_is_foreign_queue)
device->consume_semaphore_nolock(semaphore);
else
device->destroy_semaphore_nolock(semaphore);
}
else
device->recycle_semaphore_nolock(semaphore);
}
else
{
if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE || external_compatible_features)
{
device->destroy_semaphore(semaphore);
}
else if (is_signalled())
{
// We can't just destroy a semaphore if we don't know who signals it (e.g. WSI).
// Have to consume it by waiting then recycle.
if (signal_is_foreign_queue)
device->consume_semaphore(semaphore);
else
device->destroy_semaphore(semaphore);
}
else
device->recycle_semaphore(semaphore);
}
}
bool SemaphoreHolder::wait_timeline_timeout(uint64_t value, uint64_t timeout)
{
VK_ASSERT(semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE);
VK_ASSERT(is_proxy_timeline());
VkSemaphoreWaitInfo wait_info = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO };
wait_info.pSemaphores = &semaphore;
wait_info.semaphoreCount = 1;
wait_info.pValues = &value;
return device->get_device_table().vkWaitSemaphores(device->get_device(), &wait_info, timeout) == VK_SUCCESS;
}
void SemaphoreHolder::wait_timeline(uint64_t value)
{
wait_timeline_timeout(value, UINT64_MAX);
}
SemaphoreHolder &SemaphoreHolder::operator=(SemaphoreHolder &&other) noexcept
{
if (this == &other)
return *this;
assert(device == other.device);
recycle_semaphore();
semaphore = other.semaphore;
timeline = other.timeline;
signalled = other.signalled;
pending_wait = other.pending_wait;
semaphore_type = other.semaphore_type;
owned = other.owned;
other.semaphore = VK_NULL_HANDLE;
other.timeline = 0;
other.signalled = false;
other.pending_wait = false;
other.owned = false;
return *this;
}
ExternalHandle SemaphoreHolder::export_to_handle()
{
ExternalHandle h;
if ((external_compatible_features & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) == 0)
{
LOGE("Semaphore is not export compatible.\n");
return h;
}
if (!semaphore)
{
LOGE("Semaphore has already been consumed.\n");
return h;
}
// Technically we can export early with reference transference, but it's a bit dubious.
// We want to remain compatible with copy transference for later, e.g. SYNC_FD.
if (!signalled && semaphore_type == VK_SEMAPHORE_TYPE_BINARY)
{
LOGE("Cannot export payload from a semaphore that is not queued up for signal.\n");
return h;
}
#ifdef _WIN32
VkSemaphoreGetWin32HandleInfoKHR handle_info = { VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR };
handle_info.semaphore = semaphore;
handle_info.handleType = external_compatible_handle_type;
if (device->get_device_table().vkGetSemaphoreWin32HandleKHR(device->get_device(), &handle_info, &h.handle) != VK_SUCCESS)
{
LOGE("Failed to export to opaque handle.\n");
h.handle = nullptr;
}
#else
VkSemaphoreGetFdInfoKHR fd_info = { VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR };
fd_info.semaphore = semaphore;
fd_info.handleType = external_compatible_handle_type;
if (device->get_device_table().vkGetSemaphoreFdKHR(device->get_device(), &fd_info, &h.handle) != VK_SUCCESS)
{
LOGE("Failed to export to opaque FD.\n");
h.handle = -1;
}
#endif
h.semaphore_handle_type = external_compatible_handle_type;
return h;
}
bool SemaphoreHolder::import_from_handle(ExternalHandle handle)
{
if ((external_compatible_features & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT) == 0)
{
LOGE("Semaphore is not import compatible.\n");
return false;
}
if (!semaphore)
{
LOGE("Semaphore has already been consumed.\n");
return false;
}
if (signalled)
{
LOGE("Cannot import payload to semaphore that is already signalled.\n");
return false;
}
if (handle.semaphore_handle_type != external_compatible_handle_type)
{
LOGE("Mismatch in semaphore handle type.\n");
return false;
}
#ifdef _WIN32
VkImportSemaphoreWin32HandleInfoKHR import = { VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR };
import.handle = handle.handle;
import.semaphore = semaphore;
import.handleType = handle.semaphore_handle_type;
import.flags = semaphore_type == VK_SEMAPHORE_TYPE_BINARY_KHR ? VK_SEMAPHORE_IMPORT_TEMPORARY_BIT : 0;
if (device->get_device_table().vkImportSemaphoreWin32HandleKHR(device->get_device(), &import) != VK_SUCCESS)
{
LOGE("Failed to import semaphore handle %p!\n", handle.handle);
return false;
}
#else
VkImportSemaphoreFdInfoKHR import = { VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR };
import.fd = handle.handle;
import.semaphore = semaphore;
import.handleType = handle.semaphore_handle_type;
import.flags = semaphore_type == VK_SEMAPHORE_TYPE_BINARY_KHR ? VK_SEMAPHORE_IMPORT_TEMPORARY_BIT : 0;
if (device->get_device_table().vkImportSemaphoreFdKHR(device->get_device(), &import) != VK_SUCCESS)
{
LOGE("Failed to import semaphore FD %d!\n", handle.handle);
return false;
}
#endif
if (ExternalHandle::semaphore_handle_type_imports_by_reference(import.handleType))
{
#ifdef _WIN32
// Consume the handle, since the VkSemaphore holds a reference on Win32.
::CloseHandle(handle.handle);
#else
::close(handle.handle);
#endif
}
signal_external();
return true;
}
void SemaphoreHolderDeleter::operator()(Vulkan::SemaphoreHolder *semaphore)
{
semaphore->device->handle_pool.semaphores.free(semaphore);
}
}

View File

@@ -0,0 +1,206 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_common.hpp"
#include "vulkan_headers.hpp"
#include "cookie.hpp"
#include "object_pool.hpp"
namespace Vulkan
{
class Device;
class SemaphoreHolder;
struct SemaphoreHolderDeleter
{
void operator()(SemaphoreHolder *semaphore);
};
class SemaphoreHolder : public Util::IntrusivePtrEnabled<SemaphoreHolder, SemaphoreHolderDeleter, HandleCounter>,
public InternalSyncEnabled
{
public:
friend struct SemaphoreHolderDeleter;
~SemaphoreHolder();
const VkSemaphore &get_semaphore() const
{
return semaphore;
}
bool is_signalled() const
{
return signalled;
}
uint64_t get_timeline_value() const
{
VK_ASSERT(!owned && semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE_KHR);
return timeline;
}
VkSemaphore consume()
{
auto ret = semaphore;
VK_ASSERT(semaphore);
VK_ASSERT(signalled);
semaphore = VK_NULL_HANDLE;
signalled = false;
owned = false;
return ret;
}
VkSemaphore release_semaphore()
{
auto ret = semaphore;
semaphore = VK_NULL_HANDLE;
signalled = false;
owned = false;
return ret;
}
void wait_external()
{
VK_ASSERT(semaphore);
VK_ASSERT(signalled);
signalled = false;
}
void signal_external()
{
VK_ASSERT(!signalled);
VK_ASSERT(semaphore);
signalled = true;
}
void set_signal_is_foreign_queue()
{
VK_ASSERT(signalled);
signal_is_foreign_queue = true;
}
void set_pending_wait()
{
pending_wait = true;
}
bool is_pending_wait() const
{
return pending_wait;
}
void set_external_object_compatible(VkExternalSemaphoreHandleTypeFlagBits handle_type,
VkExternalSemaphoreFeatureFlags features)
{
external_compatible_handle_type = handle_type;
external_compatible_features = features;
}
bool is_external_object_compatible() const
{
return external_compatible_features != 0;
}
VkSemaphoreTypeKHR get_semaphore_type() const
{
return semaphore_type;
}
bool is_proxy_timeline() const
{
return proxy_timeline;
}
void set_proxy_timeline()
{
proxy_timeline = true;
signalled = false;
}
// If successful, importing takes ownership of the handle/fd.
// Application can use dup() / DuplicateHandle() to keep a reference.
// Imported semaphores are assumed to be signalled, or pending to be signalled.
// All imports are performed with TEMPORARY permanence.
ExternalHandle export_to_handle();
bool import_from_handle(ExternalHandle handle);
VkExternalSemaphoreFeatureFlags get_external_features() const
{
return external_compatible_features;
}
VkExternalSemaphoreHandleTypeFlagBits get_external_handle_type() const
{
return external_compatible_handle_type;
}
SemaphoreHolder &operator=(SemaphoreHolder &&other) noexcept;
void wait_timeline(uint64_t value);
bool wait_timeline_timeout(uint64_t value, uint64_t timeout);
private:
friend class Util::ObjectPool<SemaphoreHolder>;
SemaphoreHolder(Device *device_, VkSemaphore semaphore_, bool signalled_, bool owned_)
: device(device_)
, semaphore(semaphore_)
, timeline(0)
, semaphore_type(VK_SEMAPHORE_TYPE_BINARY_KHR)
, signalled(signalled_)
, owned(owned_)
{
}
SemaphoreHolder(Device *device_, uint64_t timeline_, VkSemaphore semaphore_, bool owned_)
: device(device_)
, semaphore(semaphore_)
, timeline(timeline_)
, semaphore_type(VK_SEMAPHORE_TYPE_TIMELINE_KHR)
, owned(owned_)
{
}
explicit SemaphoreHolder(Device *device_)
: device(device_)
{
}
void recycle_semaphore();
Device *device;
VkSemaphore semaphore = VK_NULL_HANDLE;
uint64_t timeline = 0;
VkSemaphoreTypeKHR semaphore_type = VK_SEMAPHORE_TYPE_BINARY_KHR;
bool signalled = false;
bool pending_wait = false;
bool owned = false;
bool proxy_timeline = false;
bool signal_is_foreign_queue = false;
VkExternalSemaphoreHandleTypeFlagBits external_compatible_handle_type = {};
VkExternalSemaphoreFeatureFlags external_compatible_features = 0;
};
using Semaphore = Util::IntrusivePtr<SemaphoreHolder>;
}

View File

@@ -0,0 +1,68 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "semaphore_manager.hpp"
#include "device.hpp"
namespace Vulkan
{
void SemaphoreManager::init(Device *device_)
{
device = device_;
table = &device->get_device_table();
}
SemaphoreManager::~SemaphoreManager()
{
for (auto &sem : semaphores)
table->vkDestroySemaphore(device->get_device(), sem, nullptr);
}
void SemaphoreManager::recycle(VkSemaphore sem)
{
if (sem != VK_NULL_HANDLE)
semaphores.push_back(sem);
}
VkSemaphore SemaphoreManager::request_cleared_semaphore()
{
if (semaphores.empty())
{
VkSemaphoreCreateInfo info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
VkSemaphore semaphore;
if (table->vkCreateSemaphore(device->get_device(), &info, nullptr, &semaphore) != VK_SUCCESS)
{
LOGE("Failed to create semaphore.\n");
semaphore = VK_NULL_HANDLE;
}
return semaphore;
}
else
{
auto sem = semaphores.back();
semaphores.pop_back();
return sem;
}
}
}

View File

@@ -0,0 +1,45 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include <vector>
namespace Vulkan
{
class Device;
class SemaphoreManager
{
public:
void init(Device *device);
~SemaphoreManager();
VkSemaphore request_cleared_semaphore();
void recycle(VkSemaphore semaphore);
private:
Device *device = nullptr;
const VolkDeviceTable *table = nullptr;
std::vector<VkSemaphore> semaphores;
};
}

View File

@@ -0,0 +1,687 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "shader.hpp"
#include "device.hpp"
#ifdef GRANITE_VULKAN_SPIRV_CROSS
#include "spirv_cross.hpp"
using namespace spirv_cross;
#endif
using namespace Util;
namespace Vulkan
{
void ImmutableSamplerBank::hash(Util::Hasher &h, const ImmutableSamplerBank *sampler_bank)
{
h.u32(0);
if (sampler_bank)
{
unsigned index = 0;
for (auto &set : sampler_bank->samplers)
{
for (auto *binding : set)
{
if (binding)
{
h.u32(index);
h.u64(binding->get_hash());
}
index++;
}
}
}
}
PipelineLayout::PipelineLayout(Hash hash, Device *device_, const CombinedResourceLayout &layout_,
const ImmutableSamplerBank *immutable_samplers)
: IntrusiveHashMapEnabled<PipelineLayout>(hash)
, device(device_)
, layout(layout_)
{
VkDescriptorSetLayout layouts[VULKAN_NUM_DESCRIPTOR_SETS] = {};
unsigned num_sets = 0;
for (unsigned i = 0; i < VULKAN_NUM_DESCRIPTOR_SETS; i++)
{
set_allocators[i] = device->request_descriptor_set_allocator(layout.sets[i], layout.stages_for_bindings[i],
immutable_samplers ? immutable_samplers->samplers[i] : nullptr);
layouts[i] = set_allocators[i]->get_layout_for_pool();
if (layout.descriptor_set_mask & (1u << i))
{
num_sets = i + 1;
// Assume the last set index in layout is the highest frequency update one, make that push descriptor if possible.
// Only one descriptor set can be push descriptor.
bool has_push_layout = set_allocators[i]->get_layout_for_push() != VK_NULL_HANDLE;
if (has_push_layout)
push_set_index = i;
}
}
if (push_set_index != UINT32_MAX)
layouts[push_set_index] = set_allocators[push_set_index]->get_layout_for_push();
if (num_sets > VULKAN_NUM_DESCRIPTOR_SETS)
LOGE("Number of sets %u exceeds limit of %u.\n", num_sets, VULKAN_NUM_DESCRIPTOR_SETS);
VkPipelineLayoutCreateInfo info = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
if (num_sets)
{
info.setLayoutCount = num_sets;
info.pSetLayouts = layouts;
}
if (layout.push_constant_range.stageFlags != 0)
{
info.pushConstantRangeCount = 1;
info.pPushConstantRanges = &layout.push_constant_range;
}
#ifdef VULKAN_DEBUG
LOGI("Creating pipeline layout.\n");
#endif
auto &table = device->get_device_table();
if (table.vkCreatePipelineLayout(device->get_device(), &info, nullptr, &pipe_layout) != VK_SUCCESS)
LOGE("Failed to create pipeline layout.\n");
#ifdef GRANITE_VULKAN_FOSSILIZE
device->register_pipeline_layout(pipe_layout, get_hash(), info);
#endif
create_update_templates();
}
void PipelineLayout::create_update_templates()
{
auto &table = device->get_device_table();
for (unsigned desc_set = 0; desc_set < VULKAN_NUM_DESCRIPTOR_SETS; desc_set++)
{
if ((layout.descriptor_set_mask & (1u << desc_set)) == 0)
continue;
if ((layout.bindless_descriptor_set_mask & (1u << desc_set)) != 0)
continue;
VkDescriptorUpdateTemplateEntry update_entries[VULKAN_NUM_BINDINGS];
uint32_t update_count = 0;
auto &set_layout = layout.sets[desc_set];
for_each_bit(set_layout.uniform_buffer_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
// Work around a RenderDoc capture bug where descriptorCount > 1 is not handled correctly.
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = desc_set == push_set_index ?
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
if (desc_set == push_set_index)
entry.offset = offsetof(ResourceBinding, buffer.push) + sizeof(ResourceBinding) * (binding + i);
else
entry.offset = offsetof(ResourceBinding, buffer.dynamic) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.storage_buffer_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
entry.offset = offsetof(ResourceBinding, buffer.dynamic) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.sampled_texel_buffer_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
entry.offset = offsetof(ResourceBinding, buffer_view) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.storage_texel_buffer_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
entry.offset = offsetof(ResourceBinding, buffer_view) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.sampled_image_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
if (set_layout.fp_mask & (1u << binding))
entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * (binding + i);
else
entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.separate_image_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
if (set_layout.fp_mask & (1u << binding))
entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * (binding + i);
else
entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.sampler_mask & ~set_layout.immutable_sampler_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.storage_image_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
if (set_layout.fp_mask & (1u << binding))
entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * (binding + i);
else
entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
for_each_bit(set_layout.input_attachment_mask, [&](uint32_t binding) {
unsigned array_size = set_layout.array_size[binding];
VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
for (unsigned i = 0; i < array_size; i++)
{
auto &entry = update_entries[update_count++];
entry.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
entry.dstBinding = binding;
entry.dstArrayElement = i;
entry.descriptorCount = 1;
if (set_layout.fp_mask & (1u << binding))
entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * (binding + i);
else
entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * (binding + i);
entry.stride = sizeof(ResourceBinding);
}
});
VkDescriptorUpdateTemplateCreateInfo info = { VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO };
info.pipelineLayout = pipe_layout;
if (desc_set == push_set_index)
{
info.descriptorSetLayout = set_allocators[desc_set]->get_layout_for_push();
info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR;
}
else
{
info.descriptorSetLayout = set_allocators[desc_set]->get_layout_for_pool();
info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET;
}
info.set = desc_set;
info.descriptorUpdateEntryCount = update_count;
info.pDescriptorUpdateEntries = update_entries;
info.pipelineBindPoint = (layout.stages_for_sets[desc_set] & VK_SHADER_STAGE_COMPUTE_BIT) ?
VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS;
if (table.vkCreateDescriptorUpdateTemplate(device->get_device(), &info, nullptr,
&update_template[desc_set]) != VK_SUCCESS)
{
LOGE("Failed to create descriptor update template.\n");
}
}
}
PipelineLayout::~PipelineLayout()
{
auto &table = device->get_device_table();
if (pipe_layout != VK_NULL_HANDLE)
table.vkDestroyPipelineLayout(device->get_device(), pipe_layout, nullptr);
for (auto &update : update_template)
if (update != VK_NULL_HANDLE)
table.vkDestroyDescriptorUpdateTemplate(device->get_device(), update, nullptr);
}
const char *Shader::stage_to_name(ShaderStage stage)
{
switch (stage)
{
case ShaderStage::Compute:
return "compute";
case ShaderStage::Vertex:
return "vertex";
case ShaderStage::Fragment:
return "fragment";
case ShaderStage::Geometry:
return "geometry";
case ShaderStage::TessControl:
return "tess_control";
case ShaderStage::TessEvaluation:
return "tess_evaluation";
default:
return "unknown";
}
}
// Implicitly also checks for endian issues.
static const uint16_t reflection_magic[] = { 'G', 'R', 'A', ResourceLayout::Version };
size_t ResourceLayout::serialization_size()
{
return sizeof(ResourceLayout) + sizeof(reflection_magic);
}
bool ResourceLayout::serialize(uint8_t *data, size_t size) const
{
if (size != serialization_size())
return false;
// Cannot serialize externally defined immutable samplers.
for (auto &set : sets)
if (set.immutable_sampler_mask != 0)
return false;
memcpy(data, reflection_magic, sizeof(reflection_magic));
memcpy(data + sizeof(reflection_magic), this, sizeof(*this));
return true;
}
bool ResourceLayout::unserialize(const uint8_t *data, size_t size)
{
if (size != sizeof(*this) + sizeof(reflection_magic))
{
LOGE("Reflection size mismatch.\n");
return false;
}
if (memcmp(data, reflection_magic, sizeof(reflection_magic)) != 0)
{
LOGE("Magic mismatch.\n");
return false;
}
memcpy(this, data + sizeof(reflection_magic), sizeof(*this));
return true;
}
Util::Hash Shader::hash(const uint32_t *data, size_t size)
{
Util::Hasher hasher;
hasher.data(data, size);
return hasher.get();
}
#ifdef GRANITE_VULKAN_SPIRV_CROSS
static void update_array_info(ResourceLayout &layout, const SPIRType &type, unsigned set, unsigned binding)
{
auto &size = layout.sets[set].array_size[binding];
if (!type.array.empty())
{
if (type.array.size() != 1)
LOGE("Array dimension must be 1.\n");
else if (!type.array_size_literal.front())
LOGE("Array dimension must be a literal.\n");
else
{
if (type.array.front() == 0)
{
if (binding != 0)
LOGE("Bindless textures can only be used with binding = 0 in a set.\n");
if (type.basetype != SPIRType::Image || type.image.dim == spv::DimBuffer)
{
LOGE("Can only use bindless for sampled images.\n");
}
else
{
layout.bindless_set_mask |= 1u << set;
// Ignore fp_mask for bindless since we can mix and match.
layout.sets[set].fp_mask = 0;
}
size = DescriptorSetLayout::UNSIZED_ARRAY;
}
else if (size && size != type.array.front())
LOGE("Array dimension for (%u, %u) is inconsistent.\n", set, binding);
else if (type.array.front() + binding > VULKAN_NUM_BINDINGS)
LOGE("Binding array will go out of bounds.\n");
else
size = uint8_t(type.array.front());
}
}
else
{
if (size && size != 1)
LOGE("Array dimension for (%u, %u) is inconsistent.\n", set, binding);
size = 1;
}
}
bool Shader::reflect_resource_layout(ResourceLayout &layout, const uint32_t *data, size_t size)
{
Compiler compiler(data, size / sizeof(uint32_t));
#ifdef VULKAN_DEBUG
LOGI("Reflecting shader layout.\n");
#endif
auto resources = compiler.get_shader_resources();
for (auto &image : resources.sampled_images)
{
auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
auto &type = compiler.get_type(image.type_id);
if (type.image.dim == spv::DimBuffer)
layout.sets[set].sampled_texel_buffer_mask |= 1u << binding;
else
layout.sets[set].sampled_image_mask |= 1u << binding;
if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
layout.sets[set].fp_mask |= 1u << binding;
update_array_info(layout, type, set, binding);
}
for (auto &image : resources.subpass_inputs)
{
auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
layout.sets[set].input_attachment_mask |= 1u << binding;
auto &type = compiler.get_type(image.type_id);
if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
layout.sets[set].fp_mask |= 1u << binding;
update_array_info(layout, type, set, binding);
}
for (auto &image : resources.separate_images)
{
auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
auto &type = compiler.get_type(image.type_id);
if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
layout.sets[set].fp_mask |= 1u << binding;
if (type.image.dim == spv::DimBuffer)
layout.sets[set].sampled_texel_buffer_mask |= 1u << binding;
else
layout.sets[set].separate_image_mask |= 1u << binding;
update_array_info(layout, type, set, binding);
}
for (auto &image : resources.separate_samplers)
{
auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
layout.sets[set].sampler_mask |= 1u << binding;
update_array_info(layout, compiler.get_type(image.type_id), set, binding);
}
for (auto &image : resources.storage_images)
{
auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
auto &type = compiler.get_type(image.type_id);
if (type.image.dim == spv::DimBuffer)
layout.sets[set].storage_texel_buffer_mask |= 1u << binding;
else
layout.sets[set].storage_image_mask |= 1u << binding;
if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
layout.sets[set].fp_mask |= 1u << binding;
update_array_info(layout, type, set, binding);
}
for (auto &buffer : resources.uniform_buffers)
{
auto set = compiler.get_decoration(buffer.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(buffer.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
layout.sets[set].uniform_buffer_mask |= 1u << binding;
update_array_info(layout, compiler.get_type(buffer.type_id), set, binding);
}
for (auto &buffer : resources.storage_buffers)
{
auto set = compiler.get_decoration(buffer.id, spv::DecorationDescriptorSet);
auto binding = compiler.get_decoration(buffer.id, spv::DecorationBinding);
VK_ASSERT(set < VULKAN_NUM_DESCRIPTOR_SETS);
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
layout.sets[set].storage_buffer_mask |= 1u << binding;
update_array_info(layout, compiler.get_type(buffer.type_id), set, binding);
}
for (auto &attrib : resources.stage_inputs)
{
auto location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
layout.input_mask |= 1u << location;
}
for (auto &attrib : resources.stage_outputs)
{
auto location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
layout.output_mask |= 1u << location;
}
if (!resources.push_constant_buffers.empty())
{
// Don't bother trying to extract which part of a push constant block we're using.
// Just assume we're accessing everything. At least on older validation layers,
// it did not do a static analysis to determine similar information, so we got a lot
// of false positives.
layout.push_constant_size =
compiler.get_declared_struct_size(compiler.get_type(resources.push_constant_buffers.front().base_type_id));
}
auto spec_constants = compiler.get_specialization_constants();
for (auto &c : spec_constants)
{
if (c.constant_id >= VULKAN_NUM_TOTAL_SPEC_CONSTANTS)
{
LOGE("Spec constant ID: %u is out of range, will be ignored.\n", c.constant_id);
continue;
}
layout.spec_constant_mask |= 1u << c.constant_id;
}
return true;
}
#else
bool Shader::reflect_resource_layout(ResourceLayout &, const uint32_t *, size_t)
{
return false;
}
#endif
Shader::Shader(Hash hash, Device *device_, const uint32_t *data, size_t size,
const ResourceLayout *resource_layout)
: IntrusiveHashMapEnabled<Shader>(hash)
, device(device_)
{
VkShaderModuleCreateInfo info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
info.codeSize = size;
info.pCode = data;
#ifdef VULKAN_DEBUG
LOGI("Creating shader module.\n");
#endif
auto &table = device->get_device_table();
if (table.vkCreateShaderModule(device->get_device(), &info, nullptr, &module) != VK_SUCCESS)
LOGE("Failed to create shader module.\n");
#ifdef GRANITE_VULKAN_FOSSILIZE
device->register_shader_module(module, get_hash(), info);
#endif
if (resource_layout)
layout = *resource_layout;
#ifdef GRANITE_VULKAN_SPIRV_CROSS
else if (!reflect_resource_layout(layout, data, size))
LOGE("Failed to reflect resource layout.\n");
#endif
if (layout.bindless_set_mask != 0 && !device->get_device_features().vk12_features.descriptorIndexing)
LOGE("Sufficient features for descriptor indexing is not supported on this device.\n");
}
Shader::~Shader()
{
auto &table = device->get_device_table();
if (module)
table.vkDestroyShaderModule(device->get_device(), module, nullptr);
}
void Program::set_shader(ShaderStage stage, Shader *handle)
{
shaders[Util::ecast(stage)] = handle;
}
Program::Program(Device *device_, Shader *vertex, Shader *fragment, const ImmutableSamplerBank *sampler_bank)
: device(device_)
{
set_shader(ShaderStage::Vertex, vertex);
set_shader(ShaderStage::Fragment, fragment);
device->bake_program(*this, sampler_bank);
}
Program::Program(Device *device_, Shader *task, Shader *mesh, Shader *fragment, const ImmutableSamplerBank *sampler_bank)
: device(device_)
{
if (task)
set_shader(ShaderStage::Task, task);
set_shader(ShaderStage::Mesh, mesh);
set_shader(ShaderStage::Fragment, fragment);
device->bake_program(*this, sampler_bank);
}
Program::Program(Device *device_, Shader *compute_shader, const ImmutableSamplerBank *sampler_bank)
: device(device_)
{
set_shader(ShaderStage::Compute, compute_shader);
device->bake_program(*this, sampler_bank);
}
Pipeline Program::get_pipeline(Hash hash) const
{
auto *ret = pipelines.find(hash);
return ret ? ret->get() : Pipeline{};
}
Pipeline Program::add_pipeline(Hash hash, const Pipeline &pipeline)
{
return pipelines.emplace_yield(hash, pipeline)->get();
}
void Program::destroy_pipeline(const Pipeline &pipeline)
{
device->get_device_table().vkDestroyPipeline(device->get_device(), pipeline.pipeline, nullptr);
}
void Program::promote_read_write_to_read_only()
{
pipelines.move_to_read_only();
}
Program::~Program()
{
for (auto &pipe : pipelines.get_read_only())
destroy_pipeline(pipe.get());
for (auto &pipe : pipelines.get_read_write())
destroy_pipeline(pipe.get());
}
}

View File

@@ -0,0 +1,228 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "cookie.hpp"
#include "descriptor_set.hpp"
#include "hash.hpp"
#include "intrusive.hpp"
#include "limits.hpp"
#include "vulkan_headers.hpp"
#include "enum_cast.hpp"
namespace spirv_cross
{
struct SPIRType;
}
namespace Vulkan
{
class Device;
enum class ShaderStage
{
Vertex = 0,
TessControl = 1,
TessEvaluation = 2,
Geometry = 3,
Fragment = 4,
Compute = 5,
Task = 6,
Mesh = 7,
Count
};
struct ResourceLayout
{
DescriptorSetLayout sets[VULKAN_NUM_DESCRIPTOR_SETS];
uint32_t input_mask = 0;
uint32_t output_mask = 0;
uint32_t push_constant_size = 0;
uint32_t spec_constant_mask = 0;
uint32_t bindless_set_mask = 0;
enum { Version = 4 };
bool unserialize(const uint8_t *data, size_t size);
bool serialize(uint8_t *data, size_t size) const;
static size_t serialization_size();
};
static_assert(sizeof(DescriptorSetLayout) % 8 == 0, "Size of DescriptorSetLayout does not align to 64 bits.");
struct CombinedResourceLayout
{
uint32_t attribute_mask = 0;
uint32_t render_target_mask = 0;
DescriptorSetLayout sets[VULKAN_NUM_DESCRIPTOR_SETS] = {};
uint32_t stages_for_bindings[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS] = {};
uint32_t stages_for_sets[VULKAN_NUM_DESCRIPTOR_SETS] = {};
VkPushConstantRange push_constant_range = {};
uint32_t descriptor_set_mask = 0;
uint32_t bindless_descriptor_set_mask = 0;
uint32_t spec_constant_mask[Util::ecast(ShaderStage::Count)] = {};
uint32_t combined_spec_constant_mask = 0;
Util::Hash push_constant_layout_hash = 0;
};
union ResourceBinding
{
struct
{
VkDescriptorBufferInfo dynamic;
VkDescriptorBufferInfo push;
} buffer;
struct
{
VkDescriptorImageInfo fp;
VkDescriptorImageInfo integer;
} image;
VkBufferView buffer_view;
};
struct ResourceBindings
{
ResourceBinding bindings[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS];
uint64_t cookies[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS];
uint64_t secondary_cookies[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS];
uint8_t push_constant_data[VULKAN_PUSH_CONSTANT_SIZE];
};
struct ImmutableSamplerBank
{
const ImmutableSampler *samplers[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS];
static void hash(Util::Hasher &h, const ImmutableSamplerBank *bank);
};
class PipelineLayout : public HashedObject<PipelineLayout>
{
public:
PipelineLayout(Util::Hash hash, Device *device, const CombinedResourceLayout &layout,
const ImmutableSamplerBank *sampler_bank);
~PipelineLayout();
const CombinedResourceLayout &get_resource_layout() const
{
return layout;
}
VkPipelineLayout get_layout() const
{
return pipe_layout;
}
DescriptorSetAllocator *get_allocator(unsigned set) const
{
return set_allocators[set];
}
VkDescriptorUpdateTemplate get_update_template(unsigned set) const
{
return update_template[set];
}
uint32_t get_push_set_index() const
{
return push_set_index;
}
private:
Device *device;
VkPipelineLayout pipe_layout = VK_NULL_HANDLE;
CombinedResourceLayout layout;
DescriptorSetAllocator *set_allocators[VULKAN_NUM_DESCRIPTOR_SETS] = {};
VkDescriptorUpdateTemplate update_template[VULKAN_NUM_DESCRIPTOR_SETS] = {};
uint32_t push_set_index = UINT32_MAX;
void create_update_templates();
};
class Shader : public HashedObject<Shader>
{
public:
Shader(Util::Hash binding, Device *device, const uint32_t *data, size_t size,
const ResourceLayout *layout = nullptr);
~Shader();
const ResourceLayout &get_layout() const
{
return layout;
}
VkShaderModule get_module() const
{
return module;
}
static bool reflect_resource_layout(ResourceLayout &layout, const uint32_t *spirv_data, size_t spirv_size);
static const char *stage_to_name(ShaderStage stage);
static Util::Hash hash(const uint32_t *data, size_t size);
private:
Device *device;
VkShaderModule module = VK_NULL_HANDLE;
ResourceLayout layout;
};
struct Pipeline
{
VkPipeline pipeline;
uint32_t dynamic_mask;
};
class Program : public HashedObject<Program>
{
public:
Program(Device *device, Shader *vertex, Shader *fragment, const ImmutableSamplerBank *sampler_bank);
Program(Device *device, Shader *task, Shader *mesh, Shader *fragment, const ImmutableSamplerBank *sampler_bank);
Program(Device *device, Shader *compute, const ImmutableSamplerBank *sampler_bank);
~Program();
inline const Shader *get_shader(ShaderStage stage) const
{
return shaders[Util::ecast(stage)];
}
void set_pipeline_layout(const PipelineLayout *new_layout)
{
layout = new_layout;
}
const PipelineLayout *get_pipeline_layout() const
{
return layout;
}
Pipeline get_pipeline(Util::Hash hash) const;
Pipeline add_pipeline(Util::Hash hash, const Pipeline &pipeline);
void promote_read_write_to_read_only();
private:
void set_shader(ShaderStage stage, Shader *handle);
Device *device;
Shader *shaders[Util::ecast(ShaderStage::Count)] = {};
const PipelineLayout *layout = nullptr;
VulkanCache<Util::IntrusivePODWrapper<Pipeline>> pipelines;
void destroy_pipeline(const Pipeline &pipeline);
};
}

View File

@@ -0,0 +1,518 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "texture_format.hpp"
#include "format.hpp"
#include <algorithm>
namespace Vulkan
{
uint32_t TextureFormatLayout::num_miplevels(uint32_t width, uint32_t height, uint32_t depth)
{
uint32_t size = unsigned(std::max(std::max(width, height), depth));
uint32_t levels = 0;
while (size)
{
levels++;
size >>= 1;
}
return levels;
}
void TextureFormatLayout::format_block_dim(VkFormat format, uint32_t &width, uint32_t &height)
{
#define fmt(x, w, h) \
case VK_FORMAT_##x: \
width = w; \
height = h; \
break
switch (format)
{
fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 4, 4);
fmt(ETC2_R8G8B8_UNORM_BLOCK, 4, 4);
fmt(ETC2_R8G8B8_SRGB_BLOCK, 4, 4);
fmt(EAC_R11_UNORM_BLOCK, 4, 4);
fmt(EAC_R11_SNORM_BLOCK, 4, 4);
fmt(EAC_R11G11_UNORM_BLOCK, 4, 4);
fmt(EAC_R11G11_SNORM_BLOCK, 4, 4);
fmt(BC1_RGB_UNORM_BLOCK, 4, 4);
fmt(BC1_RGB_SRGB_BLOCK, 4, 4);
fmt(BC1_RGBA_UNORM_BLOCK, 4, 4);
fmt(BC1_RGBA_SRGB_BLOCK, 4, 4);
fmt(BC2_UNORM_BLOCK, 4, 4);
fmt(BC2_SRGB_BLOCK, 4, 4);
fmt(BC3_UNORM_BLOCK, 4, 4);
fmt(BC3_SRGB_BLOCK, 4, 4);
fmt(BC4_UNORM_BLOCK, 4, 4);
fmt(BC4_SNORM_BLOCK, 4, 4);
fmt(BC5_UNORM_BLOCK, 4, 4);
fmt(BC5_SNORM_BLOCK, 4, 4);
fmt(BC6H_UFLOAT_BLOCK, 4, 4);
fmt(BC6H_SFLOAT_BLOCK, 4, 4);
fmt(BC7_SRGB_BLOCK, 4, 4);
fmt(BC7_UNORM_BLOCK, 4, 4);
#define astc_fmt(w, h) \
fmt(ASTC_##w##x##h##_UNORM_BLOCK, w, h); \
fmt(ASTC_##w##x##h##_SRGB_BLOCK, w, h); \
fmt(ASTC_##w##x##h##_SFLOAT_BLOCK_EXT, w, h)
astc_fmt(4, 4);
astc_fmt(5, 4);
astc_fmt(5, 5);
astc_fmt(6, 5);
astc_fmt(6, 6);
astc_fmt(8, 5);
astc_fmt(8, 6);
astc_fmt(8, 8);
astc_fmt(10, 5);
astc_fmt(10, 6);
astc_fmt(10, 8);
astc_fmt(10, 10);
astc_fmt(12, 10);
astc_fmt(12, 12);
default:
width = 1;
height = 1;
break;
}
#undef fmt
#undef astc_fmt
}
uint32_t TextureFormatLayout::format_block_size(VkFormat format, VkImageAspectFlags aspect)
{
#define fmt(x, bpp) \
case VK_FORMAT_##x: \
return bpp
#define fmt2(x, bpp0, bpp1) \
case VK_FORMAT_##x: \
return aspect == VK_IMAGE_ASPECT_PLANE_0_BIT ? bpp0 : bpp1
switch (format)
{
fmt(R4G4_UNORM_PACK8, 1);
fmt(R4G4B4A4_UNORM_PACK16, 2);
fmt(B4G4R4A4_UNORM_PACK16, 2);
fmt(R5G6B5_UNORM_PACK16, 2);
fmt(B5G6R5_UNORM_PACK16, 2);
fmt(R5G5B5A1_UNORM_PACK16, 2);
fmt(B5G5R5A1_UNORM_PACK16, 2);
fmt(A1R5G5B5_UNORM_PACK16, 2);
fmt(R8_UNORM, 1);
fmt(R8_SNORM, 1);
fmt(R8_USCALED, 1);
fmt(R8_SSCALED, 1);
fmt(R8_UINT, 1);
fmt(R8_SINT, 1);
fmt(R8_SRGB, 1);
fmt(R8G8_UNORM, 2);
fmt(R8G8_SNORM, 2);
fmt(R8G8_USCALED, 2);
fmt(R8G8_SSCALED, 2);
fmt(R8G8_UINT, 2);
fmt(R8G8_SINT, 2);
fmt(R8G8_SRGB, 2);
fmt(R8G8B8_UNORM, 3);
fmt(R8G8B8_SNORM, 3);
fmt(R8G8B8_USCALED, 3);
fmt(R8G8B8_SSCALED, 3);
fmt(R8G8B8_UINT, 3);
fmt(R8G8B8_SINT, 3);
fmt(R8G8B8_SRGB, 3);
fmt(R8G8B8A8_UNORM, 4);
fmt(R8G8B8A8_SNORM, 4);
fmt(R8G8B8A8_USCALED, 4);
fmt(R8G8B8A8_SSCALED, 4);
fmt(R8G8B8A8_UINT, 4);
fmt(R8G8B8A8_SINT, 4);
fmt(R8G8B8A8_SRGB, 4);
fmt(B8G8R8A8_UNORM, 4);
fmt(B8G8R8A8_SNORM, 4);
fmt(B8G8R8A8_USCALED, 4);
fmt(B8G8R8A8_SSCALED, 4);
fmt(B8G8R8A8_UINT, 4);
fmt(B8G8R8A8_SINT, 4);
fmt(B8G8R8A8_SRGB, 4);
fmt(A8B8G8R8_UNORM_PACK32, 4);
fmt(A8B8G8R8_SNORM_PACK32, 4);
fmt(A8B8G8R8_USCALED_PACK32, 4);
fmt(A8B8G8R8_SSCALED_PACK32, 4);
fmt(A8B8G8R8_UINT_PACK32, 4);
fmt(A8B8G8R8_SINT_PACK32, 4);
fmt(A8B8G8R8_SRGB_PACK32, 4);
fmt(A2B10G10R10_UNORM_PACK32, 4);
fmt(A2B10G10R10_SNORM_PACK32, 4);
fmt(A2B10G10R10_USCALED_PACK32, 4);
fmt(A2B10G10R10_SSCALED_PACK32, 4);
fmt(A2B10G10R10_UINT_PACK32, 4);
fmt(A2B10G10R10_SINT_PACK32, 4);
fmt(A2R10G10B10_UNORM_PACK32, 4);
fmt(A2R10G10B10_SNORM_PACK32, 4);
fmt(A2R10G10B10_USCALED_PACK32, 4);
fmt(A2R10G10B10_SSCALED_PACK32, 4);
fmt(A2R10G10B10_UINT_PACK32, 4);
fmt(A2R10G10B10_SINT_PACK32, 4);
fmt(R16_UNORM, 2);
fmt(R16_SNORM, 2);
fmt(R16_USCALED, 2);
fmt(R16_SSCALED, 2);
fmt(R16_UINT, 2);
fmt(R16_SINT, 2);
fmt(R16_SFLOAT, 2);
fmt(R16G16_UNORM, 4);
fmt(R16G16_SNORM, 4);
fmt(R16G16_USCALED, 4);
fmt(R16G16_SSCALED, 4);
fmt(R16G16_UINT, 4);
fmt(R16G16_SINT, 4);
fmt(R16G16_SFLOAT, 4);
fmt(R16G16B16_UNORM, 6);
fmt(R16G16B16_SNORM, 6);
fmt(R16G16B16_USCALED, 6);
fmt(R16G16B16_SSCALED, 6);
fmt(R16G16B16_UINT, 6);
fmt(R16G16B16_SINT, 6);
fmt(R16G16B16_SFLOAT, 6);
fmt(R16G16B16A16_UNORM, 8);
fmt(R16G16B16A16_SNORM, 8);
fmt(R16G16B16A16_USCALED, 8);
fmt(R16G16B16A16_SSCALED, 8);
fmt(R16G16B16A16_UINT, 8);
fmt(R16G16B16A16_SINT, 8);
fmt(R16G16B16A16_SFLOAT, 8);
fmt(R32_UINT, 4);
fmt(R32_SINT, 4);
fmt(R32_SFLOAT, 4);
fmt(R32G32_UINT, 8);
fmt(R32G32_SINT, 8);
fmt(R32G32_SFLOAT, 8);
fmt(R32G32B32_UINT, 12);
fmt(R32G32B32_SINT, 12);
fmt(R32G32B32_SFLOAT, 12);
fmt(R32G32B32A32_UINT, 16);
fmt(R32G32B32A32_SINT, 16);
fmt(R32G32B32A32_SFLOAT, 16);
fmt(R64_UINT, 8);
fmt(R64_SINT, 8);
fmt(R64_SFLOAT, 8);
fmt(R64G64_UINT, 16);
fmt(R64G64_SINT, 16);
fmt(R64G64_SFLOAT, 16);
fmt(R64G64B64_UINT, 24);
fmt(R64G64B64_SINT, 24);
fmt(R64G64B64_SFLOAT, 24);
fmt(R64G64B64A64_UINT, 32);
fmt(R64G64B64A64_SINT, 32);
fmt(R64G64B64A64_SFLOAT, 32);
fmt(B10G11R11_UFLOAT_PACK32, 4);
fmt(E5B9G9R9_UFLOAT_PACK32, 4);
fmt(D16_UNORM, 2);
fmt(X8_D24_UNORM_PACK32, 4);
fmt(D32_SFLOAT, 4);
fmt(S8_UINT, 1);
case VK_FORMAT_D16_UNORM_S8_UINT:
return aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? 2 : 1;
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? 4 : 1;
// ETC2
fmt(ETC2_R8G8B8A8_UNORM_BLOCK, 16);
fmt(ETC2_R8G8B8A8_SRGB_BLOCK, 16);
fmt(ETC2_R8G8B8A1_UNORM_BLOCK, 8);
fmt(ETC2_R8G8B8A1_SRGB_BLOCK, 8);
fmt(ETC2_R8G8B8_UNORM_BLOCK, 8);
fmt(ETC2_R8G8B8_SRGB_BLOCK, 8);
fmt(EAC_R11_UNORM_BLOCK, 8);
fmt(EAC_R11_SNORM_BLOCK, 8);
fmt(EAC_R11G11_UNORM_BLOCK, 16);
fmt(EAC_R11G11_SNORM_BLOCK, 16);
// BC
fmt(BC1_RGB_UNORM_BLOCK, 8);
fmt(BC1_RGB_SRGB_BLOCK, 8);
fmt(BC1_RGBA_UNORM_BLOCK, 8);
fmt(BC1_RGBA_SRGB_BLOCK, 8);
fmt(BC2_UNORM_BLOCK, 16);
fmt(BC2_SRGB_BLOCK, 16);
fmt(BC3_UNORM_BLOCK, 16);
fmt(BC3_SRGB_BLOCK, 16);
fmt(BC4_UNORM_BLOCK, 8);
fmt(BC4_SNORM_BLOCK, 8);
fmt(BC5_UNORM_BLOCK, 16);
fmt(BC5_SNORM_BLOCK, 16);
fmt(BC6H_UFLOAT_BLOCK, 16);
fmt(BC6H_SFLOAT_BLOCK, 16);
fmt(BC7_SRGB_BLOCK, 16);
fmt(BC7_UNORM_BLOCK, 16);
// ASTC
#define astc_fmt(w, h) \
fmt(ASTC_##w##x##h##_UNORM_BLOCK, 16); \
fmt(ASTC_##w##x##h##_SRGB_BLOCK, 16); \
fmt(ASTC_##w##x##h##_SFLOAT_BLOCK_EXT, 16)
astc_fmt(4, 4);
astc_fmt(5, 4);
astc_fmt(5, 5);
astc_fmt(6, 5);
astc_fmt(6, 6);
astc_fmt(8, 5);
astc_fmt(8, 6);
astc_fmt(8, 8);
astc_fmt(10, 5);
astc_fmt(10, 6);
astc_fmt(10, 8);
astc_fmt(10, 10);
astc_fmt(12, 10);
astc_fmt(12, 12);
fmt(G8B8G8R8_422_UNORM, 4);
fmt(B8G8R8G8_422_UNORM, 4);
fmt(G8_B8_R8_3PLANE_420_UNORM, 1);
fmt2(G8_B8R8_2PLANE_420_UNORM, 1, 2);
fmt(G8_B8_R8_3PLANE_422_UNORM, 1);
fmt2(G8_B8R8_2PLANE_422_UNORM, 1, 2);
fmt(G8_B8_R8_3PLANE_444_UNORM, 1);
fmt(R10X6_UNORM_PACK16, 2);
fmt(R10X6G10X6_UNORM_2PACK16, 4);
fmt(R10X6G10X6B10X6A10X6_UNORM_4PACK16, 8);
fmt(G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, 8);
fmt(B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, 8);
fmt(G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, 2);
fmt(G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, 2);
fmt(G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, 2);
fmt2(G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, 2, 4);
fmt2(G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, 2, 4);
fmt(R12X4_UNORM_PACK16, 2);
fmt(R12X4G12X4_UNORM_2PACK16, 4);
fmt(R12X4G12X4B12X4A12X4_UNORM_4PACK16, 8);
fmt(G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, 8);
fmt(B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, 8);
fmt(G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, 2);
fmt(G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, 2);
fmt(G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, 2);
fmt2(G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, 2, 4);
fmt2(G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, 2, 4);
fmt(G16B16G16R16_422_UNORM, 8);
fmt(B16G16R16G16_422_UNORM, 8);
fmt(G16_B16_R16_3PLANE_420_UNORM, 2);
fmt(G16_B16_R16_3PLANE_422_UNORM, 2);
fmt(G16_B16_R16_3PLANE_444_UNORM, 2);
fmt2(G16_B16R16_2PLANE_420_UNORM, 2, 4);
fmt2(G16_B16R16_2PLANE_422_UNORM, 2, 4);
default:
assert(0 && "Unknown format.");
return 0;
}
#undef fmt
#undef fmt2
#undef astc_fmt
}
void TextureFormatLayout::fill_mipinfo(uint32_t width, uint32_t height, uint32_t depth)
{
block_stride = format_block_size(format, 0);
format_block_dim(format, block_dim_x, block_dim_y);
if (mip_levels == 0)
mip_levels = num_miplevels(width, height, depth);
size_t offset = 0;
for (uint32_t mip = 0; mip < mip_levels; mip++)
{
offset = (offset + 15) & ~15;
uint32_t blocks_x = (width + block_dim_x - 1) / block_dim_x;
uint32_t blocks_y = (height + block_dim_y - 1) / block_dim_y;
size_t mip_size = blocks_x * blocks_y * array_layers * depth * block_stride;
mips[mip].offset = offset;
mips[mip].block_row_length = blocks_x;
mips[mip].block_image_height = blocks_y;
mips[mip].row_length = blocks_x * block_dim_x;
mips[mip].image_height = blocks_y * block_dim_y;
mips[mip].width = width;
mips[mip].height = height;
mips[mip].depth = depth;
offset += mip_size;
width = std::max((width >> 1u), 1u);
height = std::max((height >> 1u), 1u);
depth = std::max((depth >> 1u), 1u);
}
required_size = offset;
}
void TextureFormatLayout::set_1d(VkFormat format_, uint32_t width, uint32_t array_layers_, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_1D;
format = format_;
array_layers = array_layers_;
mip_levels = mip_levels_;
fill_mipinfo(width, 1, 1);
}
void TextureFormatLayout::set_2d(VkFormat format_, uint32_t width, uint32_t height,
uint32_t array_layers_, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_2D;
format = format_;
array_layers = array_layers_;
mip_levels = mip_levels_;
fill_mipinfo(width, height, 1);
}
void TextureFormatLayout::set_3d(VkFormat format_, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels_)
{
image_type = VK_IMAGE_TYPE_3D;
format = format_;
array_layers = 1;
mip_levels = mip_levels_;
fill_mipinfo(width, height, depth);
}
void TextureFormatLayout::set_buffer(void *buffer_, size_t size)
{
buffer = static_cast<uint8_t *>(buffer_);
buffer_size = size;
}
uint32_t TextureFormatLayout::get_width(uint32_t mip) const
{
return mips[mip].width;
}
uint32_t TextureFormatLayout::get_height(uint32_t mip) const
{
return mips[mip].height;
}
uint32_t TextureFormatLayout::get_depth(uint32_t mip) const
{
return mips[mip].depth;
}
uint32_t TextureFormatLayout::get_layers() const
{
return array_layers;
}
VkImageType TextureFormatLayout::get_image_type() const
{
return image_type;
}
VkFormat TextureFormatLayout::get_format() const
{
return format;
}
uint32_t TextureFormatLayout::get_block_stride() const
{
return block_stride;
}
uint32_t TextureFormatLayout::get_levels() const
{
return mip_levels;
}
size_t TextureFormatLayout::get_required_size() const
{
return required_size;
}
const TextureFormatLayout::MipInfo &TextureFormatLayout::get_mip_info(uint32_t mip) const
{
return mips[mip];
}
uint32_t TextureFormatLayout::get_block_dim_x() const
{
return block_dim_x;
}
uint32_t TextureFormatLayout::get_block_dim_y() const
{
return block_dim_y;
}
size_t TextureFormatLayout::row_byte_stride(uint32_t row_length) const
{
return ((row_length + block_dim_x - 1) / block_dim_x) * block_stride;
}
size_t TextureFormatLayout::layer_byte_stride(uint32_t image_height, size_t row_byte_stride) const
{
return ((image_height + block_dim_y - 1) / block_dim_y) * row_byte_stride;
}
void TextureFormatLayout::build_buffer_image_copies(Util::SmallVector<VkBufferImageCopy, 32> &copies) const
{
copies.resize(mip_levels);
for (unsigned level = 0; level < mip_levels; level++)
{
const auto &mip_info = mips[level];
auto &blit = copies[level];
blit = {};
blit.bufferOffset = mip_info.offset;
blit.bufferRowLength = mip_info.row_length;
blit.bufferImageHeight = mip_info.image_height;
blit.imageSubresource.aspectMask = format_to_aspect_mask(format);
blit.imageSubresource.mipLevel = level;
blit.imageSubresource.baseArrayLayer = 0;
blit.imageSubresource.layerCount = array_layers;
blit.imageExtent.width = mip_info.width;
blit.imageExtent.height = mip_info.height;
blit.imageExtent.depth = mip_info.depth;
}
}
}

View File

@@ -0,0 +1,178 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
#include "small_vector.hpp"
#include <vector>
#include <stddef.h>
#include <assert.h>
namespace Vulkan
{
class TextureFormatLayout
{
public:
void set_1d(VkFormat format, uint32_t width, uint32_t array_layers = 1, uint32_t mip_levels = 1);
void set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t array_layers = 1, uint32_t mip_levels = 1);
void set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels = 1);
static uint32_t format_block_size(VkFormat format, VkImageAspectFlags aspect);
static void format_block_dim(VkFormat format, uint32_t &width, uint32_t &height);
static uint32_t num_miplevels(uint32_t width, uint32_t height = 1, uint32_t depth = 1);
void set_buffer(void *buffer, size_t size);
inline void *get_buffer()
{
return buffer;
}
uint32_t get_width(uint32_t mip = 0) const;
uint32_t get_height(uint32_t mip = 0) const;
uint32_t get_depth(uint32_t mip = 0) const;
uint32_t get_levels() const;
uint32_t get_layers() const;
uint32_t get_block_stride() const;
uint32_t get_block_dim_x() const;
uint32_t get_block_dim_y() const;
VkImageType get_image_type() const;
VkFormat get_format() const;
size_t get_required_size() const;
size_t row_byte_stride(uint32_t row_length) const;
size_t layer_byte_stride(uint32_t row_length, size_t row_byte_stride) const;
inline size_t get_row_size(uint32_t mip) const
{
return size_t(mips[mip].block_row_length) * block_stride;
}
inline size_t get_layer_size(uint32_t mip) const
{
return size_t(mips[mip].block_image_height) * get_row_size(mip);
}
struct MipInfo
{
size_t offset = 0;
uint32_t width = 1;
uint32_t height = 1;
uint32_t depth = 1;
uint32_t block_image_height = 0;
uint32_t block_row_length = 0;
uint32_t image_height = 0;
uint32_t row_length = 0;
};
const MipInfo &get_mip_info(uint32_t mip) const;
inline void *data(uint32_t layer = 0, uint32_t mip = 0) const
{
assert(buffer);
assert(buffer_size == required_size);
auto &mip_info = mips[mip];
uint8_t *slice = buffer + mip_info.offset;
slice += block_stride * layer * mip_info.block_row_length * mip_info.block_image_height;
return slice;
}
template <typename T>
inline T *data_generic(uint32_t x, uint32_t y, uint32_t slice_index, uint32_t mip = 0) const
{
auto &mip_info = mips[mip];
T *slice = reinterpret_cast<T *>(buffer + mip_info.offset);
slice += slice_index * mip_info.block_row_length * mip_info.block_image_height;
slice += y * mip_info.block_row_length;
slice += x;
return slice;
}
inline void *data_opaque(uint32_t x, uint32_t y, uint32_t slice_index, uint32_t mip = 0) const
{
auto &mip_info = mips[mip];
uint8_t *slice = buffer + mip_info.offset;
size_t off = slice_index * mip_info.block_row_length * mip_info.block_image_height;
off += y * mip_info.block_row_length;
off += x;
return slice + off * block_stride;
}
template <typename T>
inline T *data_generic() const
{
return data_generic<T>(0, 0, 0, 0);
}
template <typename T>
inline T *data_1d(uint32_t x, uint32_t layer = 0, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_1D);
assert(buffer_size == required_size);
return data_generic<T>(x, 0, layer, mip);
}
template <typename T>
inline T *data_2d(uint32_t x, uint32_t y, uint32_t layer = 0, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_2D);
assert(buffer_size == required_size);
return data_generic<T>(x, y, layer, mip);
}
template <typename T>
inline T *data_3d(uint32_t x, uint32_t y, uint32_t z, uint32_t mip = 0) const
{
assert(sizeof(T) == block_stride);
assert(buffer);
assert(image_type == VK_IMAGE_TYPE_3D);
assert(buffer_size == required_size);
return data_generic<T>(x, y, z, mip);
}
void build_buffer_image_copies(Util::SmallVector<VkBufferImageCopy, 32> &copies) const;
private:
uint8_t *buffer = nullptr;
size_t buffer_size = 0;
VkImageType image_type = VK_IMAGE_TYPE_MAX_ENUM;
VkFormat format = VK_FORMAT_UNDEFINED;
size_t required_size = 0;
uint32_t block_stride = 1;
uint32_t mip_levels = 1;
uint32_t array_layers = 1;
uint32_t block_dim_x = 1;
uint32_t block_dim_y = 1;
MipInfo mips[16];
void fill_mipinfo(uint32_t width, uint32_t height, uint32_t depth);
};
}

View File

@@ -0,0 +1,113 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <string>
namespace Vulkan
{
static inline const char *layout_to_string(VkImageLayout layout)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
return "SHADER_READ_ONLY";
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
return "DS_READ_ONLY";
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
return "DS";
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
return "COLOR";
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
return "TRANSFER_DST";
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
return "TRANSFER_SRC";
case VK_IMAGE_LAYOUT_GENERAL:
return "GENERAL";
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
return "PRESENT";
default:
return "UNDEFINED";
}
}
static inline std::string access_flags_to_string(VkAccessFlags2 flags)
{
std::string result;
if (flags & VK_ACCESS_SHADER_READ_BIT)
result += "SHADER_READ ";
if (flags & VK_ACCESS_SHADER_WRITE_BIT)
result += "SHADER_WRITE ";
if (flags & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)
result += "DS_WRITE ";
if (flags & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT)
result += "DS_READ ";
if (flags & VK_ACCESS_COLOR_ATTACHMENT_READ_BIT)
result += "COLOR_READ ";
if (flags & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)
result += "COLOR_WRITE ";
if (flags & VK_ACCESS_INPUT_ATTACHMENT_READ_BIT)
result += "INPUT_READ ";
if (flags & VK_ACCESS_TRANSFER_WRITE_BIT)
result += "TRANSFER_WRITE ";
if (flags & VK_ACCESS_TRANSFER_READ_BIT)
result += "TRANSFER_READ ";
if (flags & VK_ACCESS_UNIFORM_READ_BIT)
result += "UNIFORM_READ ";
if (!result.empty())
result.pop_back();
else
result = "NONE";
return result;
}
static inline std::string stage_flags_to_string(VkPipelineStageFlags2 flags)
{
std::string result;
if (flags & VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT)
result += "GRAPHICS ";
if (flags & (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT))
result += "DEPTH ";
if (flags & VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)
result += "COLOR ";
if (flags & VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)
result += "FRAGMENT ";
if (flags & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT)
result += "COMPUTE ";
if (flags & VK_PIPELINE_STAGE_TRANSFER_BIT)
result += "TRANSFER ";
if (flags & (VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT))
result += "VERTEX ";
if (!result.empty())
result.pop_back();
else
result = "NONE";
return result;
}
}

View File

@@ -0,0 +1,111 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "intrusive.hpp"
#include "object_pool.hpp"
#include "intrusive_hash_map.hpp"
#include "vulkan_headers.hpp"
namespace Vulkan
{
using HandleCounter = Util::MultiThreadCounter;
template <typename T>
using VulkanObjectPool = Util::ThreadSafeObjectPool<T>;
template <typename T>
using VulkanCache = Util::ThreadSafeIntrusiveHashMapReadCached<T>;
template <typename T>
using VulkanCacheReadWrite = Util::ThreadSafeIntrusiveHashMap<T>;
enum QueueIndices
{
QUEUE_INDEX_GRAPHICS,
QUEUE_INDEX_COMPUTE,
QUEUE_INDEX_TRANSFER,
QUEUE_INDEX_VIDEO_DECODE,
QUEUE_INDEX_VIDEO_ENCODE,
QUEUE_INDEX_COUNT
};
struct ExternalHandle
{
#ifdef _WIN32
using NativeHandle = void *;
NativeHandle handle = nullptr;
#else
using NativeHandle = int;
NativeHandle handle = -1;
#endif
VkExternalMemoryHandleTypeFlagBits memory_handle_type = get_opaque_memory_handle_type();
VkExternalSemaphoreHandleTypeFlagBits semaphore_handle_type = get_opaque_semaphore_handle_type();
constexpr static VkExternalMemoryHandleTypeFlagBits get_opaque_memory_handle_type()
{
#ifdef _WIN32
return VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
#else
return VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
}
constexpr static VkExternalSemaphoreHandleTypeFlagBits get_opaque_semaphore_handle_type()
{
#ifdef _WIN32
return VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT;
#else
return VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
}
inline explicit operator bool() const
{
#ifdef _WIN32
return handle != nullptr;
#else
return handle >= 0;
#endif
}
static bool memory_handle_type_imports_by_reference(VkExternalMemoryHandleTypeFlagBits type)
{
VK_ASSERT(type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT ||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT ||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT ||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT ||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT);
return type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
}
static bool semaphore_handle_type_imports_by_reference(VkExternalSemaphoreHandleTypeFlagBits type)
{
VK_ASSERT(type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT ||
type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT ||
type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT);
// D3D11 fence aliases D3D12 fence. It's basically the same thing, just D3D11.3.
return type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
}
};
}

View File

@@ -0,0 +1,71 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#if defined(_WIN32) && !defined(VK_USE_PLATFORM_WIN32_KHR)
#define VK_USE_PLATFORM_WIN32_KHR
#endif
#if defined(VULKAN_H_) || defined(VULKAN_CORE_H_)
#error "Must include vulkan_headers.hpp before Vulkan headers"
#endif
#include "volk.h"
#include <stdlib.h>
#include "logging.hpp"
#include <utility>
// Workaround silly Xlib headers that define macros for these globally :(
#ifdef None
#undef None
#endif
#ifdef Bool
#undef Bool
#endif
#ifdef Status
#undef Status
#endif
#ifdef VULKAN_DEBUG
#define VK_ASSERT(x) \
do \
{ \
if (!bool(x)) \
{ \
LOGE("Vulkan error at %s:%d.\n", __FILE__, __LINE__); \
abort(); \
} \
} while (0)
#else
#define VK_ASSERT(x) ((void)0)
#endif
namespace Vulkan
{
struct NoCopyNoMove
{
NoCopyNoMove() = default;
NoCopyNoMove(const NoCopyNoMove &) = delete;
void operator=(const NoCopyNoMove &) = delete;
};
}

View File

@@ -0,0 +1,169 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "vulkan_headers.hpp"
namespace Vulkan
{
// FIXME: Also consider that we might have to flip X or Y w.r.t. dimensions,
// but that only matters for partial rendering ...
static inline bool surface_transform_swaps_xy(VkSurfaceTransformFlagBitsKHR transform)
{
return (transform & (
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR |
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR |
VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR |
VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)) != 0;
}
static inline void viewport_transform_xy(VkViewport &vp, VkSurfaceTransformFlagBitsKHR transform,
uint32_t fb_width, uint32_t fb_height)
{
switch (transform)
{
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
{
float new_y = vp.x;
float new_x = float(fb_width) - (vp.y + vp.height);
vp.x = new_x;
vp.y = new_y;
std::swap(vp.width, vp.height);
break;
}
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
{
// Untested. Cannot make Android trigger this mode.
float new_left = float(fb_width) - (vp.x + vp.width);
float new_top = float(fb_height) - (vp.y + vp.height);
vp.x = new_left;
vp.y = new_top;
break;
}
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
{
float new_x = vp.y;
float new_y = float(fb_height) - (vp.x + vp.width);
vp.x = new_x;
vp.y = new_y;
std::swap(vp.width, vp.height);
break;
}
default:
break;
}
}
static inline void rect2d_clip(VkRect2D &rect)
{
if (rect.offset.x < 0)
{
rect.extent.width += rect.offset.x;
rect.offset.x = 0;
}
if (rect.offset.y < 0)
{
rect.extent.height += rect.offset.y;
rect.offset.y = 0;
}
rect.extent.width = std::min<uint32_t>(rect.extent.width, 0x7fffffffu - rect.offset.x);
rect.extent.height = std::min<uint32_t>(rect.extent.height, 0x7fffffffu - rect.offset.y);
}
static inline void rect2d_transform_xy(VkRect2D &rect, VkSurfaceTransformFlagBitsKHR transform,
uint32_t fb_width, uint32_t fb_height)
{
switch (transform)
{
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
{
int new_y = rect.offset.x;
int new_x = int(fb_width) - int(rect.offset.y + rect.extent.height);
rect.offset = { new_x, new_y };
std::swap(rect.extent.width, rect.extent.height);
break;
}
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
{
// Untested. Cannot make Android trigger this mode.
int new_left = int(fb_width) - int(rect.offset.x + rect.extent.width);
int new_top = int(fb_height) - int(rect.offset.y + rect.extent.height);
rect.offset = { new_left, new_top };
break;
}
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
{
int new_x = rect.offset.y;
int new_y = int(fb_height) - int(rect.offset.x + rect.extent.width);
rect.offset = { new_x, new_y };
std::swap(rect.extent.width, rect.extent.height);
break;
}
default:
break;
}
}
static inline void build_prerotate_matrix_2x2(VkSurfaceTransformFlagBitsKHR pre_rotate, float mat[4])
{
// TODO: HORIZONTAL_MIRROR.
switch (pre_rotate)
{
default:
mat[0] = 1.0f;
mat[1] = 0.0f;
mat[2] = 0.0f;
mat[3] = 1.0f;
break;
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
mat[0] = 0.0f;
mat[1] = 1.0f;
mat[2] = -1.0f;
mat[3] = 0.0f;
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
mat[0] = 0.0f;
mat[1] = -1.0f;
mat[2] = 1.0f;
mat[3] = 0.0f;
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
mat[0] = -1.0f;
mat[1] = 0.0f;
mat[2] = 0.0f;
mat[3] = -1.0f;
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,379 @@
/* Copyright (c) 2017-2023 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "device.hpp"
#include "semaphore_manager.hpp"
#include "vulkan_headers.hpp"
#include "timer.hpp"
#include <vector>
#include <thread>
#include <chrono>
#include <memory>
#ifdef HAVE_WSI_DXGI_INTEROP
#include "wsi_dxgi.hpp"
#endif
namespace Granite
{
class InputTrackerHandler;
}
namespace Vulkan
{
class WSI;
class WSIPlatform
{
public:
virtual ~WSIPlatform() = default;
virtual VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice gpu) = 0;
// This is virtual so that application can hold ownership over the surface handle, for e.g. Qt interop.
virtual void destroy_surface(VkInstance instance, VkSurfaceKHR surface);
virtual std::vector<const char *> get_instance_extensions() = 0;
virtual std::vector<const char *> get_device_extensions()
{
return { "VK_KHR_swapchain" };
}
virtual VkFormat get_preferred_format()
{
return VK_FORMAT_B8G8R8A8_SRGB;
}
bool should_resize()
{
return resize;
}
virtual void notify_current_swapchain_dimensions(unsigned width, unsigned height)
{
resize = false;
current_swapchain_width = width;
current_swapchain_height = height;
}
virtual uint32_t get_surface_width() = 0;
virtual uint32_t get_surface_height() = 0;
virtual float get_aspect_ratio()
{
return float(get_surface_width()) / float(get_surface_height());
}
virtual bool alive(WSI &wsi) = 0;
virtual void poll_input() = 0;
virtual void poll_input_async(Granite::InputTrackerHandler *handler) = 0;
virtual bool has_external_swapchain()
{
return false;
}
virtual void block_until_wsi_forward_progress(WSI &wsi)
{
get_frame_timer().enter_idle();
while (!resize && alive(wsi))
{
poll_input();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
get_frame_timer().leave_idle();
}
Util::FrameTimer &get_frame_timer()
{
return timer;
}
virtual void release_resources()
{
}
virtual void event_device_created(Device *device);
virtual void event_device_destroyed();
virtual void event_swapchain_created(Device *device, VkSwapchainKHR swapchain,
unsigned width, unsigned height,
float aspect_ratio, size_t num_swapchain_images,
VkFormat format, VkColorSpaceKHR color_space,
VkSurfaceTransformFlagBitsKHR pre_rotate);
virtual void destroy_swapchain_resources(VkSwapchainKHR swapchain);
virtual void event_swapchain_destroyed();
virtual void event_frame_tick(double frame, double elapsed);
virtual void event_swapchain_index(Device *device, unsigned index);
virtual void set_window_title(const std::string &title);
virtual uintptr_t get_fullscreen_monitor();
virtual uintptr_t get_native_window();
virtual const VkApplicationInfo *get_application_info();
virtual void begin_drop_event();
enum class MessageType { Error, Warning, Info };
virtual void show_message_box(const std::string &str, MessageType type);
protected:
unsigned current_swapchain_width = 0;
unsigned current_swapchain_height = 0;
bool resize = false;
private:
Util::FrameTimer timer;
};
enum class PresentMode
{
SyncToVBlank, // Force FIFO
UnlockedMaybeTear, // MAILBOX or IMMEDIATE
UnlockedForceTearing, // Force IMMEDIATE
UnlockedNoTearing // Force MAILBOX
};
enum class BackbufferFormat
{
UNORM,
sRGB,
HDR10,
DisplayP3,
UNORMPassthrough
};
class WSI
{
public:
WSI();
void set_platform(WSIPlatform *platform);
void set_present_mode(PresentMode mode);
void set_backbuffer_format(BackbufferFormat format);
// Latency is normally pretty low, but this aims to target
// really low latency. Only suitable for cases where rendering loads are extremely simple.
void set_low_latency_mode(bool enable);
inline BackbufferFormat get_backbuffer_format() const
{
return backbuffer_format;
}
inline VkColorSpaceKHR get_backbuffer_color_space() const
{
return swapchain_surface_format.colorSpace;
}
void set_support_prerotate(bool enable);
void set_extra_usage_flags(VkImageUsageFlags usage);
VkSurfaceTransformFlagBitsKHR get_current_prerotate() const;
inline PresentMode get_present_mode() const
{
return present_mode;
}
// Deprecated, use set_backbuffer_format().
void set_backbuffer_srgb(bool enable);
inline bool get_backbuffer_srgb() const
{
return backbuffer_format == BackbufferFormat::sRGB;
}
void set_hdr_metadata(const VkHdrMetadataEXT &metadata);
inline const VkHdrMetadataEXT &get_hdr_metadata() const
{
return hdr_metadata;
}
// First, we need a Util::IntrinsivePtr<Vulkan::Context>.
// This holds the instance and device.
// The simple approach. WSI internally creates the context with instance + device.
// Required information about extensions etc, is pulled from the platform.
bool init_context_from_platform(unsigned num_thread_indices, const Context::SystemHandles &system_handles);
// If you have your own VkInstance and/or VkDevice, you must create your own Vulkan::Context with
// the appropriate init() call. Based on the platform you use, you must make sure to enable the
// required extensions.
bool init_from_existing_context(ContextHandle context);
// Then we initialize the Vulkan::Device. Either lets WSI create its own device or reuse an existing handle.
// A device provided here must have been bound to the context.
bool init_device();
bool init_device(DeviceHandle device);
// Called after we have a device and context.
// Either we can use a swapchain based on VkSurfaceKHR, or we can supply our own images
// to create a virtual swapchain.
// init_surface_swapchain() is called once.
// Here we create the surface and perform creation of the first swapchain.
bool init_surface_swapchain();
bool init_external_swapchain(std::vector<ImageHandle> external_images);
// Calls init_context_from_platform -> init_device -> init_surface_swapchain in succession.
bool init_simple(unsigned num_thread_indices, const Context::SystemHandles &system_handles);
~WSI();
inline Context &get_context()
{
return *context;
}
inline Device &get_device()
{
return *device;
}
// Acquires a frame from swapchain, also calls poll_input() after acquire
// since acquire tends to block.
bool begin_frame();
// Presents and iterates frame context.
// Present is skipped if swapchain resource was not touched.
// The normal app loop is something like begin_frame() -> submit work -> end_frame().
bool end_frame();
// For external swapchains we don't have a normal acquire -> present cycle.
// - set_external_frame()
// - index replaces the acquire next image index.
// - acquire_semaphore replaces semaphore from acquire next image.
// - frame_time controls the frame time passed down.
// - begin_frame()
// - submit work
// - end_frame()
// - consume_external_release_semaphore()
// - Returns the release semaphore that can passed to the equivalent of QueuePresentKHR.
void set_external_frame(unsigned index, Semaphore acquire_semaphore, double frame_time);
Semaphore consume_external_release_semaphore();
CommandBuffer::Type get_current_present_queue_type() const;
// Equivalent to calling destructor.
void teardown();
WSIPlatform &get_platform()
{
VK_ASSERT(platform);
return *platform;
}
// For Android. Used in response to APP_CMD_{INIT,TERM}_WINDOW once
// we have a proper swapchain going.
// We have to completely drain swapchain before the window is terminated on Android.
void deinit_surface_and_swapchain();
void reinit_surface_and_swapchain(VkSurfaceKHR new_surface);
void set_window_title(const std::string &title);
double get_smooth_frame_time() const;
double get_smooth_elapsed_time() const;
private:
void update_framebuffer(unsigned width, unsigned height);
ContextHandle context;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
std::vector<VkImage> swapchain_images;
std::vector<Semaphore> release_semaphores;
DeviceHandle device;
const VolkDeviceTable *table = nullptr;
unsigned swapchain_width = 0;
unsigned swapchain_height = 0;
float swapchain_aspect_ratio = 1.0f;
VkSurfaceFormatKHR swapchain_surface_format = { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
PresentMode current_present_mode = PresentMode::SyncToVBlank;
PresentMode present_mode = PresentMode::SyncToVBlank;
bool low_latency_mode_enable = false;
VkPresentModeKHR active_present_mode = VK_PRESENT_MODE_FIFO_KHR;
std::vector<VkPresentModeKHR> present_mode_compat_group;
bool update_active_presentation_mode(PresentMode mode);
VkImageUsageFlags current_extra_usage = 0;
VkImageUsageFlags extra_usage = 0;
bool swapchain_is_suboptimal = false;
enum class SwapchainError
{
None,
NoSurface,
Error
};
SwapchainError init_swapchain(unsigned width, unsigned height);
bool blocking_init_swapchain(unsigned width, unsigned height);
uint32_t swapchain_index = 0;
bool has_acquired_swapchain_index = false;
WSIPlatform *platform = nullptr;
std::vector<ImageHandle> external_swapchain_images;
unsigned external_frame_index = 0;
Semaphore external_acquire;
Semaphore external_release;
bool frame_is_external = false;
BackbufferFormat backbuffer_format = BackbufferFormat::sRGB;
BackbufferFormat current_backbuffer_format = BackbufferFormat::sRGB;
bool support_prerotate = false;
VkSurfaceTransformFlagBitsKHR swapchain_current_prerotate = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
bool begin_frame_external();
double external_frame_time = 0.0;
double smooth_frame_time = 0.0;
double smooth_elapsed_time = 0.0;
uint64_t present_id = 0;
uint64_t present_last_id = 0;
unsigned present_frame_latency = 0;
void tear_down_swapchain();
void drain_swapchain(bool in_tear_down);
void wait_swapchain_latency();
VkHdrMetadataEXT hdr_metadata = { VK_STRUCTURE_TYPE_HDR_METADATA_EXT };
struct DeferredDeletion
{
VkSwapchainKHR swapchain;
Fence fence;
};
Util::SmallVector<DeferredDeletion> deferred_swapchains;
Vulkan::Fence last_present_fence;
void nonblock_delete_swapchains();
VkSurfaceFormatKHR find_suitable_present_format(const std::vector<VkSurfaceFormatKHR> &formats, BackbufferFormat desired_format) const;
#ifdef HAVE_WSI_DXGI_INTEROP
std::unique_ptr<DXGIInteropSwapchain> dxgi;
bool init_surface_swapchain_dxgi(unsigned width, unsigned height);
bool begin_frame_dxgi();
bool end_frame_dxgi();
#endif
};
}