Squashed 'external/ircolib/' changes from ce3cd726c..de6e324bd
de6e324bdseparate emu thread10d3daf86Roms List improvements95d202f37Let's make the rom list process on a separate thread so the emulator doesnt take ages to load.fc306967fWow the ROM Header was just completely busted. Game list view works nowbad1691eefuck this shit2b59e5f46game list in progressd26417b83remappable inputs in progressac4af8106inpute72abc240update readme430139dc9Qt6 frontend3080d4d45Fix this small bug too08cd13b85Cop0 unused functions do not actually pose a threat (as per manual). They don't do anything, so shall we.61bb4fb44make idle loop detection a little more specific with where the load goesb037de4c3SAZDFsdff12e81e73eneed to figure out why n64-systemtest loops indefinitely at some address that appears to be valid (i think it's me not invalidating the cache properly)204f0e13bidle skipping seems to work!cb8bb634asdkfjlasdf58e5c89c1Fix compilation issue on my machine (no idea)24fb2898eattempting more serious idle skipping214719577Place rsp.Step inside cached interpreter. Gains about 3 more fpsbb97dcc23mmmmm920b77d38wjkhasdfjhkasdf430ccdab4it's a start...4f42a673aCached interpreter plays Mario 64. Start looking into RSP as wellc9a030787idle skipping works!5fbda03cenew idea366637abaIdle skipping... maybe?609fa2fb0Cache instructions implemented but broken lmao. Commented out for nowe140a6d12- Stop using inheritance for CPU, instead use composition. - Introduce KAIZEN_JIT_ENABLED optional define instead of relying on __aarch64__ and the like. - More cache work68e613057prep cache impl811b4d809fix clang formatfda755f7didkd5024ebbfsmall MI refactor in preparation of (eventually) implementing the RDRAM interface properly694b45341Merge commit '206dcdedf195fb320913584180edb12c7731e396' as 'external/SDL'206dcdedfSquashed 'external/SDL/' content from commit 4d17b99d0a4d16e1cb4need to update sdl848b19920Fix compilation errordb61b5299Merge commit 'e94a94559f28e49678fbcf72199a5258137b0fe9' as 'external/imgui'e94a94559Squashed 'external/imgui/' content from commit 02e9b8cac52edb3757need to update imguic1a705e86Emulate weird JALR behaviour4b4c32f4bFix exception for "unusable COP1" in 4 instructions i missed accidentally (again)df5828142Bug putting 0s in the log everywheref8b580048Make isviewer a sink to file8241e9735Fix exception for "unusable COP1" in 4 instructions i missed accidentallyb29715f20small changesd9a620bc1make use of my new small utility library0d1aa938eAdd 'external/ircolib/' from commit 'ce3cd726c8df8388d554abf8bb55d55020eb4450'e64eb40b3Fuck git git-subtree-dir: external/ircolib git-subtree-split:de6e324bde
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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, ¤t_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);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
}
|
||||
@@ -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 ×tamp : timestamps)
|
||||
timestamp.mark_end_of_frame_context();
|
||||
}
|
||||
|
||||
void TimestampIntervalManager::reset()
|
||||
{
|
||||
for (auto ×tamp : timestamps)
|
||||
timestamp.reset();
|
||||
}
|
||||
|
||||
void TimestampIntervalManager::log_simple(const TimestampIntervalReportCallback &func) const
|
||||
{
|
||||
for (auto ×tamp : 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
+518
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+178
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user