feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys
Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low- latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775, volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB the standalone no-renderer build needs; scripts/vendor-pyrowave.sh reproduces the tree (a pin bump is protocol-affecting, plan §4.2). build.rs drives the wrapper CMakeLists (static archives incl. a static C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7). Offline-safe by construction: no network, no system lib, vendored Vulkan headers — same model as the opus dep (flatpak builder has no network). Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03): - upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier Vulkan<->Vulkan) pass, from the pristine AND the pruned tree - GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p, 0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of target - cargo test -p pyrowave-sys green (static link + API-version pin check) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
add_granite_internal_lib(granite-vulkan
|
||||
context.cpp context.hpp
|
||||
vulkan_headers.hpp vulkan_prerotate.hpp
|
||||
device.cpp device.hpp
|
||||
wsi.cpp wsi.hpp
|
||||
wsi_pacer.cpp wsi_pacer.hpp
|
||||
buffer_pool.cpp buffer_pool.hpp
|
||||
image.cpp image.hpp
|
||||
cookie.cpp cookie.hpp
|
||||
sampler.cpp sampler.hpp
|
||||
command_pool.cpp command_pool.hpp
|
||||
fence_manager.cpp fence_manager.hpp
|
||||
descriptor_set.cpp descriptor_set.hpp
|
||||
semaphore_manager.cpp semaphore_manager.hpp
|
||||
command_buffer.cpp command_buffer.hpp
|
||||
shader.cpp shader.hpp
|
||||
render_pass.cpp render_pass.hpp
|
||||
buffer.cpp buffer.hpp
|
||||
rtas.cpp rtas.hpp
|
||||
indirect_layout.cpp indirect_layout.hpp
|
||||
pipeline_cache.cpp pipeline_cache.hpp
|
||||
semaphore.cpp semaphore.hpp
|
||||
memory_allocator.cpp memory_allocator.hpp
|
||||
fence.hpp fence.cpp
|
||||
format.hpp
|
||||
limits.hpp
|
||||
type_to_string.hpp
|
||||
quirks.hpp
|
||||
vulkan_common.hpp
|
||||
event_manager.cpp event_manager.hpp
|
||||
breadcrumbs.cpp breadcrumbs.hpp
|
||||
pipeline_event.cpp pipeline_event.hpp
|
||||
query_pool.cpp query_pool.hpp
|
||||
texture/texture_format.cpp texture/texture_format.hpp)
|
||||
|
||||
if (WIN32 AND GRANITE_VULKAN_DXGI_INTEROP)
|
||||
target_sources(granite-vulkan PRIVATE wsi_dxgi.cpp wsi_dxgi.hpp)
|
||||
target_compile_definitions(granite-vulkan PUBLIC HAVE_WSI_DXGI_INTEROP)
|
||||
endif()
|
||||
|
||||
target_include_directories(granite-vulkan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
if (GRANITE_RENDERDOC_CAPTURE)
|
||||
target_link_libraries(granite-vulkan PRIVATE granite-renderdoc-app)
|
||||
target_sources(granite-vulkan PRIVATE renderdoc_capture.cpp)
|
||||
if (NOT WIN32)
|
||||
target_link_libraries(granite-vulkan PRIVATE dl)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (GRANITE_VULKAN_SYSTEM_HANDLES)
|
||||
if (GRANITE_VULKAN_FOSSILIZE)
|
||||
target_compile_definitions(granite-vulkan PUBLIC GRANITE_VULKAN_FOSSILIZE)
|
||||
target_sources(granite-vulkan PRIVATE device_fossilize.cpp device_fossilize.hpp)
|
||||
target_link_libraries(granite-vulkan PUBLIC fossilize)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(granite-vulkan PUBLIC GRANITE_VULKAN_SYSTEM_HANDLES)
|
||||
target_sources(granite-vulkan PRIVATE
|
||||
managers/shader_manager.cpp
|
||||
managers/shader_manager.hpp
|
||||
managers/resource_manager.cpp
|
||||
managers/resource_manager.hpp)
|
||||
|
||||
target_sources(granite-vulkan PRIVATE
|
||||
texture/memory_mapped_texture.cpp texture/memory_mapped_texture.hpp
|
||||
mesh/meshlet.hpp mesh/meshlet.cpp
|
||||
texture/texture_files.cpp texture/texture_files.hpp
|
||||
texture/texture_decoder.cpp texture/texture_decoder.hpp)
|
||||
|
||||
target_link_libraries(granite-vulkan
|
||||
PUBLIC granite-filesystem
|
||||
PRIVATE granite-threading granite-rapidjson granite-stb granite-math)
|
||||
|
||||
target_include_directories(granite-vulkan PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/managers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/texture
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mesh)
|
||||
|
||||
if (GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER)
|
||||
target_compile_definitions(granite-vulkan PUBLIC GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER=1)
|
||||
target_link_libraries(granite-vulkan PRIVATE granite-compiler)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
|
||||
target_compile_definitions(granite-vulkan PUBLIC VULKAN_DEBUG)
|
||||
endif()
|
||||
|
||||
target_link_libraries(granite-vulkan
|
||||
PRIVATE granite-volk
|
||||
PUBLIC granite-util granite-volk-headers)
|
||||
|
||||
if (GRANITE_VULKAN_SPIRV_CROSS)
|
||||
target_link_libraries(granite-vulkan PRIVATE spirv-cross-core)
|
||||
target_compile_definitions(granite-vulkan PRIVATE GRANITE_VULKAN_SPIRV_CROSS=1)
|
||||
endif()
|
||||
|
||||
if (ANDROID AND GRANITE_ANDROID_SWAPPY)
|
||||
find_package(games-frame-pacing REQUIRED CONFIG)
|
||||
target_link_libraries(granite-vulkan PRIVATE games-frame-pacing::swappy)
|
||||
target_compile_definitions(granite-vulkan PRIVATE HAVE_SWAPPY)
|
||||
endif()
|
||||
|
||||
if (GRANITE_SHIPPING)
|
||||
target_compile_definitions(granite-vulkan PUBLIC GRANITE_SHIPPING)
|
||||
endif()
|
||||
|
||||
if (GRANITE_FFMPEG_VULKAN)
|
||||
target_compile_definitions(granite-vulkan PRIVATE HAVE_FFMPEG_VULKAN)
|
||||
endif()
|
||||
|
||||
if (GRANITE_VULKAN_PROFILES)
|
||||
# Must be defined by caller as an INTERFACE library before including Granite.
|
||||
if (NOT TARGET granite-vulkan-profiles)
|
||||
message(FATAL_ERROR "granite-vulkan-profiles is not a target. This must be defined by caller before add_subdirectory(Granite).")
|
||||
endif()
|
||||
target_link_libraries(granite-vulkan PRIVATE granite-vulkan-profiles)
|
||||
target_compile_definitions(granite-vulkan PRIVATE GRANITE_VULKAN_PROFILES)
|
||||
endif()
|
||||
|
||||
if (GRANITE_VULKAN_POST_MORTEM)
|
||||
add_subdirectory(post-mortem)
|
||||
target_link_libraries(granite-vulkan PRIVATE granite-vulkan-post-mortem)
|
||||
target_compile_definitions(granite-vulkan PUBLIC HAVE_GRANITE_VULKAN_POST_MORTEM)
|
||||
endif()
|
||||
@@ -0,0 +1,568 @@
|
||||
/* Copyright (c) 2017-2026 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 "breadcrumbs.hpp"
|
||||
#include "shader.hpp"
|
||||
#include "device.hpp"
|
||||
#include "timer.hpp"
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
void CheckpointString::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
void CheckpointDispatch::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "Dispatch (%u, %u, %u)\n", x, y, z);
|
||||
}
|
||||
|
||||
void CheckpointDraw::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "Draw (%u, %u, %d, %u)\n",
|
||||
vertex_count, instance_count, vertex_offset, instance_offset);
|
||||
}
|
||||
|
||||
void CheckpointDrawIndexed::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "DrawIndexed (%u, %u, %u, %d, %u)\n",
|
||||
index_count, instance_count, first_index, vertex_offset, instance_offset);
|
||||
}
|
||||
|
||||
void CheckpointMeshDispatch::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "MeshTasks (%u, %u, %u)\n", x, y, z);
|
||||
}
|
||||
|
||||
void CheckpointIndirectBase::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "%s (#%016llx)\n", tag, static_cast<unsigned long long>(va));
|
||||
}
|
||||
|
||||
void CheckpointMultiIndirectBase::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "%s (#%016llx), count %u, stride %u\n",
|
||||
tag,
|
||||
static_cast<unsigned long long>(va),
|
||||
count, stride);
|
||||
}
|
||||
|
||||
void CheckpointShader::report(FILE *file)
|
||||
{
|
||||
fprintf(file, "Shader (#%016llx)\n", static_cast<unsigned long long>(shader->get_hash()));
|
||||
}
|
||||
|
||||
static void *nv_encode_checkpoint(uint32_t index, uint32_t counter)
|
||||
{
|
||||
return reinterpret_cast<void *>(uintptr_t(index) + uintptr_t(counter) * BreadcrumbsTracker::MaxCommandBuffers);
|
||||
}
|
||||
|
||||
static uint32_t nv_decode_context(void *opaque)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(opaque) % BreadcrumbsTracker::MaxCommandBuffers;
|
||||
}
|
||||
|
||||
static uint32_t nv_decode_counter(void *opaque)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(opaque) / BreadcrumbsTracker::MaxCommandBuffers;
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::init(Device *device_)
|
||||
{
|
||||
device = device_;
|
||||
if (!device->get_device_features().supports_post_mortem)
|
||||
return;
|
||||
|
||||
active = true;
|
||||
|
||||
command_buffers.resize(MaxCommandBuffers);
|
||||
vacant_command_buffers.reserve(MaxCommandBuffers);
|
||||
for (uint32_t i = MaxCommandBuffers; i; i--)
|
||||
vacant_command_buffers.push_back(i - 1);
|
||||
|
||||
if (device->get_device_features().supports_amd_buffer_marker)
|
||||
{
|
||||
BufferCreateInfo info = {};
|
||||
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
info.domain = BufferDomain::DebugReadback;
|
||||
info.size = MaxCommandBuffers * sizeof(uint32_t) * 2;
|
||||
info.misc = BUFFER_MISC_ZERO_INITIALIZE_BIT;
|
||||
amd_marker_buffer = device->create_buffer(info).release();
|
||||
}
|
||||
|
||||
blocks.init(CheckpointObjectSize);
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::deinit()
|
||||
{
|
||||
for (auto &cmd : command_buffers)
|
||||
reset_command_buffer(cmd);
|
||||
if (amd_marker_buffer)
|
||||
amd_marker_buffer->release_reference();
|
||||
}
|
||||
|
||||
BufferMarkerHandle BreadcrumbsTracker::allocate_command_buffer(VkCommandBuffer cmd)
|
||||
{
|
||||
if (!active)
|
||||
return {};
|
||||
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
|
||||
if (vacant_command_buffers.empty())
|
||||
return {};
|
||||
|
||||
BufferMarkerHandle ret = { vacant_command_buffers.back() };
|
||||
vacant_command_buffers.pop_back();
|
||||
command_buffers[ret.index].cmd = cmd;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::free_command_buffer(BufferMarkerHandle handle)
|
||||
{
|
||||
if (handle.index == BufferMarkerHandle::Invalid)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
assert(handle.index < MaxCommandBuffers);
|
||||
vacant_command_buffers.push_back(handle.index);
|
||||
reset_command_buffer(command_buffers[handle.index]);
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::reset_command_buffer(CommandBuffer &cmd)
|
||||
{
|
||||
for (auto &check : cmd.checkpoints)
|
||||
{
|
||||
if (check.iface)
|
||||
{
|
||||
check.iface->~CheckpointReportInterface();
|
||||
blocks.free(reinterpret_cast<uint8_t *>(check.iface));
|
||||
}
|
||||
}
|
||||
|
||||
// Free the memory too to avoid extreme bloat.
|
||||
cmd = {};
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::begin(BufferMarkerHandle handle)
|
||||
{
|
||||
if (handle.index == BufferMarkerHandle::Invalid)
|
||||
return;
|
||||
|
||||
auto &cmd = command_buffers[handle.index];
|
||||
|
||||
cmd.counter++;
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, cmd.counter });
|
||||
|
||||
if (device->get_device_features().supports_nv_checkpoints)
|
||||
{
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, cmd.counter });
|
||||
// A checkpoint is implicitly a top and a bottom marker.
|
||||
device->get_device_table().vkCmdSetCheckpointNV(cmd.cmd, nv_encode_checkpoint(handle.index, cmd.counter));
|
||||
}
|
||||
else if (device->get_device_features().supports_amd_buffer_marker)
|
||||
{
|
||||
device->get_device_table().vkCmdWriteBufferMarkerAMD(cmd.cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
amd_marker_buffer->get_buffer(),
|
||||
(2 * handle.index + 0) * sizeof(uint32_t), cmd.counter);
|
||||
}
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::signal(BufferMarkerHandle handle)
|
||||
{
|
||||
if (handle.index == BufferMarkerHandle::Invalid)
|
||||
return;
|
||||
|
||||
auto &cmd = command_buffers[handle.index];
|
||||
|
||||
if (device->get_device_features().supports_nv_checkpoints)
|
||||
{
|
||||
cmd.counter++;
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, cmd.counter });
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, cmd.counter });
|
||||
device->get_device_table().vkCmdSetCheckpointNV(cmd.cmd, nv_encode_checkpoint(handle.index, cmd.counter));
|
||||
}
|
||||
else if (device->get_device_features().supports_amd_buffer_marker)
|
||||
{
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, cmd.counter });
|
||||
device->get_device_table().vkCmdWriteBufferMarkerAMD(cmd.cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
amd_marker_buffer->get_buffer(),
|
||||
(2 * handle.index + 1) * sizeof(uint32_t), cmd.counter);
|
||||
|
||||
cmd.counter++;
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, cmd.counter });
|
||||
device->get_device_table().vkCmdWriteBufferMarkerAMD(cmd.cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
amd_marker_buffer->get_buffer(),
|
||||
(2 * handle.index + 0) * sizeof(uint32_t), cmd.counter);
|
||||
}
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::end(BufferMarkerHandle handle)
|
||||
{
|
||||
if (handle.index == BufferMarkerHandle::Invalid)
|
||||
return;
|
||||
|
||||
auto &cmd = command_buffers[handle.index];
|
||||
cmd.counter = UINT32_MAX;
|
||||
|
||||
if (device->get_device_features().supports_nv_checkpoints)
|
||||
{
|
||||
device->get_device_table().vkCmdSetCheckpointNV(cmd.cmd, nv_encode_checkpoint(handle.index, cmd.counter));
|
||||
}
|
||||
else if (device->get_device_features().supports_amd_buffer_marker)
|
||||
{
|
||||
device->get_device_table().vkCmdWriteBufferMarkerAMD(cmd.cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
amd_marker_buffer->get_buffer(),
|
||||
(2 * handle.index + 0) * sizeof(uint32_t), cmd.counter);
|
||||
|
||||
device->get_device_table().vkCmdWriteBufferMarkerAMD(cmd.cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
amd_marker_buffer->get_buffer(),
|
||||
(2 * handle.index + 1) * sizeof(uint32_t), cmd.counter);
|
||||
}
|
||||
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, cmd.counter });
|
||||
cmd.checkpoints.push_back({ nullptr, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, cmd.counter });
|
||||
|
||||
// Avoid breadcrumbs spilling between command buffers.
|
||||
VkDependencyInfo dep = { VK_STRUCTURE_TYPE_DEPENDENCY_INFO };
|
||||
VkMemoryBarrier2 bar = { VK_STRUCTURE_TYPE_MEMORY_BARRIER_2 };
|
||||
dep.memoryBarrierCount = 1;
|
||||
dep.pMemoryBarriers = &bar;
|
||||
bar.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
bar.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
|
||||
bar.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
bar.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT;
|
||||
device->get_device_table().vkCmdPipelineBarrier2(cmd.cmd, &dep);
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::report_command_list(FILE *file, CommandBuffer &cmd, uint32_t top_marker, uint32_t bottom_marker)
|
||||
{
|
||||
bool observed_begin_cmd = false;
|
||||
bool observed_end_cmd = false;
|
||||
|
||||
fprintf(file, "\n=== Command Buffer ===\n");
|
||||
|
||||
if (bottom_marker == 0)
|
||||
{
|
||||
fprintf(file, "=== Crash region BEGIN ===\n");
|
||||
observed_begin_cmd = true;
|
||||
}
|
||||
|
||||
for (auto &check : cmd.checkpoints)
|
||||
{
|
||||
if (!observed_end_cmd && check.stages == VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT && check.counter > top_marker)
|
||||
{
|
||||
// The command processor did not reach this checkpoint. Any command after this point cannot be the culprit.
|
||||
fprintf(file, "=== Crash region END ===\n");
|
||||
observed_end_cmd = true;
|
||||
}
|
||||
|
||||
if (check.iface)
|
||||
check.iface->report(file);
|
||||
|
||||
if (!observed_begin_cmd && check.stages == VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT && check.counter == bottom_marker)
|
||||
{
|
||||
// The GPU completed all commands up to this point and is the last counter that was completed.
|
||||
// Crash must be after this point.
|
||||
fprintf(file, "=== Crash region BEGIN ===\n");
|
||||
observed_begin_cmd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (top_marker == UINT32_MAX)
|
||||
fprintf(file, "=== Crash region END ===\n");
|
||||
|
||||
fprintf(file, "====================\n");
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::report_command_list_amd(FILE *file, uint32_t index)
|
||||
{
|
||||
auto &cmd = command_buffers[index];
|
||||
|
||||
// Unused, cannot be the culprit.
|
||||
if (cmd.counter == 0)
|
||||
return;
|
||||
|
||||
auto *ptr = static_cast<const uint32_t *>(device->map_host_buffer(*amd_marker_buffer, MEMORY_ACCESS_READ_BIT, 2 * sizeof(uint32_t) * index, sizeof(uint32_t) * 2));
|
||||
uint32_t top_marker = ptr[0];
|
||||
uint32_t bottom_marker = ptr[1];
|
||||
|
||||
// The command buffer is done executing.
|
||||
if (top_marker == UINT32_MAX && bottom_marker == UINT32_MAX)
|
||||
return;
|
||||
|
||||
// Never started executing properly. Cannot be a culprit.
|
||||
if (top_marker == 0 && bottom_marker == 0)
|
||||
return;
|
||||
|
||||
// Edge case where we crashed before the first command of a recycled command buffer completed.
|
||||
if (top_marker > 0 && bottom_marker == UINT32_MAX)
|
||||
bottom_marker = 0;
|
||||
|
||||
fprintf(file, "Reporting for command index %u, top marker %u, bottom marker %u\n", index, top_marker, bottom_marker);
|
||||
report_command_list(file, cmd, top_marker, bottom_marker);
|
||||
reported = true;
|
||||
}
|
||||
|
||||
void BreadcrumbsTracker::notify_device_hung()
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
if (reported)
|
||||
return;
|
||||
|
||||
char path[256];
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm gmt;
|
||||
|
||||
// Date-time in C was always a great time :')
|
||||
#ifdef _WIN32
|
||||
gmtime_s(&gmt, &t);
|
||||
#else
|
||||
gmtime_r(&t, &gmt);
|
||||
#endif
|
||||
|
||||
// Windows does not like colons in path names, so %T breaks.
|
||||
strftime(path, sizeof(path), "granite-post-mortem-%Y-%m-%d-%H-%M-%S.txt", &gmt);
|
||||
|
||||
LOGE("Device hung ... Attempting to grab post-mortem data to: %s\n", path);
|
||||
|
||||
auto start_time = Util::get_current_time_nsecs();
|
||||
auto end_time = start_time + 5ll * 1000 * 1000 * 1000;
|
||||
|
||||
// Try to observe device lost properly.
|
||||
VkResult vr = VK_SUCCESS;
|
||||
while (vr != VK_ERROR_DEVICE_LOST && Util::get_current_time_nsecs() < end_time)
|
||||
vr = device->get_device_table().vkDeviceWaitIdle(device->get_device());
|
||||
|
||||
if (vr == VK_ERROR_DEVICE_LOST)
|
||||
LOGE("Observed device lost after %.3f seconds of blocking.\n", 1e-9 * (Util::get_current_time_nsecs() - start_time));
|
||||
|
||||
FILE *file = fopen(path, "w");
|
||||
if (!file)
|
||||
{
|
||||
LOGE("Failed to open \"%s\", dumping to stderr instead.\n", path);
|
||||
file = stderr;
|
||||
}
|
||||
|
||||
if (vr != VK_ERROR_DEVICE_LOST)
|
||||
{
|
||||
if (file != stderr)
|
||||
LOGE("Cannot observe device lost state, report may be incomplete ...\n");
|
||||
fprintf(file, "Cannot observe device lost state, report may be incomplete ...\n");
|
||||
}
|
||||
|
||||
fprintf(file, "Post-mortem analysis ...\n");
|
||||
|
||||
if (device->get_device_features().supports_nv_checkpoints)
|
||||
{
|
||||
auto &queues = device->get_queue_info().queues;
|
||||
for (uint32_t i = 0; i < QUEUE_INDEX_COUNT; i++)
|
||||
{
|
||||
if (queues[i] == VK_NULL_HANDLE || std::find(queues, queues + i, queues[i]) != queues + i)
|
||||
continue;
|
||||
|
||||
auto &table = device->get_device_table();
|
||||
uint32_t count;
|
||||
table.vkGetQueueCheckpointDataNV(queues[i], &count, nullptr);
|
||||
|
||||
if (count == 0)
|
||||
continue;
|
||||
|
||||
std::vector<VkCheckpointDataNV> checkpoints(count);
|
||||
for (auto &check : checkpoints)
|
||||
check.sType = VK_STRUCTURE_TYPE_CHECKPOINT_DATA_NV;
|
||||
table.vkGetQueueCheckpointDataNV(queues[i], &count, checkpoints.data());
|
||||
|
||||
uint32_t top_marker = 0;
|
||||
uint32_t bottom_marker = 0;
|
||||
uint32_t top_context = BufferMarkerHandle::Invalid;
|
||||
uint32_t bottom_context = BufferMarkerHandle::Invalid;
|
||||
|
||||
for (auto &check : checkpoints)
|
||||
{
|
||||
uint32_t context = nv_decode_context(check.pCheckpointMarker);
|
||||
uint32_t counter = nv_decode_counter(check.pCheckpointMarker);
|
||||
|
||||
if (check.stage == VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT)
|
||||
{
|
||||
top_marker = counter;
|
||||
top_context = context;
|
||||
}
|
||||
else if (check.stage == VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT)
|
||||
{
|
||||
bottom_marker = counter;
|
||||
bottom_context = context;
|
||||
}
|
||||
}
|
||||
|
||||
if (top_context == BufferMarkerHandle::Invalid)
|
||||
fprintf(file, "Missing context, this should not happen.\n");
|
||||
else if (top_context != bottom_context || top_context == BufferMarkerHandle::Invalid)
|
||||
fprintf(file, "Mismatching contexts, this should not happen.\n");
|
||||
else
|
||||
{
|
||||
report_command_list(file, command_buffers[top_context], top_marker, bottom_marker);
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (device->get_device_features().supports_amd_buffer_marker)
|
||||
{
|
||||
for (uint32_t i = 0; i < MaxCommandBuffers; i++)
|
||||
report_command_list_amd(file, i);
|
||||
}
|
||||
|
||||
// Need to observe the device lost properly first before we can query fault information.
|
||||
auto &table = device->get_device_table();
|
||||
|
||||
const auto addr_type_to_str = [](VkDeviceFaultAddressTypeKHR type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_NONE_KHR: return "None";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_READ_INVALID_KHR: return "ReadInvalid";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_KHR: return "WriteInvalid";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_EXECUTE_INVALID_KHR: return "ExecuteInvalid";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_UNKNOWN_KHR: return "IPUnknown";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_INVALID_KHR: return "IPInvalid";
|
||||
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_FAULT_KHR: return "IPFault";
|
||||
default: return "???";
|
||||
}
|
||||
};
|
||||
|
||||
const auto report_address = [&](const char *tag, const VkDeviceFaultAddressInfoKHR &info)
|
||||
{
|
||||
fprintf(file, " %s fault: %s\n", tag, addr_type_to_str(info.addressType));
|
||||
fprintf(file, " %s address: #%016llx\n", tag,
|
||||
static_cast<unsigned long long>(info.reportedAddress));
|
||||
fprintf(file, " %s precision: #%016llx\n", tag,
|
||||
static_cast<unsigned long long>(info.addressPrecision));
|
||||
};
|
||||
|
||||
const auto report_vendor = [&](const VkDeviceFaultVendorInfoKHR &info)
|
||||
{
|
||||
fprintf(file, " Vendor desc: %s\n", info.description);
|
||||
fprintf(file, " Vendor fault code: %llu\n",
|
||||
static_cast<unsigned long long>(info.vendorFaultCode));
|
||||
fprintf(file, " Vendor fault data: %llu\n",
|
||||
static_cast<unsigned long long>(info.vendorFaultData));
|
||||
};
|
||||
|
||||
if (device->get_device_features().fault_features_khr.deviceFault)
|
||||
{
|
||||
std::vector<VkDeviceFaultInfoKHR> faults;
|
||||
uint32_t count;
|
||||
|
||||
if (table.vkGetDeviceFaultReportsKHR(device->get_device(), UINT64_MAX, &count, nullptr) != VK_SUCCESS)
|
||||
{
|
||||
fprintf(file, "Failed to get fault reports.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
faults.resize(count);
|
||||
for (auto &fault : faults)
|
||||
fault.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_KHR;
|
||||
|
||||
if (table.vkGetDeviceFaultReportsKHR(device->get_device(), UINT64_MAX, &count, faults.data()) != VK_SUCCESS)
|
||||
{
|
||||
fprintf(file, "Failed to get fault reports.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &fault : faults)
|
||||
{
|
||||
fprintf(file, "=== Fault ===\n");
|
||||
fprintf(file, " Desc: %s\n", fault.description);
|
||||
fprintf(file, " groupID: %llu\n", static_cast<unsigned long long>(fault.groupId));
|
||||
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_DEVICE_LOST_KHR)
|
||||
fprintf(file, " Fault caused DEVICE_LOST\n");
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_WATCHDOG_TIMEOUT_KHR)
|
||||
fprintf(file, " GPU Timeout\n");
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_OVERFLOW_KHR)
|
||||
fprintf(file, " Fault buffer overflowed\n");
|
||||
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_VENDOR_KHR)
|
||||
report_vendor(fault.vendorInfo);
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_MEMORY_ADDRESS_KHR)
|
||||
report_address("Memory", fault.faultAddressInfo);
|
||||
if (fault.flags & VK_DEVICE_FAULT_FLAG_INSTRUCTION_ADDRESS_KHR)
|
||||
report_address("Instruction ", fault.instructionAddressInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VkDeviceFaultCountsEXT counts = { VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT };
|
||||
VkDeviceFaultInfoEXT fault = { VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT };
|
||||
|
||||
if (table.vkGetDeviceFaultInfoEXT(device->get_device(), &counts, nullptr) != VK_SUCCESS)
|
||||
{
|
||||
fprintf(file, "Failed to get fault reports.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<VkDeviceFaultAddressInfoEXT> addresses(counts.addressInfoCount);
|
||||
std::vector<VkDeviceFaultVendorInfoEXT> vendor_infos(counts.vendorInfoCount);
|
||||
uint8_t *vendor_data = counts.vendorBinarySize ? new uint8_t[counts.vendorBinarySize] : nullptr;
|
||||
|
||||
fault.pAddressInfos = addresses.data();
|
||||
fault.pVendorInfos = vendor_infos.data();
|
||||
fault.pVendorBinaryData = vendor_data;
|
||||
|
||||
if (table.vkGetDeviceFaultInfoEXT(device->get_device(), &counts, &fault) != VK_SUCCESS)
|
||||
{
|
||||
fprintf(file, "Failed to get fault reports.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < counts.addressInfoCount; i++)
|
||||
report_address("Memory", addresses[i]);
|
||||
for (uint32_t i = 0; i < counts.vendorInfoCount; i++)
|
||||
report_vendor(vendor_infos[i]);
|
||||
|
||||
delete[] vendor_data;
|
||||
}
|
||||
|
||||
fprintf(file, "... DONE\n");
|
||||
|
||||
if (file != stderr)
|
||||
fclose(file);
|
||||
|
||||
LOGE("Completed post-mortem analysis, will crash now.\n");
|
||||
#ifdef _WIN32
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "GPU crashed, see post-mortem log in %s. Application will now terminate.", path);
|
||||
MessageBoxA(nullptr, msg, "Granite Post Mortem", MB_OK);
|
||||
TerminateProcess(GetCurrentProcess(), 1);
|
||||
#endif
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/* Copyright (c) 2017-2026 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 "slab_allocator.hpp"
|
||||
#include "vulkan_headers.hpp"
|
||||
#include "vulkan_common.hpp"
|
||||
#include <mutex>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class Device;
|
||||
class Buffer;
|
||||
class Shader;
|
||||
|
||||
struct CheckpointReportInterface
|
||||
{
|
||||
virtual void report(FILE *file) = 0;
|
||||
virtual ~CheckpointReportInterface() = default;
|
||||
};
|
||||
|
||||
struct CheckpointString : CheckpointReportInterface
|
||||
{
|
||||
CheckpointString(std::string str_) : str(std::move(str_)) {}
|
||||
void report(FILE *file) override;
|
||||
std::string str;
|
||||
};
|
||||
|
||||
struct CheckpointDispatch : CheckpointReportInterface
|
||||
{
|
||||
CheckpointDispatch(uint32_t x_, uint32_t y_, uint32_t z_)
|
||||
: x(x_), y(y_), z(z_) {}
|
||||
void report(FILE *file) override;
|
||||
uint32_t x, y, z;
|
||||
};
|
||||
|
||||
struct CheckpointDraw : CheckpointReportInterface
|
||||
{
|
||||
CheckpointDraw(uint32_t vertex_count_, uint32_t instance_count_, int32_t vertex_offset_, uint32_t instance_offset_)
|
||||
: vertex_count(vertex_count_), instance_count(instance_count_)
|
||||
, vertex_offset(vertex_offset_), instance_offset(instance_offset_) {}
|
||||
|
||||
void report(FILE *file) override;
|
||||
uint32_t vertex_count;
|
||||
uint32_t instance_count;
|
||||
int32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
};
|
||||
|
||||
struct CheckpointDrawIndexed : CheckpointReportInterface
|
||||
{
|
||||
CheckpointDrawIndexed(uint32_t index_count_, uint32_t instance_count_, uint32_t first_index_, int32_t vertex_offset_, uint32_t instance_offset_)
|
||||
: index_count(index_count_), instance_count(instance_count_), first_index(first_index_)
|
||||
, vertex_offset(vertex_offset_), instance_offset(instance_offset_) {}
|
||||
|
||||
void report(FILE *file) override;
|
||||
uint32_t index_count;
|
||||
uint32_t instance_count;
|
||||
uint32_t first_index;
|
||||
int32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
};
|
||||
|
||||
struct CheckpointMeshDispatch : CheckpointReportInterface
|
||||
{
|
||||
CheckpointMeshDispatch(uint32_t x_, uint32_t y_, uint32_t z_)
|
||||
: x(x_), y(y_), z(z_) {}
|
||||
void report(FILE *file) override;
|
||||
uint32_t x, y, z;
|
||||
};
|
||||
|
||||
struct CheckpointIndirectBase : CheckpointReportInterface
|
||||
{
|
||||
CheckpointIndirectBase(const char *tag_, VkDeviceAddress va_) : tag(tag_), va(va_) {}
|
||||
|
||||
void report(FILE *file) override;
|
||||
|
||||
const char *tag;
|
||||
VkDeviceAddress va;
|
||||
};
|
||||
|
||||
struct CheckpointMultiIndirectBase : CheckpointReportInterface
|
||||
{
|
||||
CheckpointMultiIndirectBase(const char *tag_, VkDeviceAddress va_,
|
||||
uint32_t count_, uint32_t stride_)
|
||||
: tag(tag_), va(va_), count(count_), stride(stride_) {}
|
||||
|
||||
void report(FILE *file) override;
|
||||
|
||||
const char *tag;
|
||||
VkDeviceAddress va;
|
||||
uint32_t count;
|
||||
uint32_t stride;
|
||||
};
|
||||
|
||||
struct CheckpointMultiIndirectCountBase : CheckpointReportInterface
|
||||
{
|
||||
CheckpointMultiIndirectCountBase(const char *tag_, VkDeviceAddress va_, VkDeviceAddress count_va_,
|
||||
uint32_t count_, uint32_t stride_)
|
||||
: tag(tag_), va(va_), count_va(count_va_), count(count_), stride(stride_) {}
|
||||
|
||||
void report(FILE *file) override
|
||||
{
|
||||
fprintf(file, "%s (#%016llx), countVA (#%016llx), count %u, stride %u\n",
|
||||
tag,
|
||||
static_cast<unsigned long long>(va),
|
||||
static_cast<unsigned long long>(count_va),
|
||||
count, stride);
|
||||
}
|
||||
|
||||
const char *tag;
|
||||
VkDeviceAddress va;
|
||||
VkDeviceAddress count_va;
|
||||
uint32_t count;
|
||||
uint32_t stride;
|
||||
};
|
||||
|
||||
struct CheckpointShader : CheckpointReportInterface
|
||||
{
|
||||
CheckpointShader(const Shader *shader_) : shader(shader_) {}
|
||||
void report(FILE *file) override;
|
||||
const Shader *shader;
|
||||
};
|
||||
|
||||
// 1 second.
|
||||
// It seems like if we have too long timeout, NV driver on Windows is broken
|
||||
// and loses checkpoint information (?!?!).
|
||||
// It seems like we have to call it before we start getting vkQueueSubmit() device losts,
|
||||
// then it's too late.
|
||||
static constexpr uint64_t PostMortemTimeout = 1ull * 1000 * 1000 * 1000;
|
||||
|
||||
class BreadcrumbsTracker
|
||||
{
|
||||
public:
|
||||
enum { CheckpointObjectSize = 64, MaxCommandBuffers = 8 * 1024 };
|
||||
void init(Device *device);
|
||||
void deinit();
|
||||
BufferMarkerHandle allocate_command_buffer(VkCommandBuffer cmd);
|
||||
void free_command_buffer(BufferMarkerHandle handle);
|
||||
|
||||
void begin(BufferMarkerHandle handle);
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void checkpoint(BufferMarkerHandle handle, Ts &&... ts)
|
||||
{
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
static_assert(sizeof(T) <= CheckpointObjectSize, "Object size is too large.");
|
||||
auto *raw = reinterpret_cast<T *>(blocks.allocate());
|
||||
new (raw) T(std::forward<Ts>(ts)...);
|
||||
|
||||
Checkpoint checkpoint = {};
|
||||
checkpoint.iface = raw;
|
||||
assert(handle.index < command_buffers.size());
|
||||
command_buffers[handle.index].checkpoints.push_back(checkpoint);
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void checkpoint_with_signal(BufferMarkerHandle handle, Ts &&... ts)
|
||||
{
|
||||
checkpoint<T>(handle, std::forward<Ts>(ts)...);
|
||||
signal(handle);
|
||||
}
|
||||
|
||||
void signal(BufferMarkerHandle handle);
|
||||
void end(BufferMarkerHandle handle);
|
||||
|
||||
void notify_device_hung();
|
||||
|
||||
private:
|
||||
Device *device = nullptr;
|
||||
bool active = false;
|
||||
Buffer *amd_marker_buffer = nullptr;
|
||||
std::mutex lock;
|
||||
bool reported = false;
|
||||
|
||||
struct Checkpoint
|
||||
{
|
||||
CheckpointReportInterface *iface;
|
||||
VkPipelineStageFlags2 stages;
|
||||
uint32_t counter;
|
||||
};
|
||||
|
||||
struct CommandBuffer
|
||||
{
|
||||
std::vector<Checkpoint> checkpoints;
|
||||
uint32_t counter = 0;
|
||||
VkCommandBuffer cmd;
|
||||
};
|
||||
|
||||
std::vector<CommandBuffer> command_buffers;
|
||||
std::vector<uint32_t> vacant_command_buffers;
|
||||
Util::ThreadSafeSlabAllocator blocks;
|
||||
|
||||
void reset_command_buffer(CommandBuffer &cmd);
|
||||
void report_command_list_amd(FILE *file, uint32_t index);
|
||||
void report_command_list(FILE *file, CommandBuffer &cmd, uint32_t top_marker, uint32_t bottom_marker);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* Copyright (c) 2017-2026 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 (owns_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_,
|
||||
const CachedBufferView &view_,
|
||||
const BufferViewCreateInfo &create_info_)
|
||||
: Cookie(device_)
|
||||
, device(device_)
|
||||
, view(view_)
|
||||
, info(create_info_)
|
||||
{
|
||||
}
|
||||
|
||||
BufferView::~BufferView()
|
||||
{
|
||||
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,183 @@
|
||||
/* Copyright (c) 2017-2026 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
|
||||
UMACachedCoherentPreferDevice, // Aim for DEVICE | CACHED | COHERENT, but fallback to plain DEVICE if not supported.
|
||||
DebugReadback // DEVICE_COHERENT + HOST_COHERENT. For AMD_buffer_marker and other breadcrumbs.
|
||||
};
|
||||
|
||||
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;
|
||||
VkBufferUsageFlags2 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;
|
||||
}
|
||||
|
||||
void disown_buffer()
|
||||
{
|
||||
owns_buffer = false;
|
||||
}
|
||||
|
||||
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;
|
||||
bool owns_buffer = true;
|
||||
};
|
||||
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
|
||||
{
|
||||
VK_ASSERT(view.view);
|
||||
return view.view;
|
||||
}
|
||||
|
||||
const CachedDescriptorPayload &get_uniform_payload() const
|
||||
{
|
||||
VK_ASSERT(view.uniform.ptr && view.uniform.type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER);
|
||||
return view.uniform;
|
||||
}
|
||||
|
||||
const CachedDescriptorPayload &get_storage_payload() const
|
||||
{
|
||||
VK_ASSERT(view.storage.ptr && view.storage.type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER);
|
||||
return view.storage;
|
||||
}
|
||||
|
||||
const BufferViewCreateInfo &get_create_info()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
const Buffer &get_buffer() const
|
||||
{
|
||||
return *info.buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Util::ObjectPool<BufferView>;
|
||||
BufferView(Device *device, const CachedBufferView &view, const BufferViewCreateInfo &info);
|
||||
|
||||
Device *device;
|
||||
CachedBufferView view;
|
||||
BufferViewCreateInfo info;
|
||||
};
|
||||
using BufferViewHandle = Util::IntrusivePtr<BufferView>;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/* Copyright (c) 2017-2026 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.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
#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_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;
|
||||
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;
|
||||
return { ret, buffer, aligned_offset, allocate_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,91 @@
|
||||
/* Copyright (c) 2017-2026 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);
|
||||
bool is_mapped() const { return mapped != nullptr; }
|
||||
const Buffer &get_buffer() const { return *buffer; }
|
||||
void unmap(Device &device);
|
||||
|
||||
VkDeviceSize get_offset() const { return offset; }
|
||||
VkDeviceSize get_size() const { return size; }
|
||||
|
||||
private:
|
||||
friend class BufferPool;
|
||||
Util::IntrusivePtr<Buffer> buffer;
|
||||
VkDeviceSize offset = 0;
|
||||
VkDeviceSize alignment = 0;
|
||||
VkDeviceSize size = 0;
|
||||
uint8_t *mapped = nullptr;
|
||||
};
|
||||
|
||||
class BufferPool
|
||||
{
|
||||
public:
|
||||
~BufferPool();
|
||||
void init(Device *device, VkDeviceSize block_size, VkDeviceSize alignment, VkBufferUsageFlags usage);
|
||||
void reset();
|
||||
|
||||
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;
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,180 @@
|
||||
/* Copyright (c) 2017-2026 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-2026 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,458 @@
|
||||
/* Copyright (c) 2017-2026 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_video_decode_av1 = 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_video_encode_av1 = 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_low_latency2_nv = false;
|
||||
bool supports_drm_modifiers = false;
|
||||
bool supports_descriptor_buffer = false;
|
||||
bool supports_amd_buffer_marker = false;
|
||||
bool supports_nv_checkpoints = false;
|
||||
bool supports_post_mortem = false;
|
||||
|
||||
bool supports_descriptor_buffer_or_heap = false;
|
||||
uint32_t resource_heap_offset_alignment = 0;
|
||||
uint32_t resource_heap_resource_desc_size = 0;
|
||||
uint32_t resource_heap_resource_desc_size_log2 = 0;
|
||||
|
||||
VkPhysicalDeviceFeatures enabled_features = {};
|
||||
|
||||
VkPhysicalDeviceVulkan11Features vk11_features = {};
|
||||
VkPhysicalDeviceVulkan12Features vk12_features = {};
|
||||
VkPhysicalDeviceVulkan13Features vk13_features = {};
|
||||
VkPhysicalDeviceVulkan14Features vk14_features = {};
|
||||
VkPhysicalDeviceVulkan11Properties vk11_props = {};
|
||||
VkPhysicalDeviceVulkan12Properties vk12_props = {};
|
||||
VkPhysicalDeviceVulkan13Properties vk13_props = {};
|
||||
VkPhysicalDeviceVulkan14Properties vk14_props = {};
|
||||
|
||||
// KHR
|
||||
VkPhysicalDeviceComputeShaderDerivativesFeaturesKHR compute_shader_derivative_features = {};
|
||||
VkPhysicalDevicePerformanceQueryFeaturesKHR performance_query_features = {};
|
||||
VkPhysicalDevicePresentIdFeaturesKHR present_id_features = {};
|
||||
VkPhysicalDevicePresentId2FeaturesKHR present_id2_features = {};
|
||||
VkPhysicalDevicePresentWaitFeaturesKHR present_wait_features = {};
|
||||
VkPhysicalDevicePresentWait2FeaturesKHR present_wait2_features = {};
|
||||
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric_features = {};
|
||||
VkPhysicalDeviceVideoMaintenance1FeaturesKHR video_maintenance1_features = {};
|
||||
VkPhysicalDevicePipelineBinaryFeaturesKHR pipeline_binary_features = {};
|
||||
VkPhysicalDevicePipelineBinaryPropertiesKHR pipeline_binary_properties = {};
|
||||
VkDevicePipelineBinaryInternalCacheControlKHR pipeline_binary_internal_cache_control = {};
|
||||
VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5_features = {};
|
||||
VkPhysicalDeviceVideoEncodeAV1FeaturesKHR av1_features = {};
|
||||
VkPhysicalDeviceAccelerationStructureFeaturesKHR rtas_features = {};
|
||||
VkPhysicalDeviceAccelerationStructurePropertiesKHR rtas_properties = {};
|
||||
VkPhysicalDeviceRayQueryFeaturesKHR ray_query_features = {};
|
||||
VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untyped_pointers_features = {};
|
||||
VkPhysicalDeviceFaultFeaturesKHR fault_features_khr = {};
|
||||
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cooperative_matrix_features = {};
|
||||
|
||||
// EXT
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT host_memory_properties = {};
|
||||
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_rasterization_properties = {};
|
||||
VkPhysicalDeviceMemoryPriorityFeaturesEXT memory_priority_features = {};
|
||||
VkPhysicalDeviceASTCDecodeFeaturesEXT astc_decode_features = {};
|
||||
VkPhysicalDeviceSwapchainMaintenance1FeaturesKHR 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 = {};
|
||||
VkPhysicalDeviceDeviceGeneratedCommandsFeaturesEXT device_generated_commands_features = {};
|
||||
VkPhysicalDeviceDeviceGeneratedCommandsPropertiesEXT device_generated_commands_properties = {};
|
||||
VkPhysicalDeviceRobustness2FeaturesEXT robustness2_features = {};
|
||||
VkPhysicalDeviceDescriptorBufferFeaturesEXT descriptor_buffer_features = {};
|
||||
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer_properties = {};
|
||||
VkPhysicalDevicePresentTimingFeaturesEXT present_timing_features = {};
|
||||
VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptor_heap_features = {};
|
||||
VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties = {};
|
||||
VkPhysicalDeviceFaultFeaturesEXT fault_features_ext = {};
|
||||
|
||||
// Vendor
|
||||
VkPhysicalDeviceDescriptorPoolOverallocationFeaturesNV descriptor_pool_overallocation_features = {};
|
||||
VkPhysicalDeviceAntiLagFeaturesAMD anti_lag_features = {};
|
||||
VkPhysicalDeviceCoherentMemoryFeaturesAMD coherent_memory_features = {};
|
||||
VkPhysicalDeviceShaderMixedFloatDotProductFeaturesVALVE shader_mixed_float_dot_product_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 = {};
|
||||
VkPhysicalDeviceSynchronization2Features sync2_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,
|
||||
CONTEXT_CREATION_ENABLE_PIPELINE_BINARY_BIT = 1 << 5,
|
||||
CONTEXT_CREATION_ENABLE_PUSH_DESCRIPTOR_BIT = 1 << 6,
|
||||
CONTEXT_CREATION_ENABLE_ROBUSTNESS_2_BIT = 1 << 7,
|
||||
CONTEXT_CREATION_ENABLE_VIDEO_AV1_BIT = 1 << 8,
|
||||
CONTEXT_CREATION_ENABLE_DESCRIPTOR_BUFFER_BIT = 1 << 9,
|
||||
CONTEXT_CREATION_ENABLE_DESCRIPTOR_HEAP_BIT = 1 << 10,
|
||||
CONTEXT_CREATION_ENABLE_POST_MORTEM_BIT = 1 << 11,
|
||||
CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT = 1 << 12,
|
||||
};
|
||||
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;
|
||||
|
||||
// Lifetime of any data in create info must remain as long as Context is alive.
|
||||
virtual const VkInstanceCreateInfo *get_existing_create_info();
|
||||
virtual bool factory_owns_created_instance();
|
||||
};
|
||||
|
||||
struct DeviceFactory
|
||||
{
|
||||
virtual ~DeviceFactory() = default;
|
||||
virtual VkDevice create_device(VkPhysicalDevice gpu, const VkDeviceCreateInfo *info) = 0;
|
||||
|
||||
// Lifetime of any data in create info must remain as long as Context is alive.
|
||||
// If pNext is not NULL, the first link in the pNext chain must be VkPhysicalDeviceFeatures2.
|
||||
virtual const VkDeviceCreateInfo *get_existing_create_info();
|
||||
virtual bool factory_owns_created_device();
|
||||
virtual VkQueue get_queue(uint32_t family_index, uint32_t index);
|
||||
};
|
||||
|
||||
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 to 1.4 instance is created depending on support.
|
||||
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;
|
||||
void physical_device_feature_query(VkPhysicalDeviceFeatures2 *pdf2) 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-2026 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-2026 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,672 @@
|
||||
/* Copyright (c) 2017-2026 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.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
#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.meta[0].array_size == 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)
|
||||
{
|
||||
if (!device->ext.supports_descriptor_buffer)
|
||||
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT;
|
||||
info.pNext = &flags;
|
||||
|
||||
flags.bindingCount = 1;
|
||||
flags.pBindingFlags = &binding_flags;
|
||||
|
||||
binding_flags = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
|
||||
if (!device->ext.supports_descriptor_buffer)
|
||||
{
|
||||
// These flags are implied when using descriptor buffer.
|
||||
binding_flags |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT |
|
||||
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (device->ext.supports_descriptor_buffer)
|
||||
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_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.meta[i].array_size;
|
||||
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))
|
||||
{
|
||||
auto type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
bindings.push_back({ i, type, array_size, stages, nullptr });
|
||||
pool_size.push_back({ type, 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.rtas_mask & (1u << i))
|
||||
{
|
||||
bindings.push_back({ i, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, array_size, stages, nullptr });
|
||||
pool_size.push_back({ VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 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])
|
||||
{
|
||||
if (!device->get_device_features().supports_descriptor_buffer)
|
||||
vk_immutable_samplers[i] = immutable_samplers[i]->get_sampler().get_sampler();
|
||||
else
|
||||
LOGE("Cannot use immutable samplers with descriptor buffer. Ignoring.\n");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool heap = device->get_device_features().descriptor_heap_features.descriptorHeap == VK_TRUE;
|
||||
|
||||
if (!heap)
|
||||
{
|
||||
#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.");
|
||||
}
|
||||
|
||||
if (device->ext.supports_descriptor_buffer && !heap)
|
||||
{
|
||||
// Query the memory layout.
|
||||
table.vkGetDescriptorSetLayoutSizeEXT(device->get_device(), set_layout_pool, &desc_set_size);
|
||||
|
||||
if (bindless)
|
||||
{
|
||||
table.vkGetDescriptorSetLayoutBindingOffsetEXT(
|
||||
device->get_device(), set_layout_pool, 0, &desc_set_variable_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto &bind : bindings)
|
||||
{
|
||||
VkDeviceSize offset = 0;
|
||||
VkDeviceSize stride = device->managers.descriptor_buffer.get_descriptor_size_for_type(bind.descriptorType);
|
||||
|
||||
table.vkGetDescriptorSetLayoutBindingOffsetEXT(
|
||||
device->get_device(), set_layout_pool, bind.binding, &offset);
|
||||
|
||||
for (uint32_t i = 0; i < bind.descriptorCount; i++)
|
||||
desc_offsets[bind.binding + i] = offset + i * stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GRANITE_VULKAN_FOSSILIZE
|
||||
if (device->ext.supports_descriptor_buffer && !heap)
|
||||
{
|
||||
// Normalize the recorded flags.
|
||||
if (bindless)
|
||||
{
|
||||
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT;
|
||||
binding_flags |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT |
|
||||
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT;
|
||||
}
|
||||
|
||||
info.flags &= ~VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
}
|
||||
|
||||
if (set_layout_pool)
|
||||
device->register_descriptor_set_layout(set_layout_pool, get_hash(), info);
|
||||
#endif
|
||||
|
||||
// Push descriptors is not used with descriptor buffer.
|
||||
if (!bindless && device->get_device_features().vk14_features.pushDescriptor &&
|
||||
!heap && !device->get_device_features().descriptor_buffer_features.descriptorBuffer)
|
||||
{
|
||||
info.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT;
|
||||
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);
|
||||
}
|
||||
|
||||
BindlessDescriptorSet DescriptorSetAllocator::allocate_bindless_set(VkDescriptorPool pool, unsigned num_descriptors)
|
||||
{
|
||||
if (!pool || !bindless)
|
||||
return {};
|
||||
|
||||
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;
|
||||
|
||||
BindlessDescriptorSet desc_set;
|
||||
if (table.vkAllocateDescriptorSets(device->get_device(), &info, &desc_set.handle.set) != VK_SUCCESS)
|
||||
return {};
|
||||
|
||||
desc_set.valid = true;
|
||||
return desc_set;
|
||||
}
|
||||
|
||||
DescriptorBufferAllocation DescriptorSetAllocator::allocate_bindless_buffer(unsigned num_sets, unsigned num_descriptors)
|
||||
{
|
||||
if (!bindless)
|
||||
return {};
|
||||
|
||||
VkDeviceSize size = get_variable_offset() * num_sets +
|
||||
device->managers.descriptor_buffer.get_descriptor_size_for_type(pool_size[0].type) *
|
||||
num_descriptors;
|
||||
|
||||
size += (std::max<uint32_t>(num_sets, 1u) - 1u) *
|
||||
std::max<VkDeviceSize>(
|
||||
device->get_device_features().resource_heap_offset_alignment,
|
||||
device->get_device_features().resource_heap_resource_desc_size);
|
||||
|
||||
return device->managers.descriptor_buffer.allocate(size);
|
||||
}
|
||||
|
||||
VkDeviceSize DescriptorSetAllocator::get_variable_size(unsigned count) const
|
||||
{
|
||||
return get_variable_offset() +
|
||||
device->managers.descriptor_buffer.get_descriptor_size_for_type(pool_size[0].type) *
|
||||
count;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!desc_pool)
|
||||
bindless_buffer = allocator->allocate_bindless_buffer(num_sets, num_desc);
|
||||
}
|
||||
|
||||
BindlessDescriptorPool::~BindlessDescriptorPool()
|
||||
{
|
||||
if (desc_pool)
|
||||
{
|
||||
if (internal_sync)
|
||||
device->destroy_descriptor_pool_nolock(desc_pool);
|
||||
else
|
||||
device->destroy_descriptor_pool(desc_pool);
|
||||
}
|
||||
|
||||
if (bindless_buffer.get_size() != 0)
|
||||
{
|
||||
if (internal_sync)
|
||||
device->free_descriptor_buffer_allocation_nolock(bindless_buffer);
|
||||
else
|
||||
device->free_descriptor_buffer_allocation(bindless_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
BindlessDescriptorSet 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 = {};
|
||||
allocated_descriptor_count = 0;
|
||||
allocated_sets = 0;
|
||||
bindless_buffer_offset = 0;
|
||||
}
|
||||
|
||||
bool BindlessDescriptorPool::allocate_descriptors(unsigned count)
|
||||
{
|
||||
if (device->get_device_features().supports_descriptor_buffer_or_heap)
|
||||
{
|
||||
auto alignment = std::max<VkDeviceSize>(
|
||||
device->get_device_features().resource_heap_offset_alignment,
|
||||
device->get_device_features().resource_heap_resource_desc_size);
|
||||
|
||||
bindless_buffer_offset = (bindless_buffer_offset + alignment - 1) & ~(alignment - 1);
|
||||
VkDeviceSize size = allocator->get_variable_size(count);
|
||||
|
||||
desc_set = {};
|
||||
if (bindless_buffer_offset + size <= bindless_buffer.get_size())
|
||||
{
|
||||
desc_set.handle.offset = bindless_buffer_offset + bindless_buffer.get_offset();
|
||||
desc_set.valid = true;
|
||||
bindless_buffer_offset += size;
|
||||
|
||||
allocated_descriptor_count += count;
|
||||
allocated_sets++;
|
||||
}
|
||||
|
||||
info_ptrs.reserve(count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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 bool(desc_set);
|
||||
}
|
||||
|
||||
void BindlessDescriptorPool::push_texture(const ImageView &view)
|
||||
{
|
||||
// TODO: Deal with integer view for depth-stencil images?
|
||||
if (!desc_pool)
|
||||
push_texture(view.get_float_view().sampled.ptr);
|
||||
else
|
||||
push_texture(view.get_float_view().view, view.get_image().get_layout(VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL));
|
||||
}
|
||||
|
||||
void BindlessDescriptorPool::push_texture_unorm(const ImageView &view)
|
||||
{
|
||||
if (!desc_pool)
|
||||
push_texture(view.get_unorm_view().sampled.ptr);
|
||||
else
|
||||
push_texture(view.get_unorm_view().view, view.get_image().get_layout(VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL));
|
||||
}
|
||||
|
||||
void BindlessDescriptorPool::push_texture_srgb(const ImageView &view)
|
||||
{
|
||||
if (!desc_pool)
|
||||
push_texture(view.get_srgb_view().sampled.ptr);
|
||||
else
|
||||
push_texture(view.get_srgb_view().view, view.get_image().get_layout(VK_IMAGE_LAYOUT_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::push_texture(const uint8_t *ptr)
|
||||
{
|
||||
VK_ASSERT(write_count < info_ptrs.get_capacity());
|
||||
info_ptrs[write_count++] = ptr;
|
||||
}
|
||||
|
||||
void BindlessDescriptorPool::update()
|
||||
{
|
||||
if (device->get_device_features().supports_descriptor_buffer_or_heap)
|
||||
{
|
||||
device->managers.descriptor_buffer.copy_sampled_image_n(
|
||||
device->managers.descriptor_buffer.get_resource_heap().mapped +
|
||||
desc_set.handle.offset + allocator->get_variable_offset(),
|
||||
info_ptrs.data(), write_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
VkWriteDescriptorSet desc = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
|
||||
desc.descriptorCount = write_count;
|
||||
desc.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
||||
desc.dstSet = desc_set.handle.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;
|
||||
}
|
||||
|
||||
BindlessDescriptorSet 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 {};
|
||||
}
|
||||
}
|
||||
|
||||
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,243 @@
|
||||
/* Copyright (c) 2017-2026 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"
|
||||
#include "memory_allocator.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class Device;
|
||||
struct ArraySizeAccessMeta
|
||||
{
|
||||
uint8_t array_size : 7;
|
||||
uint8_t requires_descriptor_size : 1;
|
||||
};
|
||||
static_assert(sizeof(ArraySizeAccessMeta) == sizeof(uint8_t), "Unexpected bitfield padding.");
|
||||
|
||||
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 rtas_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;
|
||||
ArraySizeAccessMeta meta[VULKAN_NUM_BINDINGS] = {};
|
||||
enum { UNSIZED_ARRAY = 0x7f };
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
struct BindlessDescriptorSet
|
||||
{
|
||||
union Handle
|
||||
{
|
||||
VkDescriptorSet set;
|
||||
VkDeviceSize offset;
|
||||
} handle = {};
|
||||
bool valid = false;
|
||||
explicit operator bool() const { return valid; }
|
||||
};
|
||||
|
||||
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);
|
||||
BindlessDescriptorSet 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;
|
||||
DescriptorBufferAllocation bindless_buffer;
|
||||
VkDeviceSize bindless_buffer_offset = 0;
|
||||
|
||||
BindlessDescriptorSet desc_set;
|
||||
|
||||
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);
|
||||
void push_texture(const uint8_t *ptr);
|
||||
Util::DynamicArray<VkDescriptorImageInfo> infos;
|
||||
Util::DynamicArray<const uint8_t *> info_ptrs;
|
||||
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;
|
||||
}
|
||||
|
||||
// Legacy descriptors.
|
||||
VkDescriptorPool allocate_bindless_pool(unsigned num_sets, unsigned num_descriptors);
|
||||
BindlessDescriptorSet allocate_bindless_set(VkDescriptorPool pool, unsigned num_descriptors);
|
||||
void reset_bindless_pool(VkDescriptorPool pool);
|
||||
|
||||
// Descriptor buffer integration.
|
||||
DescriptorBufferAllocation allocate_bindless_buffer(unsigned num_sets, unsigned num_descriptors);
|
||||
|
||||
VkDeviceSize get_resource_heap_size() const
|
||||
{
|
||||
return desc_set_size;
|
||||
}
|
||||
|
||||
VkDeviceSize get_variable_offset() const
|
||||
{
|
||||
return desc_set_variable_offset;
|
||||
}
|
||||
|
||||
VkDeviceSize get_variable_size(unsigned count) const;
|
||||
|
||||
uint32_t get_binding_offset(uint32_t binding) const
|
||||
{
|
||||
return desc_offsets[binding];
|
||||
}
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
const VolkDeviceTable &table;
|
||||
VkDescriptorSetLayout set_layout_pool = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout set_layout_push = VK_NULL_HANDLE;
|
||||
|
||||
VkDeviceSize desc_set_size = 0;
|
||||
VkDeviceSize desc_set_variable_offset = 0;
|
||||
uint32_t desc_offsets[VULKAN_NUM_BINDINGS] = {};
|
||||
|
||||
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);
|
||||
|
||||
BindlessDescriptorSet 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,973 @@
|
||||
/* Copyright (c) 2017-2026 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 "pipeline_cache.hpp"
|
||||
#include "breadcrumbs.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 HostReference
|
||||
{
|
||||
const void *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct InitialImageBuffer
|
||||
{
|
||||
// Either buffer or host is used. Ideally host is used so that host image copy can be used for uploads.
|
||||
BufferHandle buffer;
|
||||
HostReference host;
|
||||
Util::SmallVector<VkBufferImageCopy, 32> blits;
|
||||
};
|
||||
|
||||
struct HandlePool
|
||||
{
|
||||
VulkanObjectPool<Buffer> buffers;
|
||||
VulkanObjectPool<RTAS> rtas;
|
||||
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 };
|
||||
|
||||
explicit BatchComposer(uint64_t present_id_nv);
|
||||
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<VkLatencySubmissionPresentIdNV> present_ids_nv;
|
||||
Util::SmallVector<VkSemaphoreSubmitInfo> waits[MaxSubmissions];
|
||||
Util::SmallVector<VkSemaphoreSubmitInfo> signals[MaxSubmissions];
|
||||
Util::SmallVector<VkCommandBufferSubmitInfo> cmds[MaxSubmissions];
|
||||
|
||||
uint64_t present_id_nv = 0;
|
||||
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 RTAS;
|
||||
friend struct RTASDeleter;
|
||||
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, VkImageLayout layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
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);
|
||||
// If persistent_mapping is true, the data pointer lifetime is live as long as the device is.
|
||||
// Useful for read-only file mmap.
|
||||
bool init_pipeline_cache(const uint8_t *data, size_t size, bool persistent_mapping = false);
|
||||
|
||||
// Frame-pushing interface.
|
||||
void next_frame_context();
|
||||
bool next_frame_context_is_non_blocking();
|
||||
|
||||
// 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);
|
||||
// Must be given back with submit_discard().
|
||||
CommandBufferHandle request_borrowed_command_buffer(VkCommandBuffer cmd);
|
||||
|
||||
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 PipelineLayout *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);
|
||||
BufferHandle wrap_buffer(const BufferCreateInfo &info, VkBuffer buffer, bool supports_bda = true);
|
||||
// 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);
|
||||
|
||||
// If cmd is not null, the RTAS is immediately built.
|
||||
// If compacted_size is not null, a compacted size query will be made. info.mode must be compatible with compaction.
|
||||
RTASHandle create_rtas(const BottomRTASCreateInfo &info, CommandBuffer *cmd, QueryPoolHandle *compacted_size);
|
||||
RTASHandle create_rtas(const TopRTASCreateInfo &info, CommandBuffer *cmd);
|
||||
// Generic creation methods.
|
||||
RTASHandle create_rtas(VkAccelerationStructureTypeKHR type, VkDeviceSize size);
|
||||
RTASHandle create_rtas(VkAccelerationStructureTypeKHR type, BufferHandle buffer, VkDeviceSize offset, VkDeviceSize size);
|
||||
|
||||
// Create staging buffers for images.
|
||||
|
||||
// This is deprecated and considered slow path.
|
||||
// If number of subresources is 1, the fast path can be taken.
|
||||
InitialImageBuffer create_image_staging_buffer(const ImageCreateInfo &info, const ImageInitialData *initial);
|
||||
|
||||
// Only takes a reference to the layout.
|
||||
// Ideal path when uploading resources since it's compatible with host image copy, etc.
|
||||
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();
|
||||
Granite::FileMappingHandle persistent_pipeline_cache;
|
||||
#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;
|
||||
int64_t convert_timestamp_to_absolute_nsec(const QueryPoolResult &handle);
|
||||
// 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);
|
||||
void set_present_id(VkSwapchainKHR low_latency_swapchain, uint64_t present_id);
|
||||
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);
|
||||
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();
|
||||
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;
|
||||
DescriptorBufferAllocator descriptor_buffer;
|
||||
BreadcrumbsTracker breadcrumbs;
|
||||
};
|
||||
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;
|
||||
|
||||
bool wait(uint64_t timeout);
|
||||
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_ts, query_pool_rtas;
|
||||
|
||||
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<CachedImageView> destroyed_image_views;
|
||||
std::vector<CachedBufferView> destroyed_buffer_views;
|
||||
std::vector<VkImage> destroyed_images;
|
||||
std::vector<VkBuffer> destroyed_buffers;
|
||||
std::vector<VkAccelerationStructureKHR> destroyed_rtas;
|
||||
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;
|
||||
std::vector<VkIndirectExecutionSetEXT> destroyed_execution_sets;
|
||||
std::vector<DescriptorBufferAllocation> descriptor_buffer_allocs;
|
||||
std::vector<CachedDescriptorPayload> cached_descriptor_payloads;
|
||||
std::vector<BufferMarkerHandle> breadcrumbs;
|
||||
|
||||
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;
|
||||
|
||||
struct
|
||||
{
|
||||
uint64_t present_id;
|
||||
bool need_submit_begin_marker;
|
||||
VkSwapchainKHR swapchain;
|
||||
} low_latency = {};
|
||||
} 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;
|
||||
uint32_t implicit_sync_to_queues = 0;
|
||||
uint32_t has_incoming_queue_dependencies = 0;
|
||||
} 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 legacy_pipeline_cache = VK_NULL_HANDLE;
|
||||
PipelineCache pipeline_binary_cache;
|
||||
|
||||
void init_pipeline_cache();
|
||||
void flush_pipeline_cache();
|
||||
|
||||
PerformanceQueryPool &get_performance_query_pool(QueueIndices physical_type);
|
||||
PipelineEvent request_pipeline_event();
|
||||
|
||||
std::function<void ()> queue_lock_callback;
|
||||
std::function<void ()> queue_unlock_callback;
|
||||
void flush_frame_nolock(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);
|
||||
void emit_implicit_sync_to_queues(QueueIndices physical_type);
|
||||
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_rtas(VkAccelerationStructureKHR rtas);
|
||||
void destroy_image(VkImage image);
|
||||
void destroy_image_view(const CachedImageView &view);
|
||||
void destroy_buffer_view(const CachedBufferView &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_indirect_execution_set(VkIndirectExecutionSetEXT exec_set);
|
||||
void free_descriptor_buffer_allocation(const DescriptorBufferAllocation &alloc);
|
||||
void free_cached_descriptor_payload(const CachedDescriptorPayload &payload);
|
||||
|
||||
void destroy_buffer_nolock(VkBuffer buffer);
|
||||
void destroy_rtas_nolock(VkAccelerationStructureKHR rtas);
|
||||
void destroy_image_nolock(VkImage image);
|
||||
void destroy_image_view_nolock(const CachedImageView &view);
|
||||
void destroy_buffer_view_nolock(const CachedBufferView &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 destroy_indirect_execution_set_nolock(VkIndirectExecutionSetEXT exec_set);
|
||||
void free_descriptor_buffer_allocation_nolock(const DescriptorBufferAllocation &alloc);
|
||||
void free_cached_descriptor_payload_nolock(const CachedDescriptorPayload &payload);
|
||||
|
||||
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_and_sync_to_queues(CommandBufferHandle &cmd, uint32_t sync_to_queues);
|
||||
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(Granite::TaskGroup *shader_compilation_group);
|
||||
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, VkImageUsageFlags usage);
|
||||
|
||||
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-2026 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-2026 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-2026 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,155 @@
|
||||
/* Copyright (c) 2017-2026 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 (device->get_device_features().supports_post_mortem)
|
||||
{
|
||||
VkResult vr = table.vkWaitSemaphores(device->get_device(), &info, PostMortemTimeout);
|
||||
if (vr == VK_TIMEOUT)
|
||||
vr = table.vkWaitSemaphores(device->get_device(), &info, 0);
|
||||
if (vr != VK_SUCCESS)
|
||||
{
|
||||
device->managers.breadcrumbs.notify_device_hung();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (table.vkWaitSemaphores(device->get_device(), &info, UINT64_MAX) != VK_SUCCESS)
|
||||
{
|
||||
LOGE("Failed to wait for timeline semaphore!\n");
|
||||
device->managers.breadcrumbs.notify_device_hung();
|
||||
}
|
||||
else
|
||||
observed_wait = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (device->get_device_features().supports_post_mortem)
|
||||
{
|
||||
VkResult vr = table.vkWaitForFences(device->get_device(), 1, &fence, VK_TRUE, PostMortemTimeout);
|
||||
if (vr == VK_TIMEOUT)
|
||||
vr = table.vkWaitForFences(device->get_device(), 1, &fence, VK_TRUE, 0);
|
||||
if (vr != VK_SUCCESS)
|
||||
{
|
||||
device->managers.breadcrumbs.notify_device_hung();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (table.vkWaitForFences(device->get_device(), 1, &fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS)
|
||||
{
|
||||
LOGE("Failed to wait for fence!\n");
|
||||
device->managers.breadcrumbs.notify_device_hung();
|
||||
}
|
||||
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-2026 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-2026 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-2026 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-2026 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,260 @@
|
||||
/* Copyright (c) 2017-2026 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_, const CachedImageView &view_, const ImageViewCreateInfo &info_)
|
||||
: Cookie(device_)
|
||||
, device(device_)
|
||||
, view(view_)
|
||||
, info(info_)
|
||||
{
|
||||
}
|
||||
|
||||
const CachedImageView &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];
|
||||
}
|
||||
}
|
||||
|
||||
const CachedImageView &ImageView::get_mip_view(unsigned level) const
|
||||
{
|
||||
VK_ASSERT(level < get_create_info().levels);
|
||||
|
||||
if (mip_views.empty())
|
||||
return view;
|
||||
else
|
||||
{
|
||||
VK_ASSERT(level < mip_views.size());
|
||||
return mip_views[level];
|
||||
}
|
||||
}
|
||||
|
||||
void ImageView::free_cached_view(CachedImageView &cached)
|
||||
{
|
||||
if (internal_sync)
|
||||
device->destroy_image_view_nolock(cached);
|
||||
else
|
||||
device->destroy_image_view(cached);
|
||||
}
|
||||
|
||||
ImageView::~ImageView()
|
||||
{
|
||||
free_cached_view(view);
|
||||
free_cached_view(depth_view);
|
||||
free_cached_view(stencil_view);
|
||||
free_cached_view(unorm_view);
|
||||
free_cached_view(srgb_view);
|
||||
for (auto &v : render_target_views)
|
||||
free_cached_view(v);
|
||||
for (auto &v : mip_views)
|
||||
free_cached_view(v);
|
||||
}
|
||||
|
||||
unsigned ImageView::get_view_width() const
|
||||
{
|
||||
unsigned width = info.image->get_width(info.base_level);
|
||||
|
||||
if (info.aspect == VK_IMAGE_ASPECT_PLANE_1_BIT || info.aspect == VK_IMAGE_ASPECT_PLANE_2_BIT)
|
||||
{
|
||||
unsigned h = 0;
|
||||
format_ycbcr_downsample_dimensions(info.image->get_format(), info.aspect, width, h);
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
unsigned ImageView::get_view_height() const
|
||||
{
|
||||
unsigned height = info.image->get_height(info.base_level);
|
||||
|
||||
if (info.aspect == VK_IMAGE_ASPECT_PLANE_1_BIT || info.aspect == VK_IMAGE_ASPECT_PLANE_2_BIT)
|
||||
{
|
||||
unsigned w = 0;
|
||||
format_ycbcr_downsample_dimensions(info.image->get_format(), info.aspect, w, height);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
unsigned ImageView::get_view_depth() const
|
||||
{
|
||||
return info.image->get_depth(info.base_level);
|
||||
}
|
||||
|
||||
Image::Image(Device *device_, VkImage image_, const CachedImageView &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 (view_type != VK_IMAGE_VIEW_TYPE_MAX_ENUM)
|
||||
{
|
||||
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,582 @@
|
||||
/* Copyright (c) 2017-2026 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,
|
||||
IMAGE_MISC_CREATE_PER_MIP_LEVEL_VIEWS_BIT = 1 << 14
|
||||
};
|
||||
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, const CachedImageView &view, const ImageViewCreateInfo &info);
|
||||
|
||||
~ImageView();
|
||||
|
||||
void set_separate_depth_stencil_views(const CachedImageView &depth, const CachedImageView &stencil)
|
||||
{
|
||||
VK_ASSERT(depth_view.view == VK_NULL_HANDLE);
|
||||
VK_ASSERT(stencil_view.view == VK_NULL_HANDLE);
|
||||
depth_view = depth;
|
||||
stencil_view = stencil;
|
||||
}
|
||||
|
||||
void set_render_target_views(std::vector<CachedImageView> views)
|
||||
{
|
||||
VK_ASSERT(render_target_views.empty());
|
||||
render_target_views = std::move(views);
|
||||
}
|
||||
|
||||
void set_mip_views(std::vector<CachedImageView> views)
|
||||
{
|
||||
VK_ASSERT(mip_views.empty());
|
||||
mip_views = std::move(views);
|
||||
}
|
||||
|
||||
void set_unorm_view(const CachedImageView &view_)
|
||||
{
|
||||
VK_ASSERT(unorm_view.view == VK_NULL_HANDLE);
|
||||
unorm_view = view_;
|
||||
}
|
||||
|
||||
void set_srgb_view(const CachedImageView &view_)
|
||||
{
|
||||
VK_ASSERT(srgb_view.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.
|
||||
const CachedImageView &get_view() const
|
||||
{
|
||||
return view;
|
||||
}
|
||||
|
||||
const CachedImageView &get_render_target_view(unsigned layer) const;
|
||||
const CachedImageView &get_mip_view(unsigned level) 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.
|
||||
const CachedImageView &get_float_view() const
|
||||
{
|
||||
return depth_view.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.
|
||||
const CachedImageView &get_integer_view() const
|
||||
{
|
||||
return stencil_view.view != VK_NULL_HANDLE ? stencil_view : view;
|
||||
}
|
||||
|
||||
const CachedImageView &get_unorm_view() const
|
||||
{
|
||||
return unorm_view.view != VK_NULL_HANDLE ? unorm_view : view;
|
||||
}
|
||||
|
||||
const CachedImageView &get_srgb_view() const
|
||||
{
|
||||
return srgb_view.view != VK_NULL_HANDLE ? srgb_view : 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;
|
||||
CachedImageView view = {};
|
||||
std::vector<CachedImageView> render_target_views;
|
||||
std::vector<CachedImageView> mip_views;
|
||||
CachedImageView depth_view = {};
|
||||
CachedImageView stencil_view = {};
|
||||
CachedImageView unorm_view = {};
|
||||
CachedImageView srgb_view = {};
|
||||
ImageViewCreateInfo info;
|
||||
|
||||
void free_cached_view(CachedImageView &cached);
|
||||
};
|
||||
|
||||
using ImageViewHandle = Util::IntrusivePtr<ImageView>;
|
||||
|
||||
enum class ImageDomain
|
||||
{
|
||||
Physical,
|
||||
Transient,
|
||||
LinearHostCached,
|
||||
LinearHost,
|
||||
LinearDevice,
|
||||
HostCopy
|
||||
};
|
||||
|
||||
enum class ImageLayout
|
||||
{
|
||||
Optimal,
|
||||
General
|
||||
};
|
||||
|
||||
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;
|
||||
ImageLayout layout = ImageLayout::Optimal;
|
||||
|
||||
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_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_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 = VK_IMAGE_LAYOUT_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);
|
||||
};
|
||||
|
||||
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 create_info.layout == ImageLayout::Optimal ? optimal : VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
|
||||
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, const CachedImageView &default_view, const DeviceAllocation &alloc,
|
||||
const ImageCreateInfo &info, VkImageViewType view_type);
|
||||
|
||||
Device *device;
|
||||
VkImage image;
|
||||
ImageViewHandle view;
|
||||
DeviceAllocation alloc;
|
||||
ImageCreateInfo create_info;
|
||||
|
||||
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,146 @@
|
||||
/* Copyright (c) 2017-2026 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"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
IndirectLayout::IndirectLayout(Device *device_,
|
||||
const PipelineLayout *pipeline_layout, const IndirectLayoutToken *tokens,
|
||||
uint32_t num_tokens, uint32_t stride)
|
||||
: device(device_)
|
||||
{
|
||||
VkIndirectCommandsLayoutCreateInfoEXT info = { VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_CREATE_INFO_EXT };
|
||||
info.indirectStride = stride;
|
||||
info.pipelineLayout = pipeline_layout ? pipeline_layout->get_layout() : VK_NULL_HANDLE;
|
||||
info.flags = VK_INDIRECT_COMMANDS_LAYOUT_USAGE_EXPLICIT_PREPROCESS_BIT_EXT |
|
||||
VK_INDIRECT_COMMANDS_LAYOUT_USAGE_UNORDERED_SEQUENCES_BIT_EXT;
|
||||
|
||||
Util::SmallVector<VkIndirectCommandsLayoutTokenEXT, 8> ext_tokens;
|
||||
Util::SmallVector<VkIndirectCommandsVertexBufferTokenEXT, 8> vbo_tokens;
|
||||
Util::SmallVector<VkIndirectCommandsPushConstantTokenEXT, 8> push_tokens;
|
||||
VkIndirectCommandsIndexBufferTokenEXT ibo_token;
|
||||
VkIndirectCommandsExecutionSetTokenEXT exec_token;
|
||||
|
||||
ext_tokens.reserve(num_tokens);
|
||||
vbo_tokens.reserve(num_tokens);
|
||||
push_tokens.reserve(num_tokens);
|
||||
bool heap = device->get_device_features().descriptor_heap_features.descriptorHeap == VK_TRUE;
|
||||
|
||||
for (uint32_t i = 0; i < num_tokens; i++)
|
||||
{
|
||||
VkIndirectCommandsLayoutTokenEXT token = { VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_TOKEN_EXT };
|
||||
switch (tokens[i].type)
|
||||
{
|
||||
case IndirectLayoutToken::Type::VBO:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_EXT;
|
||||
vbo_tokens.emplace_back();
|
||||
token.data.pVertexBuffer = &vbo_tokens.back();
|
||||
vbo_tokens.back().vertexBindingUnit = tokens[i].data.vbo.binding;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::IBO:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_EXT;
|
||||
token.data.pIndexBuffer = &ibo_token;
|
||||
ibo_token.mode = VK_INDIRECT_COMMANDS_INPUT_MODE_VULKAN_INDEX_BUFFER_EXT;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::PushConstant:
|
||||
case IndirectLayoutToken::Type::SequenceCount:
|
||||
{
|
||||
auto push_token_type = heap ?
|
||||
VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT :
|
||||
VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_EXT;
|
||||
|
||||
auto sequence_token_type = heap ?
|
||||
VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT :
|
||||
VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT;
|
||||
|
||||
token.type = tokens[i].type == IndirectLayoutToken::Type::PushConstant ?
|
||||
push_token_type : sequence_token_type;
|
||||
|
||||
push_tokens.emplace_back();
|
||||
token.data.pPushConstant = &push_tokens.back();
|
||||
VK_ASSERT(pipeline_layout->get_layout());
|
||||
push_tokens.back().updateRange.size = tokens[i].data.push.range;
|
||||
push_tokens.back().updateRange.offset = tokens[i].data.push.offset;
|
||||
push_tokens.back().updateRange.stageFlags =
|
||||
heap ? VkShaderStageFlags(VK_SHADER_STAGE_ALL) :
|
||||
pipeline_layout->get_resource_layout().push_constant_range.stageFlags;
|
||||
break;
|
||||
}
|
||||
|
||||
case IndirectLayoutToken::Type::Draw:
|
||||
info.shaderStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_EXT;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::DrawIndexed:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_EXT;
|
||||
info.shaderStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::Shader:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_EXECUTION_SET_EXT;
|
||||
token.data.pExecutionSet = &exec_token;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::MeshTasks:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_EXT;
|
||||
info.shaderStages |= VK_SHADER_STAGE_MESH_BIT_EXT | VK_SHADER_STAGE_TASK_BIT_EXT |
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
break;
|
||||
|
||||
case IndirectLayoutToken::Type::Dispatch:
|
||||
token.type = VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_EXT;
|
||||
info.shaderStages |= VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGE("Invalid token type.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
token.offset = tokens[i].offset;
|
||||
|
||||
ext_tokens.push_back(token);
|
||||
}
|
||||
|
||||
info.pTokens = ext_tokens.data();
|
||||
info.tokenCount = num_tokens;
|
||||
exec_token.type = VK_INDIRECT_EXECUTION_SET_INFO_TYPE_PIPELINES_EXT;
|
||||
exec_token.shaderStages = info.shaderStages;
|
||||
stages = info.shaderStages;
|
||||
|
||||
auto &table = device->get_device_table();
|
||||
if (table.vkCreateIndirectCommandsLayoutEXT(device->get_device(), &info, nullptr, &layout) != VK_SUCCESS)
|
||||
{
|
||||
LOGE("Failed to create indirect layout.\n");
|
||||
}
|
||||
}
|
||||
|
||||
IndirectLayout::~IndirectLayout()
|
||||
{
|
||||
device->get_device_table().vkDestroyIndirectCommandsLayoutEXT(device->get_device(), layout, nullptr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* Copyright (c) 2017-2026 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 "small_vector.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class Device;
|
||||
class PipelineLayout;
|
||||
|
||||
struct IndirectLayoutToken
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
Invalid = 0,
|
||||
Shader,
|
||||
PushConstant,
|
||||
SequenceCount,
|
||||
VBO,
|
||||
IBO,
|
||||
Draw,
|
||||
DrawIndexed,
|
||||
MeshTasks,
|
||||
Dispatch
|
||||
};
|
||||
|
||||
Type type = Type::Invalid;
|
||||
uint32_t offset = 0;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t range;
|
||||
} push;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t binding;
|
||||
} vbo;
|
||||
} data = {};
|
||||
};
|
||||
|
||||
class IndirectLayout : public HashedObject<IndirectLayout>
|
||||
{
|
||||
public:
|
||||
IndirectLayout(Device *device, const PipelineLayout *layout, const IndirectLayoutToken *token,
|
||||
uint32_t num_tokens, uint32_t stride);
|
||||
~IndirectLayout();
|
||||
|
||||
VkIndirectCommandsLayoutEXT get_layout() const
|
||||
{
|
||||
return layout;
|
||||
}
|
||||
|
||||
VkShaderStageFlags get_shader_stages() const
|
||||
{
|
||||
return stages;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Device;
|
||||
|
||||
Device *device;
|
||||
VkIndirectCommandsLayoutEXT layout;
|
||||
VkShaderStageFlags stages;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Copyright (c) 2017-2026 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_PUSH_DATA_SIZE = 256;
|
||||
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;
|
||||
}
|
||||
+922
@@ -0,0 +1,922 @@
|
||||
/* Copyright (c) 2017-2026 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.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
#include "resource_manager.hpp"
|
||||
#include "device.hpp"
|
||||
#include "memory_mapped_texture.hpp"
|
||||
#include "texture_files.hpp"
|
||||
#include "texture_decoder.hpp"
|
||||
#include "string_helpers.hpp"
|
||||
#include "thread_group.hpp"
|
||||
#include "meshlet.hpp"
|
||||
#include "aabb.hpp"
|
||||
#include "environment.hpp"
|
||||
#include <float.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
ResourceManager::ResourceManager(Device *device_)
|
||||
: device(device_)
|
||||
, index_buffer_allocator(*device_, 256, 17)
|
||||
, attribute_buffer_allocator(*device_, 256, 17)
|
||||
, indirect_buffer_allocator(*device_, 32, 15)
|
||||
, mesh_header_allocator(*device_, 32, 15)
|
||||
, mesh_stream_allocator(*device_, 8, 17)
|
||||
, mesh_payload_allocator(*device_, 32, 17)
|
||||
{
|
||||
assets.reserve(Granite::AssetID::MaxIDs);
|
||||
}
|
||||
|
||||
ResourceManager::~ResourceManager()
|
||||
{
|
||||
// Also works as a teardown mechanism to make sure there are no async threads in flight.
|
||||
if (manager)
|
||||
manager->set_asset_instantiator_interface(nullptr);
|
||||
|
||||
// Ensure resource releases go through.
|
||||
latch_handles();
|
||||
}
|
||||
|
||||
void ResourceManager::set_id_bounds(uint32_t bound)
|
||||
{
|
||||
// We must avoid reallocation here to avoid a ton of extra silly locking.
|
||||
VK_ASSERT(bound <= Granite::AssetID::MaxIDs);
|
||||
assets.resize(bound);
|
||||
}
|
||||
|
||||
void ResourceManager::set_asset_class(Granite::AssetID id, Granite::AssetClass asset_class)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
assets[id.id].asset_class = asset_class;
|
||||
if (asset_class != Granite::AssetClass::Mesh)
|
||||
{
|
||||
std::unique_lock<std::mutex> holder{lock};
|
||||
views.resize(assets.size());
|
||||
|
||||
if (!views[id.id])
|
||||
views[id.id] = &get_fallback_image(asset_class)->get_view();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::release_asset(Granite::AssetID id)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
std::unique_lock<std::mutex> holder{lock};
|
||||
VK_ASSERT(id.id < assets.size());
|
||||
auto &asset = assets[id.id];
|
||||
asset.latchable = false;
|
||||
updates.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ResourceManager::estimate_cost_asset(Granite::AssetID id, Granite::File &file)
|
||||
{
|
||||
if (assets[id.id].asset_class == Granite::AssetClass::Mesh)
|
||||
{
|
||||
// Compression factor of 2x is reasonable to assume.
|
||||
if (mesh_encoding == MeshEncoding::VBOAndIBOMDI)
|
||||
return file.get_size() * 2;
|
||||
else
|
||||
return file.get_size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: When we get compressed BC/ASTC, this will have to change.
|
||||
return file.get_size();
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::init_mesh_assets()
|
||||
{
|
||||
Internal::MeshGlobalAllocator::PrimeOpaque opaque = {};
|
||||
opaque.domain = BufferDomain::Device;
|
||||
opaque.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
if (device->get_device_features().mesh_shader_features.meshShader)
|
||||
{
|
||||
mesh_encoding = MeshEncoding::MeshletEncoded;
|
||||
LOGI("Opting in to meshlet path.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh_encoding = MeshEncoding::VBOAndIBOMDI;
|
||||
LOGI("Falling back to multi-draw-indirect path.\n");
|
||||
}
|
||||
|
||||
std::string encoding;
|
||||
if (Util::get_environment("GRANITE_MESH_ENCODING", encoding))
|
||||
{
|
||||
if (encoding == "encoded")
|
||||
mesh_encoding = MeshEncoding::MeshletEncoded;
|
||||
else if (encoding == "decoded")
|
||||
mesh_encoding = MeshEncoding::MeshletDecoded;
|
||||
else if (encoding == "mdi")
|
||||
mesh_encoding = MeshEncoding::VBOAndIBOMDI;
|
||||
else if (encoding == "classic")
|
||||
mesh_encoding = MeshEncoding::Classic;
|
||||
else
|
||||
LOGE("Unknown encoding: %s\n", encoding.c_str());
|
||||
}
|
||||
|
||||
if (mesh_encoding != MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
unsigned index_size;
|
||||
|
||||
if (mesh_encoding == MeshEncoding::Classic)
|
||||
index_size = sizeof(uint32_t);
|
||||
else if (device->get_device_features().vk14_features.indexTypeUint8)
|
||||
index_size = sizeof(uint8_t);
|
||||
else
|
||||
index_size = sizeof(uint16_t);
|
||||
|
||||
index_buffer_allocator.set_element_size(0, 3 * index_size); // 8-bit or 32-bit indices.
|
||||
attribute_buffer_allocator.set_soa_count(3);
|
||||
attribute_buffer_allocator.set_element_size(0, sizeof(float) * 3);
|
||||
attribute_buffer_allocator.set_element_size(1, sizeof(float) * 2 + sizeof(uint32_t) * 2);
|
||||
attribute_buffer_allocator.set_element_size(2, sizeof(uint32_t) * 2);
|
||||
|
||||
opaque.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
index_buffer_allocator.prime(&opaque);
|
||||
opaque.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
attribute_buffer_allocator.prime(&opaque);
|
||||
|
||||
if (mesh_encoding != MeshEncoding::Classic)
|
||||
{
|
||||
auto element_size = mesh_encoding == MeshEncoding::MeshletDecoded ?
|
||||
sizeof(Meshlet::RuntimeHeaderDecoded) : sizeof(Meshlet::RuntimeHeaderDecodedMDI);
|
||||
|
||||
indirect_buffer_allocator.set_soa_count(2);
|
||||
indirect_buffer_allocator.set_element_size(0, Meshlet::ChunkFactor * element_size);
|
||||
indirect_buffer_allocator.set_element_size(1, sizeof(Meshlet::Bound));
|
||||
|
||||
opaque.usage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
indirect_buffer_allocator.prime(&opaque);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh_header_allocator.set_element_size(0, sizeof(Meshlet::RuntimeHeaderEncoded));
|
||||
mesh_stream_allocator.set_element_size(0, sizeof(Meshlet::Stream));
|
||||
mesh_payload_allocator.set_element_size(0, sizeof(Meshlet::PayloadWord));
|
||||
|
||||
mesh_header_allocator.set_soa_count(2);
|
||||
mesh_header_allocator.set_element_size(1, sizeof(Meshlet::Bound));
|
||||
|
||||
opaque.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
mesh_header_allocator.prime(&opaque);
|
||||
mesh_stream_allocator.prime(&opaque);
|
||||
mesh_payload_allocator.prime(&opaque);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::init()
|
||||
{
|
||||
manager = device->get_system_handles().asset_manager;
|
||||
|
||||
// Need to initialize these before setting the interface.
|
||||
{
|
||||
uint8_t buffer[4] = {0xff, 0x00, 0xff, 0xff};
|
||||
auto info = ImageCreateInfo::immutable_2d_image(1, 1, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
info.misc = IMAGE_MISC_CONCURRENT_QUEUE_ASYNC_COMPUTE_BIT |
|
||||
IMAGE_MISC_CONCURRENT_QUEUE_GRAPHICS_BIT;
|
||||
ImageInitialData data = {buffer, 0, 0};
|
||||
fallback_color = device->create_image(info, &data);
|
||||
buffer[0] = 0x80;
|
||||
buffer[1] = 0x80;
|
||||
buffer[2] = 0xff;
|
||||
fallback_normal = device->create_image(info, &data);
|
||||
buffer[0] = 0x00;
|
||||
buffer[1] = 0x00;
|
||||
fallback_pbr = device->create_image(info, &data);
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
fallback_zero = device->create_image(info, &data);
|
||||
}
|
||||
|
||||
if (manager)
|
||||
{
|
||||
manager->set_asset_instantiator_interface(this);
|
||||
|
||||
HeapBudget budget[VK_MAX_MEMORY_HEAPS] = {};
|
||||
device->get_memory_budget(budget);
|
||||
|
||||
// Try to set aside 50% of budgetable VRAM for the resource manager. Seems reasonable.
|
||||
VkDeviceSize size = 0;
|
||||
for (uint32_t i = 0; i < device->get_memory_properties().memoryHeapCount; i++)
|
||||
if ((device->get_memory_properties().memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0)
|
||||
size = std::max(size, budget[i].budget_size / 2);
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
LOGW("No DEVICE_LOCAL heap was found, assuming 2 GiB budget.\n");
|
||||
size = 2 * 1024 * 1024;
|
||||
}
|
||||
|
||||
LOGI("Using texture budget of %u MiB.\n", unsigned(size / (1024 * 1024)));
|
||||
manager->set_asset_budget(size);
|
||||
|
||||
// This is somewhat arbitrary.
|
||||
manager->set_asset_budget_per_iteration(2 * 1000 * 1000);
|
||||
}
|
||||
|
||||
// Opt-in. Normal Granite applications shouldn't allocate up a ton of space up front.
|
||||
if (manager && manager->get_wants_mesh_assets())
|
||||
init_mesh_assets();
|
||||
}
|
||||
|
||||
ImageHandle ResourceManager::create_gtx(const MemoryMappedTexture &mapped_file, Granite::AssetID id)
|
||||
{
|
||||
if (mapped_file.empty())
|
||||
return {};
|
||||
|
||||
auto &layout = mapped_file.get_layout();
|
||||
|
||||
VkComponentMapping swizzle = {};
|
||||
mapped_file.remap_swizzle(swizzle);
|
||||
|
||||
ImageHandle image;
|
||||
if (!device->image_format_is_supported(layout.get_format(), VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) &&
|
||||
format_compression_type(layout.get_format()) != FormatCompressionType::Uncompressed)
|
||||
{
|
||||
LOGI("Compressed format #%u is not supported, falling back to compute decode of compressed image.\n",
|
||||
unsigned(layout.get_format()));
|
||||
|
||||
GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file, "texture-load-submit-decompress");
|
||||
auto cmd = device->request_command_buffer(CommandBuffer::Type::AsyncCompute);
|
||||
image = Granite::decode_compressed_image(*cmd, layout, VK_FORMAT_UNDEFINED, swizzle);
|
||||
Semaphore sem;
|
||||
device->submit(cmd, nullptr, 1, &sem);
|
||||
device->add_wait_semaphore(CommandBuffer::Type::Generic, sem, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageCreateInfo info = ImageCreateInfo::immutable_image(layout);
|
||||
info.swizzle = swizzle;
|
||||
info.flags = (mapped_file.get_flags() & MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT) ?
|
||||
VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT :
|
||||
0;
|
||||
info.misc = IMAGE_MISC_CONCURRENT_QUEUE_GRAPHICS_BIT |
|
||||
IMAGE_MISC_CONCURRENT_QUEUE_ASYNC_COMPUTE_BIT;
|
||||
|
||||
if (info.levels == 1 &&
|
||||
(mapped_file.get_flags() & MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT) != 0 &&
|
||||
device->image_format_is_supported(info.format, VK_FORMAT_FEATURE_BLIT_SRC_BIT) &&
|
||||
device->image_format_is_supported(info.format, VK_FORMAT_FEATURE_BLIT_DST_BIT))
|
||||
{
|
||||
info.levels = 0;
|
||||
info.misc |= IMAGE_MISC_GENERATE_MIPS_BIT;
|
||||
}
|
||||
|
||||
if (!device->image_format_is_supported(info.format, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT))
|
||||
{
|
||||
LOGE("Format (%u) is not supported!\n", unsigned(info.format));
|
||||
return {};
|
||||
}
|
||||
|
||||
InitialImageBuffer staging;
|
||||
|
||||
{
|
||||
GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file,
|
||||
"texture-load-create-staging");
|
||||
staging = device->create_image_staging_buffer(layout);
|
||||
}
|
||||
|
||||
{
|
||||
GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file,
|
||||
"texture-load-allocate-image");
|
||||
image = device->create_image_from_staging_buffer(info, &staging);
|
||||
}
|
||||
}
|
||||
|
||||
if (image)
|
||||
{
|
||||
auto name = Util::join("AssetID-", id.id);
|
||||
device->set_name(*image, name.c_str());
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
ImageHandle ResourceManager::create_gtx(Granite::FileMappingHandle mapping, Granite::AssetID id)
|
||||
{
|
||||
MemoryMappedTexture mapped_file;
|
||||
if (!mapped_file.map_read(std::move(mapping)))
|
||||
{
|
||||
LOGE("Failed to read texture.\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
return create_gtx(mapped_file, id);
|
||||
}
|
||||
|
||||
ImageHandle ResourceManager::create_other(const Granite::FileMapping &mapping, Granite::AssetClass asset_class,
|
||||
Granite::AssetID id)
|
||||
{
|
||||
auto tex = load_texture_from_memory(mapping.data(),
|
||||
mapping.get_size(), asset_class == Granite::AssetClass::ImageColor ?
|
||||
ColorSpace::sRGB : ColorSpace::Linear);
|
||||
return create_gtx(tex, id);
|
||||
}
|
||||
|
||||
const ImageView *ResourceManager::get_image_view_blocking(Granite::AssetID id)
|
||||
{
|
||||
std::unique_lock<std::mutex> holder{lock};
|
||||
|
||||
if (id.id >= assets.size())
|
||||
{
|
||||
LOGE("ID %u is out of bounds.\n", id.id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto &asset = assets[id.id];
|
||||
|
||||
if (asset.image)
|
||||
return &asset.image->get_view();
|
||||
|
||||
if (!manager->iterate_blocking(*device->get_system_handles().thread_group, id))
|
||||
{
|
||||
LOGE("Failed to iterate.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cond.wait(holder, [&asset]() -> bool {
|
||||
return bool(asset.latchable);
|
||||
});
|
||||
|
||||
return &asset.image->get_view();
|
||||
}
|
||||
|
||||
void ResourceManager::instantiate_asset(Granite::AssetManager &manager_, Granite::TaskGroup *task,
|
||||
Granite::AssetID id, Granite::File &file)
|
||||
{
|
||||
if (task)
|
||||
{
|
||||
task->enqueue_task([this, &manager_, &file, id]() {
|
||||
instantiate_asset(manager_, id, file);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
instantiate_asset(manager_, id, file);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::instantiate_asset(Granite::AssetManager &manager_,
|
||||
Granite::AssetID id,
|
||||
Granite::File &file)
|
||||
{
|
||||
auto &asset = assets[id.id];
|
||||
if (asset.asset_class == Granite::AssetClass::Mesh)
|
||||
instantiate_asset_mesh(manager_, id, file);
|
||||
else
|
||||
instantiate_asset_image(manager_, id, file);
|
||||
}
|
||||
|
||||
bool ResourceManager::allocate_asset_mesh(Granite::AssetID id, const Meshlet::MeshView &view)
|
||||
{
|
||||
if (!view.format_header)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> holder{mesh_allocator_lock};
|
||||
auto &asset = assets[id.id];
|
||||
|
||||
bool ret = true;
|
||||
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
if (ret)
|
||||
ret = mesh_header_allocator.allocate(view.num_bounds_256, &asset.mesh.indirect_or_header);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
ret = mesh_stream_allocator.allocate(
|
||||
view.num_bounds_256 * Meshlet::ChunkFactor * view.format_header->stream_count,
|
||||
&asset.mesh.attr_or_stream);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
ret = mesh_payload_allocator.allocate(view.format_header->payload_size_words, &asset.mesh.index_or_payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ret)
|
||||
ret = index_buffer_allocator.allocate(view.total_primitives, &asset.mesh.index_or_payload);
|
||||
if (ret)
|
||||
ret = attribute_buffer_allocator.allocate(view.total_vertices, &asset.mesh.attr_or_stream);
|
||||
|
||||
if (ret && mesh_encoding != MeshEncoding::Classic)
|
||||
ret = indirect_buffer_allocator.allocate(view.num_bounds_256, &asset.mesh.indirect_or_header);
|
||||
}
|
||||
|
||||
if (mesh_encoding == MeshEncoding::Classic)
|
||||
{
|
||||
asset.mesh.draw.indexed = {
|
||||
view.total_primitives * 3, 1,
|
||||
asset.mesh.index_or_payload.offset,
|
||||
int32_t(asset.mesh.attr_or_stream.offset), 0,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
asset.mesh.draw.meshlet = {
|
||||
asset.mesh.indirect_or_header.offset,
|
||||
view.num_bounds_256,
|
||||
view.format_header->style,
|
||||
};
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
mesh_payload_allocator.free(asset.mesh.index_or_payload);
|
||||
mesh_stream_allocator.free(asset.mesh.attr_or_stream);
|
||||
mesh_header_allocator.free(asset.mesh.indirect_or_header);
|
||||
}
|
||||
else
|
||||
{
|
||||
index_buffer_allocator.free(asset.mesh.index_or_payload);
|
||||
attribute_buffer_allocator.free(asset.mesh.attr_or_stream);
|
||||
indirect_buffer_allocator.free(asset.mesh.indirect_or_header);
|
||||
}
|
||||
|
||||
asset.mesh = {};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ResourceManager::instantiate_asset_mesh(Granite::AssetManager &manager_,
|
||||
Granite::AssetID id,
|
||||
Granite::File &file)
|
||||
{
|
||||
Granite::FileMappingHandle mapping;
|
||||
if (file.get_size())
|
||||
mapping = file.map();
|
||||
|
||||
Meshlet::MeshView view = {};
|
||||
if (mapping)
|
||||
view = Meshlet::create_mesh_view(*mapping);
|
||||
bool ret = allocate_asset_mesh(id, view);
|
||||
|
||||
// Decode the meshlet. Later, we'll have to do a lot of device specific stuff here to select optimal
|
||||
// processing:
|
||||
// - Native meshlets
|
||||
// - Encoded attribute
|
||||
// - Decoded attributes
|
||||
// - Optimize for multi-draw-indirect or not? (8-bit indices).
|
||||
|
||||
auto &asset = assets[id.id];
|
||||
|
||||
if (ret)
|
||||
{
|
||||
size_t total_streams = view.format_header->meshlet_count * view.format_header->stream_count;
|
||||
size_t total_padded_streams = view.num_bounds_256 * Meshlet::ChunkFactor * view.format_header->stream_count;
|
||||
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
auto cmd = device->request_command_buffer(CommandBuffer::Type::AsyncTransfer);
|
||||
|
||||
void *payload_data = cmd->update_buffer(*mesh_payload_allocator.get_buffer(0, 0),
|
||||
asset.mesh.index_or_payload.offset * sizeof(Meshlet::PayloadWord),
|
||||
view.format_header->payload_size_words * sizeof(Meshlet::PayloadWord));
|
||||
memcpy(payload_data, view.payload, view.format_header->payload_size_words * sizeof(Meshlet::PayloadWord));
|
||||
|
||||
auto *headers = static_cast<Meshlet::RuntimeHeaderEncoded *>(
|
||||
cmd->update_buffer(*mesh_header_allocator.get_buffer(0, 0),
|
||||
asset.mesh.indirect_or_header.offset * sizeof(Meshlet::RuntimeHeaderEncoded),
|
||||
view.num_bounds_256 * sizeof(Meshlet::RuntimeHeaderEncoded)));
|
||||
|
||||
for (uint32_t i = 0, n = view.num_bounds_256; i < n; i++)
|
||||
{
|
||||
headers[i].stream_offset = asset.mesh.attr_or_stream.offset +
|
||||
i * Meshlet::ChunkFactor * view.format_header->stream_count;
|
||||
}
|
||||
|
||||
auto *bounds = static_cast<Meshlet::Bound *>(
|
||||
cmd->update_buffer(*mesh_header_allocator.get_buffer(0, 1),
|
||||
asset.mesh.indirect_or_header.offset * sizeof(Meshlet::Bound),
|
||||
view.num_bounds_256 * sizeof(Meshlet::Bound)));
|
||||
memcpy(bounds, view.bounds_256, view.num_bounds_256 * sizeof(Meshlet::Bound));
|
||||
|
||||
auto *streams = static_cast<Meshlet::Stream *>(
|
||||
cmd->update_buffer(*mesh_stream_allocator.get_buffer(0, 0),
|
||||
asset.mesh.attr_or_stream.offset * sizeof(Meshlet::Stream),
|
||||
total_padded_streams * sizeof(Meshlet::Stream)));
|
||||
|
||||
for (uint32_t i = 0; i < total_streams; i++)
|
||||
{
|
||||
auto in_stream = view.streams[i];
|
||||
in_stream.offset_in_words += asset.mesh.index_or_payload.offset;
|
||||
streams[i] = in_stream;
|
||||
}
|
||||
|
||||
memset(streams + total_streams, 0, (total_padded_streams - total_streams) * sizeof(Meshlet::Stream));
|
||||
|
||||
Semaphore sem;
|
||||
device->submit(cmd, nullptr, 1, &sem);
|
||||
device->add_wait_semaphore(CommandBuffer::Type::Generic, std::move(sem),
|
||||
VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT |
|
||||
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto cmd = device->request_command_buffer(CommandBuffer::Type::AsyncCompute);
|
||||
|
||||
BufferCreateInfo buf = {};
|
||||
buf.domain = BufferDomain::Host;
|
||||
buf.size = view.format_header->payload_size_words * sizeof(Meshlet::PayloadWord);
|
||||
buf.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
auto payload = device->create_buffer(buf, view.payload);
|
||||
|
||||
Meshlet::DecodeInfo info = {};
|
||||
info.target_style = view.format_header->style;
|
||||
if (mesh_encoding == MeshEncoding::Classic)
|
||||
info.flags |= Meshlet::DECODE_MODE_UNROLLED_MESH;
|
||||
else if (!device->get_device_features().vk14_features.indexTypeUint8)
|
||||
info.flags |= Meshlet::DECODE_MODE_INDEX_16;
|
||||
info.ibo = index_buffer_allocator.get_buffer(0, 0);
|
||||
|
||||
for (unsigned i = 0; i < 3; i++)
|
||||
info.streams[i] = attribute_buffer_allocator.get_buffer(0, i);
|
||||
|
||||
info.payload = payload.get();
|
||||
|
||||
info.push.primitive_offset = asset.mesh.index_or_payload.offset;
|
||||
info.push.vertex_offset = asset.mesh.attr_or_stream.offset;
|
||||
|
||||
info.runtime_style = mesh_encoding == MeshEncoding::MeshletDecoded ?
|
||||
Meshlet::RuntimeStyle::Meshlet : Meshlet::RuntimeStyle::MDI;
|
||||
|
||||
if (mesh_encoding != MeshEncoding::Classic)
|
||||
{
|
||||
auto *bounds = static_cast<Meshlet::Bound *>(
|
||||
cmd->update_buffer(*indirect_buffer_allocator.get_buffer(0, 1),
|
||||
asset.mesh.indirect_or_header.offset * sizeof(Meshlet::Bound),
|
||||
view.num_bounds_256 * sizeof(Meshlet::Bound)));
|
||||
memcpy(bounds, view.bounds_256, view.num_bounds_256 * sizeof(Meshlet::Bound));
|
||||
|
||||
info.indirect = indirect_buffer_allocator.get_buffer(0, 0);
|
||||
info.indirect_offset = asset.mesh.indirect_or_header.offset;
|
||||
}
|
||||
|
||||
Meshlet::decode_mesh(*cmd, info, view);
|
||||
|
||||
Semaphore sem;
|
||||
device->submit(cmd, nullptr, 1, &sem);
|
||||
device->add_wait_semaphore(CommandBuffer::Type::Generic, std::move(sem),
|
||||
VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT |
|
||||
VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT, false);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t cost = 0;
|
||||
if (ret)
|
||||
{
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
cost += view.format_header->payload_size_words * mesh_payload_allocator.get_element_size(0);
|
||||
cost += view.num_bounds_256 * mesh_header_allocator.get_element_size(0);
|
||||
cost += view.num_bounds_256 * mesh_header_allocator.get_element_size(1);
|
||||
cost += view.format_header->meshlet_count * view.format_header->stream_count * mesh_stream_allocator.get_element_size(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cost += view.total_primitives * index_buffer_allocator.get_element_size(0);
|
||||
cost += view.total_vertices * attribute_buffer_allocator.get_element_size(0);
|
||||
cost += view.total_vertices * attribute_buffer_allocator.get_element_size(1);
|
||||
cost += view.total_vertices * attribute_buffer_allocator.get_element_size(2);
|
||||
if (mesh_encoding != MeshEncoding::Classic)
|
||||
{
|
||||
cost += view.format_header->meshlet_count * indirect_buffer_allocator.get_element_size(0);
|
||||
cost += view.format_header->meshlet_count * indirect_buffer_allocator.get_element_size(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
updates.push_back(id);
|
||||
manager_.update_cost(id, ret ? cost : 0);
|
||||
asset.latchable = true;
|
||||
cond.notify_all();
|
||||
}
|
||||
|
||||
void ResourceManager::instantiate_asset_image(Granite::AssetManager &manager_,
|
||||
Granite::AssetID id,
|
||||
Granite::File &file)
|
||||
{
|
||||
auto &asset = assets[id.id];
|
||||
|
||||
ImageHandle image;
|
||||
if (file.get_size())
|
||||
{
|
||||
auto mapping = file.map();
|
||||
if (mapping)
|
||||
{
|
||||
if (MemoryMappedTexture::is_header(mapping->data(), mapping->get_size()))
|
||||
image = create_gtx(std::move(mapping), id);
|
||||
else
|
||||
image = create_other(*mapping, asset.asset_class, id);
|
||||
}
|
||||
else
|
||||
LOGE("Failed to map file.\n");
|
||||
}
|
||||
|
||||
// Have to signal something.
|
||||
if (!image)
|
||||
image = get_fallback_image(asset.asset_class);
|
||||
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
updates.push_back(id);
|
||||
asset.image = std::move(image);
|
||||
asset.latchable = true;
|
||||
manager_.update_cost(id, asset.image ? asset.image->get_allocation().get_size() : 0);
|
||||
cond.notify_all();
|
||||
}
|
||||
|
||||
const ImageHandle &ResourceManager::get_fallback_image(Granite::AssetClass asset_class)
|
||||
{
|
||||
switch (asset_class)
|
||||
{
|
||||
default:
|
||||
case Granite::AssetClass::ImageZeroable:
|
||||
return fallback_zero;
|
||||
case Granite::AssetClass::ImageColor:
|
||||
return fallback_color;
|
||||
case Granite::AssetClass::ImageNormal:
|
||||
return fallback_normal;
|
||||
case Granite::AssetClass::ImageMetallicRoughness:
|
||||
return fallback_pbr;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::latch_handles()
|
||||
{
|
||||
std::lock_guard<std::mutex> holder{lock};
|
||||
|
||||
views.resize(assets.size());
|
||||
draws.resize(assets.size());
|
||||
|
||||
for (auto &update : updates)
|
||||
{
|
||||
if (update.id >= views.size())
|
||||
continue;
|
||||
auto &asset = assets[update.id];
|
||||
|
||||
if (asset.asset_class == Granite::AssetClass::Mesh)
|
||||
{
|
||||
if (!asset.latchable)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> holder_alloc{mesh_allocator_lock};
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
{
|
||||
mesh_payload_allocator.free(asset.mesh.index_or_payload);
|
||||
mesh_stream_allocator.free(asset.mesh.attr_or_stream);
|
||||
mesh_header_allocator.free(asset.mesh.indirect_or_header);
|
||||
}
|
||||
else
|
||||
{
|
||||
index_buffer_allocator.free(asset.mesh.index_or_payload);
|
||||
attribute_buffer_allocator.free(asset.mesh.attr_or_stream);
|
||||
indirect_buffer_allocator.free(asset.mesh.indirect_or_header);
|
||||
}
|
||||
}
|
||||
asset.mesh = {};
|
||||
}
|
||||
|
||||
draws[update.id] = asset.mesh.draw;
|
||||
}
|
||||
else
|
||||
{
|
||||
const ImageView *view;
|
||||
if (!asset.latchable)
|
||||
asset.image.reset();
|
||||
|
||||
if (asset.image)
|
||||
{
|
||||
view = &asset.image->get_view();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &img = get_fallback_image(asset.asset_class);
|
||||
view = &img->get_view();
|
||||
}
|
||||
|
||||
views[update.id] = view;
|
||||
}
|
||||
}
|
||||
updates.clear();
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_index_buffer() const
|
||||
{
|
||||
return index_buffer_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_position_buffer() const
|
||||
{
|
||||
return attribute_buffer_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_attribute_buffer() const
|
||||
{
|
||||
return attribute_buffer_allocator.get_buffer(0, 1);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_skinning_buffer() const
|
||||
{
|
||||
return attribute_buffer_allocator.get_buffer(0, 2);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_indirect_buffer() const
|
||||
{
|
||||
return indirect_buffer_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_meshlet_payload_buffer() const
|
||||
{
|
||||
return mesh_payload_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_meshlet_header_buffer() const
|
||||
{
|
||||
return mesh_header_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_meshlet_stream_header_buffer() const
|
||||
{
|
||||
return mesh_stream_allocator.get_buffer(0, 0);
|
||||
}
|
||||
|
||||
const Buffer *ResourceManager::get_cluster_bounds_buffer() const
|
||||
{
|
||||
if (mesh_encoding == MeshEncoding::MeshletEncoded)
|
||||
return mesh_header_allocator.get_buffer(0, 1);
|
||||
else
|
||||
return indirect_buffer_allocator.get_buffer(0, 1);
|
||||
}
|
||||
|
||||
bool ResourceManager::mesh_rendering_is_hierarchical_task() const
|
||||
{
|
||||
return device->get_gpu_properties().vendorID == VENDOR_ID_AMD;
|
||||
}
|
||||
|
||||
bool ResourceManager::mesh_rendering_is_local_invocation_indexed() const
|
||||
{
|
||||
#if 0
|
||||
bool local_invocation_indexed =
|
||||
device->get_device_features().mesh_shader_properties.prefersLocalInvocationPrimitiveOutput ||
|
||||
device->get_device_features().mesh_shader_properties.prefersLocalInvocationVertexOutput;
|
||||
|
||||
return local_invocation_indexed;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ResourceManager::mesh_rendering_is_wave_culled() const
|
||||
{
|
||||
return device->supports_subgroup_size_log2(true, 5, 5, VK_SHADER_STAGE_MESH_BIT_EXT) &&
|
||||
device->get_device_features().vk13_props.minSubgroupSize == 32;
|
||||
}
|
||||
|
||||
MeshBufferAllocator::MeshBufferAllocator(Device &device, uint32_t sub_block_size, uint32_t num_sub_blocks_in_arena_log2)
|
||||
: global_allocator(device)
|
||||
{
|
||||
init(sub_block_size, num_sub_blocks_in_arena_log2, &global_allocator);
|
||||
}
|
||||
|
||||
void MeshBufferAllocator::set_soa_count(unsigned soa_count)
|
||||
{
|
||||
VK_ASSERT(soa_count <= Internal::MeshGlobalAllocator::MaxSoACount);
|
||||
global_allocator.soa_count = soa_count;
|
||||
}
|
||||
|
||||
void MeshBufferAllocator::set_element_size(unsigned soa_index, uint32_t element_size)
|
||||
{
|
||||
VK_ASSERT(soa_index < global_allocator.soa_count);
|
||||
global_allocator.element_size[soa_index] = element_size;
|
||||
}
|
||||
|
||||
uint32_t MeshBufferAllocator::get_element_size(unsigned soa_index) const
|
||||
{
|
||||
VK_ASSERT(soa_index < global_allocator.soa_count);
|
||||
return global_allocator.element_size[soa_index];
|
||||
}
|
||||
|
||||
const Buffer *MeshBufferAllocator::get_buffer(unsigned index, unsigned soa_index) const
|
||||
{
|
||||
VK_ASSERT(soa_index < global_allocator.soa_count);
|
||||
index = index * global_allocator.soa_count + soa_index;
|
||||
|
||||
// Avoid any race condition.
|
||||
if (index < soa_index && global_allocator.preallocated_handles[soa_index])
|
||||
return global_allocator.preallocated_handles[soa_index];
|
||||
else if (index < global_allocator.global_buffers.size())
|
||||
return global_allocator.global_buffers[index].get();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
uint32_t MeshGlobalAllocator::allocate(uint32_t count)
|
||||
{
|
||||
BufferCreateInfo info = {};
|
||||
|
||||
uint32_t target_index = UINT32_MAX;
|
||||
uint32_t search_index = 0;
|
||||
|
||||
for (uint32_t i = 0, n = global_buffers.size(); i < n; i += soa_count, search_index++)
|
||||
{
|
||||
if (!global_buffers[i])
|
||||
{
|
||||
target_index = search_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target_index == UINT32_MAX)
|
||||
{
|
||||
if (!global_buffers.empty())
|
||||
return UINT32_MAX;
|
||||
|
||||
target_index = search_index;
|
||||
for (uint32_t i = 0; i < soa_count; i++)
|
||||
global_buffers.emplace_back();
|
||||
}
|
||||
|
||||
for (uint32_t soa_index = 0; soa_index < soa_count; soa_index++)
|
||||
{
|
||||
info.size = VkDeviceSize(count) * element_size[soa_index];
|
||||
info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||
info.domain = BufferDomain::Device;
|
||||
|
||||
if (preallocated[soa_index] && preallocated[soa_index]->get_create_info().size >= info.size)
|
||||
std::swap(preallocated[soa_index], global_buffers[target_index * soa_count + soa_index]);
|
||||
else
|
||||
global_buffers[target_index * soa_count + soa_index] = device.create_buffer(info);
|
||||
}
|
||||
|
||||
return target_index;
|
||||
}
|
||||
|
||||
void MeshGlobalAllocator::prime(uint32_t count, const void *opaque_meta)
|
||||
{
|
||||
auto *opaque = static_cast<const PrimeOpaque *>(opaque_meta);
|
||||
BufferCreateInfo info = {};
|
||||
for (uint32_t i = 0; i < soa_count; i++)
|
||||
{
|
||||
if (preallocated[i])
|
||||
continue;
|
||||
|
||||
info.size = VkDeviceSize(count) * element_size[i];
|
||||
info.usage = opaque->usage;
|
||||
info.domain = opaque->domain;
|
||||
preallocated[i] = device.create_buffer(info);
|
||||
preallocated_handles[i] = preallocated[i].get();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshGlobalAllocator::free(uint32_t index)
|
||||
{
|
||||
index *= soa_count;
|
||||
VK_ASSERT(index < global_buffers.size());
|
||||
for (uint32_t i = 0; i < soa_count; i++)
|
||||
{
|
||||
std::swap(preallocated[i], global_buffers[index + i]);
|
||||
global_buffers[index + i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
MeshGlobalAllocator::MeshGlobalAllocator(Device &device_)
|
||||
: device(device_)
|
||||
{}
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/* Copyright (c) 2017-2026 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 "image.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "asset_manager.hpp"
|
||||
#include "meshlet.hpp"
|
||||
#include "arena_allocator.hpp"
|
||||
#include "small_vector.hpp"
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class MemoryMappedTexture;
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
struct MeshGlobalAllocator final : Util::SliceBackingAllocator
|
||||
{
|
||||
explicit MeshGlobalAllocator(Device &device);
|
||||
uint32_t allocate(uint32_t count) override;
|
||||
void free(uint32_t index) override;
|
||||
|
||||
struct PrimeOpaque
|
||||
{
|
||||
VkBufferUsageFlags usage;
|
||||
BufferDomain domain;
|
||||
};
|
||||
|
||||
void prime(uint32_t count, const void *opaque_meta) override;
|
||||
|
||||
enum { MaxSoACount = 3 }; // Position, attribute, skinning.
|
||||
|
||||
Device &device;
|
||||
uint32_t element_size[MaxSoACount] = {};
|
||||
uint32_t soa_count = 1;
|
||||
Util::SmallVector<BufferHandle> global_buffers;
|
||||
BufferHandle preallocated[MaxSoACount];
|
||||
const Buffer *preallocated_handles[MaxSoACount] = {};
|
||||
};
|
||||
}
|
||||
|
||||
class MeshBufferAllocator : public Util::SliceAllocator
|
||||
{
|
||||
public:
|
||||
MeshBufferAllocator(Device &device, uint32_t sub_block_size, uint32_t num_sub_blocks_in_arena_log2);
|
||||
void set_soa_count(unsigned soa_count);
|
||||
void set_element_size(unsigned soa_index, uint32_t element_size);
|
||||
uint32_t get_element_size(unsigned soa_index) const;
|
||||
const Buffer *get_buffer(unsigned index, unsigned soa_index) const;
|
||||
|
||||
private:
|
||||
Internal::MeshGlobalAllocator global_allocator;
|
||||
};
|
||||
|
||||
class ResourceManager final : private Granite::AssetInstantiatorInterface
|
||||
{
|
||||
public:
|
||||
explicit ResourceManager(Device *device);
|
||||
~ResourceManager() override;
|
||||
void init();
|
||||
|
||||
enum class MeshEncoding
|
||||
{
|
||||
MeshletEncoded,
|
||||
MeshletDecoded,
|
||||
VBOAndIBOMDI,
|
||||
Classic
|
||||
};
|
||||
|
||||
const Vulkan::ImageView *get_image_view(Granite::AssetID id) const
|
||||
{
|
||||
if (id.id < views.size())
|
||||
return views[id.id];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Vulkan::ImageView *get_image_view_blocking(Granite::AssetID id);
|
||||
|
||||
struct DrawRange
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t count;
|
||||
Meshlet::MeshStyle style;
|
||||
};
|
||||
|
||||
union DrawCall
|
||||
{
|
||||
DrawRange meshlet;
|
||||
VkDrawIndexedIndirectCommand indexed;
|
||||
};
|
||||
|
||||
DrawCall get_mesh_draw_range(Granite::AssetID id) const
|
||||
{
|
||||
if (id.id < draws.size())
|
||||
return draws[id.id];
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
MeshEncoding get_mesh_encoding() const
|
||||
{
|
||||
return mesh_encoding;
|
||||
}
|
||||
|
||||
const Buffer *get_index_buffer() const;
|
||||
const Buffer *get_position_buffer() const;
|
||||
const Buffer *get_attribute_buffer() const;
|
||||
const Buffer *get_skinning_buffer() const;
|
||||
const Buffer *get_indirect_buffer() const;
|
||||
|
||||
const Buffer *get_meshlet_payload_buffer() const;
|
||||
const Buffer *get_meshlet_header_buffer() const;
|
||||
const Buffer *get_meshlet_stream_header_buffer() const;
|
||||
|
||||
const Buffer *get_cluster_bounds_buffer() const;
|
||||
|
||||
// Mesh shading requires some vendor specific tuning.
|
||||
bool mesh_rendering_is_hierarchical_task() const;
|
||||
bool mesh_rendering_is_local_invocation_indexed() const;
|
||||
bool mesh_rendering_is_wave_culled() const;
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
Granite::AssetManager *manager = nullptr;
|
||||
|
||||
void latch_handles() override;
|
||||
uint64_t estimate_cost_asset(Granite::AssetID id, Granite::File &file) override;
|
||||
void instantiate_asset(Granite::AssetManager &manager, Granite::TaskGroup *task,
|
||||
Granite::AssetID id, Granite::File &file) override;
|
||||
void release_asset(Granite::AssetID id) override;
|
||||
void set_id_bounds(uint32_t bound) override;
|
||||
void set_asset_class(Granite::AssetID id, Granite::AssetClass asset_class) override;
|
||||
|
||||
struct Asset
|
||||
{
|
||||
ImageHandle image;
|
||||
struct
|
||||
{
|
||||
Util::AllocatedSlice index_or_payload, attr_or_stream, indirect_or_header;
|
||||
DrawCall draw;
|
||||
} mesh;
|
||||
Granite::AssetClass asset_class = Granite::AssetClass::ImageZeroable;
|
||||
bool latchable = false;
|
||||
};
|
||||
|
||||
std::mutex lock;
|
||||
std::condition_variable cond;
|
||||
|
||||
std::vector<Asset> assets;
|
||||
std::vector<const ImageView *> views;
|
||||
std::vector<DrawCall> draws;
|
||||
std::vector<Granite::AssetID> updates;
|
||||
|
||||
ImageHandle fallback_color;
|
||||
ImageHandle fallback_normal;
|
||||
ImageHandle fallback_zero;
|
||||
ImageHandle fallback_pbr;
|
||||
|
||||
ImageHandle create_gtx(Granite::FileMappingHandle mapping, Granite::AssetID id);
|
||||
ImageHandle create_gtx(const MemoryMappedTexture &mapping, Granite::AssetID id);
|
||||
ImageHandle create_other(const Granite::FileMapping &mapping, Granite::AssetClass asset_class, Granite::AssetID id);
|
||||
const ImageHandle &get_fallback_image(Granite::AssetClass asset_class);
|
||||
|
||||
void instantiate_asset(Granite::AssetManager &manager, Granite::AssetID id, Granite::File &file);
|
||||
void instantiate_asset_image(Granite::AssetManager &manager, Granite::AssetID id, Granite::File &file);
|
||||
void instantiate_asset_mesh(Granite::AssetManager &manager, Granite::AssetID id, Granite::File &file);
|
||||
|
||||
std::mutex mesh_allocator_lock;
|
||||
MeshBufferAllocator index_buffer_allocator;
|
||||
MeshBufferAllocator attribute_buffer_allocator;
|
||||
MeshBufferAllocator indirect_buffer_allocator;
|
||||
MeshBufferAllocator mesh_header_allocator;
|
||||
MeshBufferAllocator mesh_stream_allocator;
|
||||
MeshBufferAllocator mesh_payload_allocator;
|
||||
|
||||
MeshEncoding mesh_encoding = MeshEncoding::Classic;
|
||||
|
||||
bool allocate_asset_mesh(Granite::AssetID id, const Meshlet::MeshView &view);
|
||||
|
||||
void init_mesh_assets();
|
||||
};
|
||||
}
|
||||
+1039
File diff suppressed because it is too large
Load Diff
+277
@@ -0,0 +1,277 @@
|
||||
/* Copyright (c) 2017-2026 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 "shader.hpp"
|
||||
#include "vulkan_common.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "hash.hpp"
|
||||
#include "read_write_lock.hpp"
|
||||
|
||||
namespace Granite
|
||||
{
|
||||
class GLSLCompiler;
|
||||
struct TaskGroup;
|
||||
enum class Stage;
|
||||
}
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
struct ShaderTemplateVariant;
|
||||
struct PrecomputedMeta : Util::IntrusiveHashMapEnabled<PrecomputedMeta>
|
||||
{
|
||||
PrecomputedMeta(Util::Hash source_hash_, Util::Hash shader_hash_)
|
||||
: source_hash(source_hash_), shader_hash(shader_hash_)
|
||||
{
|
||||
}
|
||||
Util::Hash source_hash;
|
||||
Util::Hash shader_hash;
|
||||
};
|
||||
using PrecomputedShaderCache = VulkanCache<PrecomputedMeta>;
|
||||
using ReflectionCache = VulkanCache<Util::IntrusivePODWrapper<ResourceLayout>>;
|
||||
|
||||
struct MetaCache
|
||||
{
|
||||
PrecomputedShaderCache variant_to_shader;
|
||||
ReflectionCache shader_to_layout;
|
||||
};
|
||||
|
||||
class ShaderManager;
|
||||
class Device;
|
||||
|
||||
struct ShaderTemplateVariant : public Util::IntrusiveHashMapEnabled<ShaderTemplateVariant>
|
||||
{
|
||||
Util::Hash hash = 0;
|
||||
Util::Hash spirv_hash = 0;
|
||||
std::vector<uint32_t> spirv;
|
||||
std::vector<std::pair<std::string, int>> defines;
|
||||
Shader *precompiled_shader = nullptr;
|
||||
unsigned instance = 0;
|
||||
|
||||
Vulkan::Shader *resolve(Vulkan::Device &device) const;
|
||||
};
|
||||
|
||||
class ShaderTemplate : public Util::IntrusiveHashMapEnabled<ShaderTemplate>
|
||||
{
|
||||
public:
|
||||
ShaderTemplate(Device *device, const std::string &shader_path,
|
||||
ShaderStage force_stage, MetaCache &cache,
|
||||
Util::Hash path_hash, const std::vector<std::string> &include_directories);
|
||||
~ShaderTemplate();
|
||||
|
||||
bool init();
|
||||
|
||||
const ShaderTemplateVariant *register_variant(const std::vector<std::pair<std::string, int>> *defines,
|
||||
Shader *precompiled_shader);
|
||||
void register_dependencies(ShaderManager &manager);
|
||||
|
||||
Util::Hash get_path_hash() const
|
||||
{
|
||||
return path_hash;
|
||||
}
|
||||
|
||||
const std::string &get_path() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
ShaderStage get_stage() const
|
||||
{
|
||||
return force_stage;
|
||||
}
|
||||
|
||||
VulkanCache<ShaderTemplateVariant> &get_variants()
|
||||
{
|
||||
return variants;
|
||||
}
|
||||
|
||||
#ifndef GRANITE_SHIPPING
|
||||
// We'll never want to recompile shaders in runtime outside a dev environment.
|
||||
void recompile();
|
||||
#endif
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
std::string path;
|
||||
ShaderStage force_stage;
|
||||
MetaCache &cache;
|
||||
Util::Hash path_hash = 0;
|
||||
std::vector<uint32_t> static_shader;
|
||||
#ifdef GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER
|
||||
std::unique_ptr<Granite::GLSLCompiler> compiler;
|
||||
const std::vector<std::string> &include_directories;
|
||||
void update_variant_cache(const ShaderTemplateVariant &variant);
|
||||
Util::Hash source_hash = 0;
|
||||
#ifndef GRANITE_SHIPPING
|
||||
// We'll never want to recompile shaders in runtime outside a dev environment.
|
||||
void recompile_variant(ShaderTemplateVariant &variant);
|
||||
#endif
|
||||
#endif
|
||||
VulkanCache<ShaderTemplateVariant> variants;
|
||||
};
|
||||
|
||||
class ShaderProgramVariant : public Util::IntrusiveHashMapEnabled<ShaderProgramVariant>
|
||||
{
|
||||
public:
|
||||
explicit ShaderProgramVariant(Device *device);
|
||||
Vulkan::Program *get_program();
|
||||
|
||||
private:
|
||||
friend class ShaderProgram;
|
||||
Device *device;
|
||||
const ShaderTemplateVariant *stages[static_cast<unsigned>(Vulkan::ShaderStage::Count)] = {};
|
||||
std::unique_ptr<ImmutableSamplerBank> sampler_bank;
|
||||
|
||||
#ifndef GRANITE_SHIPPING
|
||||
// We'll never want to recompile shaders in runtime outside a dev environment.
|
||||
std::atomic_uint shader_instance[static_cast<unsigned>(Vulkan::ShaderStage::Count)];
|
||||
std::atomic<Vulkan::Program *> program;
|
||||
Util::RWSpinLock instance_lock;
|
||||
#endif
|
||||
|
||||
Vulkan::Program *get_program_compute();
|
||||
Vulkan::Program *get_program_graphics();
|
||||
};
|
||||
|
||||
class ShaderProgram : public Util::IntrusiveHashMapEnabled<ShaderProgram>
|
||||
{
|
||||
public:
|
||||
ShaderProgram(Device *device_, ShaderTemplate *compute)
|
||||
: device(device_)
|
||||
{
|
||||
set_stage(Vulkan::ShaderStage::Compute, compute);
|
||||
}
|
||||
|
||||
ShaderProgram(Device *device_, ShaderTemplate *vert, ShaderTemplate *frag)
|
||||
: device(device_)
|
||||
{
|
||||
set_stage(Vulkan::ShaderStage::Vertex, vert);
|
||||
set_stage(Vulkan::ShaderStage::Fragment, frag);
|
||||
}
|
||||
|
||||
ShaderProgram(Device *device_, ShaderTemplate *task, ShaderTemplate *mesh, ShaderTemplate *frag)
|
||||
: device(device_)
|
||||
{
|
||||
if (task)
|
||||
set_stage(Vulkan::ShaderStage::Task, task);
|
||||
set_stage(Vulkan::ShaderStage::Mesh, mesh);
|
||||
set_stage(Vulkan::ShaderStage::Fragment, frag);
|
||||
}
|
||||
|
||||
void set_stage(Vulkan::ShaderStage stage, ShaderTemplate *shader);
|
||||
ShaderProgramVariant *register_variant(const std::vector<std::pair<std::string, int>> &defines,
|
||||
const ImmutableSamplerBank *sampler_bank = nullptr);
|
||||
|
||||
ShaderProgramVariant *register_precompiled_variant(
|
||||
Shader *vert, Shader *frag,
|
||||
const std::vector<std::pair<std::string, int>> &defines,
|
||||
const ImmutableSamplerBank *sampler_bank = nullptr);
|
||||
|
||||
ShaderProgramVariant *register_precompiled_variant(
|
||||
Shader *comp,
|
||||
const std::vector<std::pair<std::string, int>> &defines,
|
||||
const ImmutableSamplerBank *sampler_bank = nullptr);
|
||||
|
||||
ShaderProgramVariant *register_precompiled_variant(
|
||||
Shader *task, Shader *mesh, Shader *frag,
|
||||
const std::vector<std::pair<std::string, int>> &defines,
|
||||
const ImmutableSamplerBank *sampler_bank = nullptr);
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
ShaderTemplate *stages[static_cast<unsigned>(Vulkan::ShaderStage::Count)] = {};
|
||||
VulkanCacheReadWrite<ShaderProgramVariant> variant_cache;
|
||||
|
||||
ShaderProgramVariant *register_variant(Shader * const *precompiled_shaders,
|
||||
const std::vector<std::pair<std::string, int>> &defines,
|
||||
const ImmutableSamplerBank *sampler_bank);
|
||||
};
|
||||
|
||||
class ShaderManager
|
||||
{
|
||||
public:
|
||||
explicit ShaderManager(Device *device_)
|
||||
: device(device_)
|
||||
{
|
||||
}
|
||||
|
||||
bool load_shader_cache(const std::string &path, Granite::TaskGroup *shader_compilation_group);
|
||||
bool save_shader_cache(const std::string &path);
|
||||
|
||||
void add_include_directory(const std::string &path);
|
||||
|
||||
~ShaderManager();
|
||||
ShaderProgram *register_graphics(const std::string &task, const std::string &mesh, const std::string &fragment);
|
||||
ShaderProgram *register_graphics(const std::string &vertex, const std::string &fragment);
|
||||
ShaderProgram *register_compute(const std::string &compute);
|
||||
|
||||
#ifdef GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER
|
||||
void register_dependency(ShaderTemplate *shader, const std::string &dependency);
|
||||
void register_dependency_nolock(ShaderTemplate *shader, const std::string &dependency);
|
||||
#endif
|
||||
|
||||
bool get_shader_hash_by_variant_hash(Util::Hash variant_hash, Util::Hash &shader_hash) const;
|
||||
bool get_resource_layout_by_shader_hash(Util::Hash shader_hash, ResourceLayout &layout) const;
|
||||
void register_shader_from_variant_hash(Util::Hash variant_hash, Util::Hash source_hash,
|
||||
Util::Hash shader_hash, const ResourceLayout &layout);
|
||||
|
||||
Device *get_device()
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
void promote_read_write_caches_to_read_only();
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
|
||||
MetaCache meta_cache;
|
||||
VulkanCache<ShaderTemplate> shaders;
|
||||
VulkanCache<ShaderProgram> programs;
|
||||
std::vector<std::string> include_directories;
|
||||
|
||||
ShaderTemplate *get_template(const std::string &source, ShaderStage force_stage);
|
||||
|
||||
#ifdef GRANITE_VULKAN_SHADER_MANAGER_RUNTIME_COMPILER
|
||||
std::unordered_map<std::string, std::unordered_set<ShaderTemplate *>> dependees;
|
||||
std::mutex dependency_lock;
|
||||
|
||||
#ifndef GRANITE_SHIPPING
|
||||
// We'll never want to recompile shaders in runtime outside a dev environment.
|
||||
struct Notify
|
||||
{
|
||||
Granite::FilesystemBackend *backend;
|
||||
Granite::FileNotifyHandle handle;
|
||||
};
|
||||
std::unordered_map<std::string, Notify> directory_watches;
|
||||
void add_directory_watch(const std::string &source);
|
||||
void recompile(const Granite::FileNotifyInfo &info);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
||||
/* Copyright (c) 2017-2026 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 "slab_allocator.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 ImageLayout;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// Avoid cross-dependency in header.
|
||||
class Buffer;
|
||||
|
||||
struct DescriptorBufferAllocation
|
||||
{
|
||||
inline VkDeviceSize get_offset() const { return backing_slice.offset; }
|
||||
inline VkDeviceSize get_size() const { return backing_slice.count; }
|
||||
|
||||
// Internal detail.
|
||||
Util::AllocatedSlice backing_slice;
|
||||
};
|
||||
|
||||
using DescriptorCopyFunc = void (*)(uint8_t *, const uint8_t *, size_t size);
|
||||
using DescriptorCopyNFunc = void (*)(uint8_t *, const uint8_t * const *, size_t count, size_t size);
|
||||
|
||||
struct CachedDescriptorPayload
|
||||
{
|
||||
uint8_t *ptr;
|
||||
VkDescriptorType type;
|
||||
uint32_t heap_index;
|
||||
explicit operator bool() const { return ptr != nullptr; }
|
||||
};
|
||||
|
||||
struct CachedImageView
|
||||
{
|
||||
VkImageView view; // For legacy and descriptor buffer.
|
||||
|
||||
// For DB, this is used all the time. For heap, only occasionally as needed,
|
||||
// usually for bindless.
|
||||
CachedDescriptorPayload sampled; // SHADER_READ_ONLY
|
||||
CachedDescriptorPayload input_attachment; // INPUT_ATTACHMENT + read only (if applicable)
|
||||
CachedDescriptorPayload input_attachment_feedback; // INPUT_ATTACHMENT + GENERAL (if applicable)
|
||||
CachedDescriptorPayload storage; // For storage image, always GENERAL layout.
|
||||
};
|
||||
|
||||
struct CachedBufferView
|
||||
{
|
||||
VkBufferView view;
|
||||
CachedDescriptorPayload uniform;
|
||||
CachedDescriptorPayload storage;
|
||||
};
|
||||
|
||||
struct BufferViewCreateInfo;
|
||||
|
||||
class DescriptorBufferAllocator : private Util::SliceAllocator
|
||||
{
|
||||
public:
|
||||
bool init(Device *device);
|
||||
~DescriptorBufferAllocator();
|
||||
|
||||
void teardown();
|
||||
|
||||
struct HeapInfo
|
||||
{
|
||||
VkDeviceAddress va;
|
||||
uint8_t *mapped;
|
||||
VkDeviceSize reserved_offset;
|
||||
VkDeviceSize size;
|
||||
};
|
||||
|
||||
HeapInfo get_resource_heap() const { return resource_heap; }
|
||||
// Only for descriptor_heap.
|
||||
HeapInfo get_sampler_heap() const { return sampler_heap; }
|
||||
|
||||
DescriptorBufferAllocation allocate(VkDeviceSize size);
|
||||
void free(const DescriptorBufferAllocation &alloc);
|
||||
void free(const DescriptorBufferAllocation *alloc, size_t count);
|
||||
|
||||
uint32_t get_descriptor_size_for_type(VkDescriptorType type) const;
|
||||
|
||||
bool create_image_view(const VkImageViewCreateInfo &info, VkImageUsageFlags usage,
|
||||
ImageLayout layout, CachedImageView &view);
|
||||
void free_image_view(const CachedImageView &view);
|
||||
|
||||
bool create_buffer_view(const BufferViewCreateInfo &info, CachedBufferView &view);
|
||||
void free_buffer_view(const CachedBufferView &view);
|
||||
|
||||
#define IMPL_TYPE(type, desc_type) \
|
||||
inline void copy_##type(uint8_t *dst, const uint8_t *src) const { type##_copy.func(dst, src, type##_copy.size); } \
|
||||
inline void copy_##type##_n(uint8_t *dst, const uint8_t * const *src, size_t count) const { type##_copy.func_n(dst, src, count, type##_copy.size); } \
|
||||
inline CachedDescriptorPayload alloc_##type() { return { type##_copy.slab.allocate(), desc_type }; } \
|
||||
inline void free_##type(uint8_t *ptr) { type##_copy.slab.free(ptr); }
|
||||
|
||||
IMPL_TYPE(combined_image, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
|
||||
IMPL_TYPE(sampled_image, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE)
|
||||
IMPL_TYPE(storage_image, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)
|
||||
IMPL_TYPE(sampler, VK_DESCRIPTOR_TYPE_SAMPLER)
|
||||
IMPL_TYPE(input_attachment, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT)
|
||||
IMPL_TYPE(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
|
||||
IMPL_TYPE(ssbo, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
|
||||
IMPL_TYPE(uniform_texel, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER)
|
||||
IMPL_TYPE(storage_texel, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER)
|
||||
|
||||
void free_cached_descriptors(const CachedDescriptorPayload *payloads, size_t count);
|
||||
|
||||
// On heap, this is a dummy handle.
|
||||
VkSampler create_sampler(const VkSamplerCreateInfo *info);
|
||||
void destroy_sampler(VkSampler sampler);
|
||||
|
||||
private:
|
||||
Device *device = nullptr;
|
||||
Buffer *resource_buffer = nullptr;
|
||||
Buffer *sampler_buffer = nullptr;
|
||||
Util::SliceBackingAllocatorVA backing_va;
|
||||
VkDeviceSize alignment = 0;
|
||||
VkDeviceSize sub_block_size = 0;
|
||||
std::mutex lock;
|
||||
|
||||
HeapInfo resource_heap = {}, sampler_heap = {};
|
||||
|
||||
struct DescriptorTypeInfo
|
||||
{
|
||||
DescriptorCopyFunc func;
|
||||
DescriptorCopyNFunc func_n;
|
||||
size_t size;
|
||||
Util::ThreadSafeSlabAllocator slab;
|
||||
};
|
||||
DescriptorTypeInfo sampled_image_copy, storage_image_copy, combined_image_copy, sampler_copy, input_attachment_copy;
|
||||
DescriptorTypeInfo ubo_copy, ssbo_copy, uniform_texel_copy, storage_texel_copy;
|
||||
void init_copy_func(DescriptorTypeInfo &info, VkDescriptorType type) const;
|
||||
|
||||
VkDeviceSize total_size = 0;
|
||||
VkDeviceSize high_water_mark = 0;
|
||||
std::vector<uint32_t> heap_resource_indices;
|
||||
std::vector<uint32_t> heap_sampler_indices;
|
||||
|
||||
// For descriptor heap.
|
||||
uint32_t allocate_single_resource_heap_entry();
|
||||
void free_single_resource_heap_entry(uint32_t index);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/* Copyright (c) 2017-2026 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 "meshlet.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "buffer.hpp"
|
||||
#include "device.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
namespace Meshlet
|
||||
{
|
||||
MeshView create_mesh_view(const Granite::FileMapping &mapping)
|
||||
{
|
||||
MeshView view = {};
|
||||
|
||||
if (mapping.get_size() < sizeof(magic) + sizeof(FormatHeader))
|
||||
{
|
||||
LOGE("MESHLET2 file too small.\n");
|
||||
return view;
|
||||
}
|
||||
|
||||
auto *ptr = mapping.data<unsigned char>();
|
||||
auto *end_ptr = ptr + mapping.get_size();
|
||||
|
||||
if (memcmp(ptr, magic, sizeof(magic)) != 0)
|
||||
{
|
||||
LOGE("Invalid MESHLET2 magic.\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
ptr += sizeof(magic);
|
||||
|
||||
view.format_header = reinterpret_cast<const FormatHeader *>(ptr);
|
||||
ptr += sizeof(*view.format_header);
|
||||
|
||||
if (end_ptr - ptr < ptrdiff_t(view.format_header->meshlet_count * sizeof(Bound)))
|
||||
return {};
|
||||
view.bounds = reinterpret_cast<const Bound *>(ptr);
|
||||
ptr += view.format_header->meshlet_count * sizeof(Bound);
|
||||
|
||||
size_t num_bounds_256 = (view.format_header->meshlet_count + ChunkFactor - 1) / ChunkFactor;
|
||||
|
||||
if (end_ptr - ptr < ptrdiff_t(num_bounds_256 * sizeof(Bound)))
|
||||
return {};
|
||||
view.bounds_256 = reinterpret_cast<const Bound *>(ptr);
|
||||
ptr += num_bounds_256 * sizeof(Bound);
|
||||
|
||||
view.num_bounds = view.format_header->meshlet_count;
|
||||
view.num_bounds_256 = num_bounds_256;
|
||||
|
||||
if (end_ptr - ptr < ptrdiff_t(view.format_header->meshlet_count * view.format_header->stream_count * sizeof(Stream)))
|
||||
return {};
|
||||
view.streams = reinterpret_cast<const Stream *>(ptr);
|
||||
ptr += view.format_header->meshlet_count * view.format_header->stream_count * sizeof(Stream);
|
||||
|
||||
if (!view.format_header->payload_size_words)
|
||||
return {};
|
||||
|
||||
if (end_ptr - ptr < ptrdiff_t(view.format_header->payload_size_words * sizeof(PayloadWord)))
|
||||
return {};
|
||||
view.payload = reinterpret_cast<const PayloadWord *>(ptr);
|
||||
|
||||
for (uint32_t i = 0, n = view.format_header->meshlet_count; i < n; i++)
|
||||
{
|
||||
auto counts = view.streams[i * view.format_header->stream_count].u.counts;
|
||||
view.total_primitives += counts.prim_count;
|
||||
view.total_vertices += counts.vert_count;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
static void upload_indirect_buffer(CommandBuffer &cmd, const Vulkan::Buffer &indirect_buffer, uint32_t alloc_offset,
|
||||
const MeshView &view, RuntimeStyle runtime_style,
|
||||
uint32_t global_prim_offset, uint32_t global_vert_offset)
|
||||
{
|
||||
size_t total_padded_meshlets = view.num_bounds_256 * ChunkFactor;
|
||||
size_t total_meshlets = view.format_header->meshlet_count;
|
||||
|
||||
uint32_t prim_offset = global_prim_offset;
|
||||
uint32_t vert_offset = global_vert_offset;
|
||||
|
||||
if (runtime_style == RuntimeStyle::Meshlet)
|
||||
{
|
||||
constexpr size_t Stride = sizeof(RuntimeHeaderDecoded) * ChunkFactor;
|
||||
auto *indirect = static_cast<RuntimeHeaderDecoded *>(
|
||||
cmd.update_buffer(indirect_buffer, alloc_offset * Stride,
|
||||
view.num_bounds_256 * Stride));
|
||||
|
||||
for (uint32_t i = 0; i < total_meshlets; i++)
|
||||
{
|
||||
auto &counts = view.streams[i * view.format_header->stream_count].u.counts;
|
||||
uint32_t prim_count = counts.prim_count;
|
||||
uint32_t vert_count = counts.vert_count;
|
||||
|
||||
indirect[i].primitive_offset = prim_offset;
|
||||
indirect[i].vertex_offset = vert_offset;
|
||||
indirect[i].primitive_count = prim_count;
|
||||
indirect[i].vertex_count = vert_count;
|
||||
|
||||
prim_offset += prim_count;
|
||||
vert_offset += vert_count;
|
||||
}
|
||||
|
||||
memset(indirect + total_meshlets, 0,
|
||||
(total_padded_meshlets - total_meshlets) * sizeof(RuntimeHeaderDecoded));
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr size_t Stride = sizeof(RuntimeHeaderDecodedMDI);
|
||||
auto *indirect = static_cast<RuntimeHeaderDecodedMDI *>(
|
||||
cmd.update_buffer(indirect_buffer, alloc_offset * Stride,
|
||||
view.num_bounds_256 * Stride));
|
||||
|
||||
for (uint32_t i = 0; i < view.num_bounds_256; i++)
|
||||
{
|
||||
uint32_t chunks = std::min<uint32_t>(total_meshlets - i * ChunkFactor, ChunkFactor);
|
||||
|
||||
RuntimeHeaderDecodedMDI draw = {};
|
||||
draw.firstIndex = 3 * prim_offset;
|
||||
draw.vertexOffset = int32_t(vert_offset);
|
||||
|
||||
for (uint32_t chunk = 0; chunk < chunks; chunk++)
|
||||
{
|
||||
auto &counts = view.streams[(i * ChunkFactor + chunk) *
|
||||
view.format_header->stream_count].u.counts;
|
||||
draw.indexCount += counts.prim_count;
|
||||
vert_offset += counts.vert_count;
|
||||
prim_offset += counts.prim_count;
|
||||
}
|
||||
|
||||
draw.indexCount *= 3;
|
||||
indirect[i] = draw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool decode_mesh(CommandBuffer &cmd, const DecodeInfo &info, const MeshView &view)
|
||||
{
|
||||
if (!cmd.get_device().supports_subgroup_size_log2(true, 5, 7))
|
||||
{
|
||||
LOGE("Device does not support subgroup paths.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!info.streams[0])
|
||||
{
|
||||
LOGE("Decode stream 0 must be set.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!info.ibo)
|
||||
{
|
||||
LOGE("Output IBO must be set.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
BufferCreateInfo buf_info = {};
|
||||
buf_info.domain = BufferDomain::LinkedDeviceHost;
|
||||
buf_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
|
||||
buf_info.size = view.format_header->meshlet_count * view.format_header->stream_count * sizeof(*view.streams);
|
||||
auto meshlet_stream_buffer = cmd.get_device().create_buffer(buf_info, view.streams);
|
||||
|
||||
bool meshlet_runtime = info.runtime_style == RuntimeStyle::Meshlet;
|
||||
cmd.set_program("builtin://shaders/decode/meshlet_decode.comp");
|
||||
|
||||
cmd.enable_subgroup_size_control(true);
|
||||
if (cmd.get_device().supports_subgroup_size_log2(true, 5, 5))
|
||||
cmd.set_subgroup_size_log2(true, 5, 5);
|
||||
else
|
||||
cmd.set_subgroup_size_log2(true, 5, 7);
|
||||
|
||||
cmd.set_storage_buffer(0, 0, *meshlet_stream_buffer);
|
||||
cmd.set_storage_buffer(0, 1, *info.payload);
|
||||
cmd.set_storage_buffer(0, 2, *info.ibo);
|
||||
|
||||
cmd.set_specialization_constant_mask(0x1f);
|
||||
cmd.set_specialization_constant(0, view.format_header->stream_count);
|
||||
cmd.set_specialization_constant(1, (info.flags & DECODE_MODE_UNROLLED_MESH) != 0);
|
||||
cmd.set_specialization_constant(2, uint32_t(info.target_style));
|
||||
cmd.set_specialization_constant(3, uint32_t(meshlet_runtime));
|
||||
cmd.set_specialization_constant(4, (info.flags & DECODE_MODE_INDEX_16) != 0);
|
||||
|
||||
for (unsigned i = 0; i < 3; i++)
|
||||
cmd.set_storage_buffer(0, 3 + i, info.streams[i] ? *info.streams[i] : *info.streams[0]);
|
||||
|
||||
struct Offsets
|
||||
{
|
||||
uint32_t primitive_output_offset;
|
||||
uint32_t vertex_output_offset;
|
||||
uint32_t index_offset;
|
||||
};
|
||||
|
||||
std::vector<Offsets> decode_offsets;
|
||||
Offsets offsets = {};
|
||||
|
||||
decode_offsets.reserve(view.format_header->meshlet_count);
|
||||
for (uint32_t i = 0; i < view.format_header->meshlet_count; i++)
|
||||
{
|
||||
if (info.runtime_style == RuntimeStyle::MDI && (info.flags & DECODE_MODE_UNROLLED_MESH) == 0)
|
||||
{
|
||||
uint32_t mdi_start_index = i & ~(Meshlet::ChunkFactor - 1);
|
||||
if (mdi_start_index == i)
|
||||
offsets.index_offset = 0;
|
||||
}
|
||||
|
||||
decode_offsets.push_back(offsets);
|
||||
|
||||
auto &counts = view.streams[i * view.format_header->stream_count].u.counts;
|
||||
offsets.primitive_output_offset += counts.prim_count;
|
||||
offsets.vertex_output_offset += counts.vert_count;
|
||||
|
||||
if (!meshlet_runtime)
|
||||
offsets.index_offset += counts.vert_count;
|
||||
}
|
||||
|
||||
buf_info.domain = BufferDomain::LinkedDeviceHost;
|
||||
buf_info.size = decode_offsets.size() * sizeof(decode_offsets.front());
|
||||
auto output_offsets_buffer = cmd.get_device().create_buffer(buf_info, decode_offsets.data());
|
||||
|
||||
cmd.set_storage_buffer(0, 6, *output_offsets_buffer);
|
||||
|
||||
uint32_t wg_x = (view.format_header->meshlet_count + 7) / 8;
|
||||
|
||||
struct Push
|
||||
{
|
||||
uint32_t primitive_offset;
|
||||
uint32_t vertex_offset;
|
||||
uint32_t meshlet_count;
|
||||
uint32_t wg_offset;
|
||||
} push = {};
|
||||
|
||||
push.primitive_offset = info.push.primitive_offset;
|
||||
push.vertex_offset = info.push.vertex_offset;
|
||||
push.meshlet_count = view.format_header->meshlet_count;
|
||||
push.wg_offset = 0;
|
||||
|
||||
const uint32_t max_wgx = cmd.get_device().get_gpu_properties().limits.maxComputeWorkGroupCount[0];
|
||||
for (uint32_t i = 0; i < wg_x; i += max_wgx)
|
||||
{
|
||||
uint32_t to_dispatch = std::min<uint32_t>(wg_x - i, max_wgx);
|
||||
push.wg_offset = i;
|
||||
cmd.push_constants(&push, 0, sizeof(push));
|
||||
cmd.dispatch(to_dispatch, 1, 1);
|
||||
}
|
||||
|
||||
cmd.set_specialization_constant_mask(0);
|
||||
cmd.enable_subgroup_size_control(false);
|
||||
|
||||
if (info.indirect)
|
||||
{
|
||||
upload_indirect_buffer(cmd, *info.indirect, info.indirect_offset, view, info.runtime_style,
|
||||
info.push.primitive_offset, info.push.vertex_offset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Copyright (c) 2017-2026 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>
|
||||
|
||||
namespace Granite
|
||||
{
|
||||
class FileMapping;
|
||||
}
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class CommandBuffer;
|
||||
class Buffer;
|
||||
}
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
// MESHLET1 format.
|
||||
namespace Meshlet
|
||||
{
|
||||
static constexpr unsigned MaxStreams = 8;
|
||||
static constexpr unsigned MaxElements = 32;
|
||||
static constexpr unsigned ChunkFactor = 256 / MaxElements;
|
||||
|
||||
struct Stream
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32_t base_value[2];
|
||||
struct { uint32_t prim_count; uint32_t vert_count; } counts;
|
||||
} u;
|
||||
uint32_t bits;
|
||||
uint32_t offset_in_words;
|
||||
};
|
||||
static_assert(sizeof(Stream) == 16, "Unexpected Stream size.");
|
||||
|
||||
struct RuntimeHeaderEncoded
|
||||
{
|
||||
uint32_t stream_offset;
|
||||
};
|
||||
|
||||
struct RuntimeHeaderDecoded
|
||||
{
|
||||
uint32_t primitive_offset;
|
||||
uint32_t vertex_offset;
|
||||
uint32_t primitive_count;
|
||||
uint32_t vertex_count;
|
||||
};
|
||||
|
||||
struct RuntimeHeaderDecodedMDI
|
||||
{
|
||||
uint32_t indexCount;
|
||||
uint32_t firstIndex;
|
||||
int32_t vertexOffset;
|
||||
};
|
||||
|
||||
struct Bound
|
||||
{
|
||||
float center[3];
|
||||
float radius;
|
||||
float cone_axis_cutoff[4];
|
||||
};
|
||||
|
||||
enum class StreamType
|
||||
{
|
||||
Primitive = 0, // RGB8_UINT (fixed 5-bit encoding, fixed base value of 0)
|
||||
Position, // RGB16_SINT * 2^aux
|
||||
NormalTangentOct8, // Octahedron encoding in RG8, BA8 for tangent. Following uvec4 encodes 1-bit sign.
|
||||
UV, // (0.5 * (R16G16_SINT * 2^aux) + 0.5
|
||||
BoneIndices, // RGBA8_UINT
|
||||
BoneWeights, // RGBA8_UNORM
|
||||
};
|
||||
|
||||
enum class MeshStyle : uint32_t
|
||||
{
|
||||
Wireframe = 0, // Primitive + Position
|
||||
Textured, // Untextured + TangentOct8 + UV
|
||||
Skinned // Textured + Bone*
|
||||
};
|
||||
|
||||
struct FormatHeader
|
||||
{
|
||||
MeshStyle style;
|
||||
uint32_t stream_count;
|
||||
uint32_t meshlet_count;
|
||||
uint32_t payload_size_words;
|
||||
};
|
||||
|
||||
using PayloadWord = uint32_t;
|
||||
|
||||
struct MeshView
|
||||
{
|
||||
const FormatHeader *format_header;
|
||||
const Bound *bounds;
|
||||
const Bound *bounds_256;
|
||||
const Stream *streams;
|
||||
const PayloadWord *payload;
|
||||
uint32_t total_primitives;
|
||||
uint32_t total_vertices;
|
||||
uint32_t num_bounds;
|
||||
uint32_t num_bounds_256;
|
||||
};
|
||||
|
||||
static const char magic[8] = { 'M', 'E', 'S', 'H', 'L', 'E', 'T', '4' };
|
||||
|
||||
MeshView create_mesh_view(const Granite::FileMapping &mapping);
|
||||
|
||||
enum DecodeModeFlagBits : uint32_t
|
||||
{
|
||||
DECODE_MODE_UNROLLED_MESH = 1 << 0,
|
||||
DECODE_MODE_INDEX_16 = 1 << 1,
|
||||
};
|
||||
using DecodeModeFlags = uint32_t;
|
||||
|
||||
enum class RuntimeStyle
|
||||
{
|
||||
MDI,
|
||||
Meshlet
|
||||
};
|
||||
|
||||
struct DecodeInfo
|
||||
{
|
||||
const Vulkan::Buffer *ibo, *streams[3], *indirect, *payload;
|
||||
DecodeModeFlags flags;
|
||||
MeshStyle target_style;
|
||||
RuntimeStyle runtime_style;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t primitive_offset;
|
||||
uint32_t vertex_offset;
|
||||
} push;
|
||||
uint32_t indirect_offset;
|
||||
};
|
||||
|
||||
bool decode_mesh(Vulkan::CommandBuffer &cmd, const DecodeInfo &decode_info, const MeshView &view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,680 @@
|
||||
/* Copyright (c) 2017-2026 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_cache.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
PipelineCache::Binary::Binary(const VkPipelineBinaryKeyKHR &key_, const void *payload_, size_t payload_size_)
|
||||
: device(nullptr), key(key_), payload(payload_), payload_size(payload_size_)
|
||||
{
|
||||
}
|
||||
|
||||
PipelineCache::Binary::Binary(Vulkan::Device &device_, const VkPipelineBinaryKeyKHR &key_, VkPipelineBinaryKHR binary_)
|
||||
: device(&device_), key(key_), binary(binary_)
|
||||
{
|
||||
VkPipelineBinaryDataInfoKHR data_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_DATA_INFO_KHR };
|
||||
VkPipelineBinaryKeyKHR dummy_key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
data_info.pipelineBinary = binary;
|
||||
device->get_device_table().vkGetPipelineBinaryDataKHR(device->get_device(), &data_info, &dummy_key, &payload_size, nullptr);
|
||||
}
|
||||
|
||||
PipelineCache::Binary::~Binary()
|
||||
{
|
||||
if (device)
|
||||
device->get_device_table().vkDestroyPipelineBinaryKHR(device->get_device(), binary, nullptr);
|
||||
}
|
||||
|
||||
PipelineCache::PipelineCache(Device *device_)
|
||||
: device(*device_), new_entries(false)
|
||||
{
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache()
|
||||
{
|
||||
}
|
||||
|
||||
Util::Hash PipelineCache::get_create_info_key(const void *create_info) const
|
||||
{
|
||||
VkPipelineCreateInfoKHR key_create_info = { VK_STRUCTURE_TYPE_PIPELINE_CREATE_INFO_KHR };
|
||||
VkPipelineBinaryKeyKHR global_key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
key_create_info.pNext = const_cast<void *>(create_info);
|
||||
if (device.get_device_table().vkGetPipelineKeyKHR(device.get_device(), &key_create_info, &global_key) != VK_SUCCESS)
|
||||
return false;
|
||||
|
||||
Util::Hasher h;
|
||||
h.data(global_key.key, global_key.keySize);
|
||||
return h.get();
|
||||
}
|
||||
|
||||
bool PipelineCache::place_binary(VkPipelineBinaryKHR binary, Util::Hash *hash)
|
||||
{
|
||||
VkPipelineBinaryDataInfoKHR data_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_DATA_INFO_KHR };
|
||||
VkPipelineBinaryKeyKHR key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
data_info.pipelineBinary = binary;
|
||||
size_t data_size = 0;
|
||||
|
||||
if (device.get_device_table().vkGetPipelineBinaryDataKHR(
|
||||
device.get_device(), &data_info, &key, &data_size, nullptr) != VK_SUCCESS)
|
||||
{
|
||||
LOGE("Failed to get pipeline binary key.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
VK_ASSERT(key.keySize);
|
||||
|
||||
Util::Hasher h;
|
||||
h.data(key.key, key.keySize);
|
||||
*hash = h.get();
|
||||
|
||||
static constexpr uint32_t AllZero[VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR] = {};
|
||||
if (memcmp(AllZero, key.key, VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR) == 0)
|
||||
{
|
||||
LOGW("Driver seems broken? Key is all zeros ...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (binaries.find(h.get()))
|
||||
device.get_device_table().vkDestroyPipelineBinaryKHR(device.get_device(), binary, nullptr);
|
||||
else
|
||||
binaries.emplace_yield(h.get(), device, key, binary);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PipelineCache::place_pipeline(Util::Hash hash, VkPipeline pipeline)
|
||||
{
|
||||
const auto release_binaries = [&]()
|
||||
{
|
||||
VkReleaseCapturedPipelineDataInfoKHR release_info = { VK_STRUCTURE_TYPE_RELEASE_CAPTURED_PIPELINE_DATA_INFO_KHR };
|
||||
release_info.pipeline = pipeline;
|
||||
device.get_device_table().vkReleaseCapturedPipelineDataKHR(device.get_device(), &release_info, nullptr);
|
||||
};
|
||||
|
||||
if (binary_mapping.find(hash) != nullptr)
|
||||
{
|
||||
release_binaries();
|
||||
return;
|
||||
}
|
||||
|
||||
VkPipelineBinaryCreateInfoKHR create_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_CREATE_INFO_KHR };
|
||||
create_info.pipeline = pipeline;
|
||||
VkPipelineBinaryHandlesInfoKHR handles_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_HANDLES_INFO_KHR };
|
||||
|
||||
if (device.get_device_table().vkCreatePipelineBinariesKHR(
|
||||
device.get_device(), &create_info, nullptr, &handles_info) != VK_SUCCESS ||
|
||||
handles_info.pipelineBinaryCount == 0)
|
||||
{
|
||||
LOGE("Failed to query pipeline binaries from pipeline.\n");
|
||||
release_binaries();
|
||||
return;
|
||||
}
|
||||
|
||||
Util::SmallVector<VkPipelineBinaryKHR> out_binaries(handles_info.pipelineBinaryCount);
|
||||
handles_info.pPipelineBinaries = out_binaries.data();
|
||||
|
||||
if (device.get_device_table().vkCreatePipelineBinariesKHR(
|
||||
device.get_device(), &create_info, nullptr, &handles_info) != VK_SUCCESS)
|
||||
{
|
||||
LOGE("Failed to query pipeline binaries from pipeline.\n");
|
||||
release_binaries();
|
||||
return;
|
||||
}
|
||||
|
||||
release_binaries();
|
||||
|
||||
Util::SmallVector<Util::Hash> keys;
|
||||
keys.resize(out_binaries.size());
|
||||
auto *pkeys = keys.data();
|
||||
|
||||
for (auto &binary : out_binaries)
|
||||
if (!place_binary(binary, pkeys++))
|
||||
return;
|
||||
|
||||
binary_mapping.emplace_yield(hash, std::move(keys));
|
||||
new_entries.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool PipelineCache::find_pipeline_binaries_from_internal_cache(const void *pso_create_info,
|
||||
Util::SmallVector<VkPipelineBinaryKHR> &out_binaries,
|
||||
Util::SmallVector<bool> &out_binaries_owned)
|
||||
{
|
||||
out_binaries.clear();
|
||||
out_binaries_owned.clear();
|
||||
|
||||
VkPipelineBinaryCreateInfoKHR create_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_CREATE_INFO_KHR };
|
||||
VkPipelineCreateInfoKHR pipeline_create_info = { VK_STRUCTURE_TYPE_PIPELINE_CREATE_INFO_KHR, const_cast<void *>(pso_create_info) };
|
||||
create_info.pPipelineCreateInfo = &pipeline_create_info;
|
||||
|
||||
out_binaries.resize(32);
|
||||
|
||||
// Ideally we don't query twice, just assume we're not going to receive more than 32 binaries in one go.
|
||||
// For graphics and compute, this is surely fine ... :')
|
||||
VkPipelineBinaryHandlesInfoKHR handles_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_HANDLES_INFO_KHR };
|
||||
handles_info.pPipelineBinaries = out_binaries.data();
|
||||
handles_info.pipelineBinaryCount = 32;
|
||||
|
||||
auto result = device.get_device_table().vkCreatePipelineBinariesKHR(
|
||||
device.get_device(), &create_info, nullptr, &handles_info);
|
||||
|
||||
out_binaries.resize(handles_info.pipelineBinaryCount);
|
||||
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
for (auto &b : out_binaries)
|
||||
device.get_device_table().vkDestroyPipelineBinaryKHR(device.get_device(), b, nullptr);
|
||||
out_binaries.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < handles_info.pipelineBinaryCount; i++)
|
||||
out_binaries_owned.push_back(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipelineCache::find_pipeline_binaries(Util::Hash pso_hash,
|
||||
Util::SmallVector<VkPipelineBinaryKHR> &out_binaries,
|
||||
Util::SmallVector<bool> &out_binaries_owned)
|
||||
{
|
||||
auto *mapped = binary_mapping.find(pso_hash);
|
||||
if (!mapped)
|
||||
return false;
|
||||
|
||||
out_binaries.clear();
|
||||
out_binaries_owned.clear();
|
||||
|
||||
for (auto &hash : mapped->hashes)
|
||||
{
|
||||
auto *existing_binary = binaries.find(hash);
|
||||
|
||||
if (!existing_binary)
|
||||
{
|
||||
for (auto &binary: out_binaries)
|
||||
device.get_device_table().vkDestroyPipelineBinaryKHR(device.get_device(), binary, nullptr);
|
||||
out_binaries.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
VkPipelineBinaryKHR binary = VK_NULL_HANDLE;
|
||||
|
||||
if (existing_binary->binary)
|
||||
{
|
||||
binary = existing_binary->binary;
|
||||
out_binaries_owned.push_back(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
VkPipelineBinaryCreateInfoKHR create_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_CREATE_INFO_KHR };
|
||||
VkPipelineBinaryKeysAndDataKHR keys_and_data_info = {};
|
||||
VkPipelineBinaryDataKHR binary_data = {};
|
||||
keys_and_data_info.binaryCount = 1;
|
||||
keys_and_data_info.pPipelineBinaryKeys = &existing_binary->key;
|
||||
VK_ASSERT(existing_binary->key.keySize);
|
||||
keys_and_data_info.pPipelineBinaryData = &binary_data;
|
||||
create_info.pKeysAndDataInfo = &keys_and_data_info;
|
||||
binary_data.pData = const_cast<void *>(existing_binary->payload);
|
||||
binary_data.dataSize = existing_binary->payload_size;
|
||||
|
||||
VkPipelineBinaryHandlesInfoKHR handles_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_HANDLES_INFO_KHR };
|
||||
handles_info.pPipelineBinaries = &binary;
|
||||
handles_info.pipelineBinaryCount = 1;
|
||||
|
||||
if (device.get_device_table().vkCreatePipelineBinariesKHR(
|
||||
device.get_device(), &create_info, nullptr, &handles_info) != VK_SUCCESS ||
|
||||
handles_info.pipelineBinaryCount != 1 ||
|
||||
handles_info.pPipelineBinaries[0] == VK_NULL_HANDLE)
|
||||
{
|
||||
for (auto &b : out_binaries)
|
||||
device.get_device_table().vkDestroyPipelineBinaryKHR(device.get_device(), b, nullptr);
|
||||
out_binaries.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
out_binaries_owned.push_back(existing_binary->binary == VK_NULL_HANDLE);
|
||||
out_binaries.push_back(binary);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipelineCache::init_from_payload(const void *payload, size_t size, bool persistent_mapping)
|
||||
{
|
||||
if (!size)
|
||||
return true;
|
||||
|
||||
if (!persistent_mapping)
|
||||
{
|
||||
payload_holder.reset(new uint8_t[size]);
|
||||
memcpy(payload_holder.get(), payload, size);
|
||||
payload = payload_holder.get();
|
||||
}
|
||||
|
||||
if (!parse(payload, size))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr char CacheUUID[VK_UUID_SIZE] = "GraniteBinary1";
|
||||
|
||||
bool PipelineCache::parse(const void *payload_, size_t size)
|
||||
{
|
||||
if (!device.get_device_features().pipeline_binary_features.pipelineBinaries)
|
||||
return false;
|
||||
|
||||
constexpr size_t minimum_size = VK_UUID_SIZE + sizeof(uint32_t) +
|
||||
VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR + sizeof(uint32_t);
|
||||
if (size < minimum_size)
|
||||
return false;
|
||||
|
||||
auto *payload = static_cast<const uint8_t *>(payload_);
|
||||
|
||||
if (memcmp(payload, CacheUUID, sizeof(CacheUUID)) != 0)
|
||||
return false;
|
||||
payload += VK_UUID_SIZE;
|
||||
|
||||
VkPipelineBinaryKeyKHR key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
device.get_device_table().vkGetPipelineKeyKHR(device.get_device(), nullptr, &key);
|
||||
|
||||
if (memcmp(payload, &key.keySize, sizeof(uint32_t)) != 0)
|
||||
{
|
||||
LOGW("Pipeline binary global key changed, resetting the cache ...\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
payload += sizeof(uint32_t);
|
||||
|
||||
if (memcmp(payload, key.key, key.keySize) != 0)
|
||||
{
|
||||
LOGW("Pipeline binary global key changed, resetting the cache ...\n");
|
||||
return true;
|
||||
}
|
||||
payload += VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR;
|
||||
|
||||
uint32_t num_pipelines = *reinterpret_cast<const uint32_t *>(payload);
|
||||
payload += sizeof(uint32_t);
|
||||
auto *payload64 = reinterpret_cast<const uint64_t *>(payload);
|
||||
size -= minimum_size;
|
||||
|
||||
const auto read_u64 = [&]() -> uint64_t {
|
||||
if (size >= sizeof(uint64_t))
|
||||
{
|
||||
auto data = *payload64++;
|
||||
size -= sizeof(uint64_t);
|
||||
return data;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < num_pipelines; i++)
|
||||
{
|
||||
Util::SmallVector<Util::Hash> hashes;
|
||||
auto hash = read_u64();
|
||||
auto num_hashes = uint32_t(read_u64());
|
||||
for (uint32_t j = 0; j < num_hashes; j++)
|
||||
hashes.push_back(read_u64());
|
||||
binary_mapping.emplace_yield(hash, std::move(hashes));
|
||||
}
|
||||
|
||||
auto num_binaries = uint32_t(read_u64());
|
||||
for (uint32_t i = 0; i < num_binaries; i++)
|
||||
{
|
||||
auto hash = read_u64();
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint32_t size;
|
||||
uint32_t key_size;
|
||||
};
|
||||
uint64_t word;
|
||||
} u;
|
||||
u.word = read_u64();
|
||||
|
||||
if (size < VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR)
|
||||
return false;
|
||||
|
||||
key.keySize = u.key_size;
|
||||
memcpy(key.key, payload64, sizeof(key.key));
|
||||
payload64 += VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR / sizeof(uint64_t);
|
||||
size -= VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR;
|
||||
|
||||
auto padded_size = (u.size + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1);
|
||||
|
||||
if (size < padded_size)
|
||||
return false;
|
||||
|
||||
binaries.emplace_yield(hash, key, payload64, u.size);
|
||||
payload64 += padded_size / sizeof(uint64_t);
|
||||
size -= padded_size;
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
LOGI("Successfully parsed %u pipelines and %u binary blobs.\n", num_pipelines, num_binaries);
|
||||
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
bool PipelineCache::has_new_binary_entries() const
|
||||
{
|
||||
return new_entries.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
size_t PipelineCache::get_serialized_size() const
|
||||
{
|
||||
// Granite's magic UUID.
|
||||
size_t size = VK_UUID_SIZE;
|
||||
|
||||
// Driver's global key.
|
||||
size += sizeof(uint32_t);
|
||||
size += VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR;
|
||||
|
||||
// Pipeline number count.
|
||||
size += sizeof(uint32_t);
|
||||
|
||||
for (auto &mapping : binary_mapping.get_thread_unsafe())
|
||||
{
|
||||
// Count + Keys per pipeline.
|
||||
size += sizeof(Util::Hash) + sizeof(uint64_t) + mapping.hashes.size() * sizeof(Util::Hash);
|
||||
}
|
||||
|
||||
// Binary count.
|
||||
size += sizeof(uint64_t);
|
||||
|
||||
for (auto &binary : binaries.get_thread_unsafe())
|
||||
{
|
||||
size += sizeof(Util::Hash); // Hash
|
||||
size += sizeof(uint32_t); // Size
|
||||
size += sizeof(uint32_t); // Key size
|
||||
size += VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR;
|
||||
size += (binary.payload_size + 7) & ~size_t(7); // Padded payload
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool PipelineCache::serialize(void *data_, size_t size) const
|
||||
{
|
||||
if (size < get_serialized_size())
|
||||
return false;
|
||||
|
||||
auto *data = static_cast<uint8_t *>(data_);
|
||||
memcpy(data, CacheUUID, sizeof(CacheUUID));
|
||||
data += VK_UUID_SIZE;
|
||||
|
||||
VkPipelineBinaryKeyKHR key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
device.get_device_table().vkGetPipelineKeyKHR(device.get_device(), nullptr, &key);
|
||||
memcpy(data, &key.keySize, sizeof(key.keySize));
|
||||
data += sizeof(uint32_t);
|
||||
memcpy(data, key.key, sizeof(key.key));
|
||||
data += sizeof(key.key);
|
||||
|
||||
uint32_t pipeline_count = 0;
|
||||
for (auto &mapping : binary_mapping.get_thread_unsafe())
|
||||
{
|
||||
(void)mapping;
|
||||
pipeline_count++;
|
||||
}
|
||||
|
||||
memcpy(data, &pipeline_count, sizeof(pipeline_count));
|
||||
data += sizeof(uint32_t);
|
||||
|
||||
auto *data64 = reinterpret_cast<uint64_t *>(data);
|
||||
|
||||
for (auto &mapping : binary_mapping.get_thread_unsafe())
|
||||
{
|
||||
*data64++ = mapping.get_hash();
|
||||
*data64++ = mapping.hashes.size();
|
||||
for (auto &hash : mapping.hashes)
|
||||
*data64++ = hash;
|
||||
}
|
||||
|
||||
uint32_t binary_count = 0;
|
||||
for (auto &mapping : binaries.get_thread_unsafe())
|
||||
{
|
||||
(void)mapping;
|
||||
binary_count++;
|
||||
}
|
||||
|
||||
*data64++ = binary_count;
|
||||
for (auto &mapping : binaries.get_thread_unsafe())
|
||||
{
|
||||
*data64++ = mapping.get_hash();
|
||||
const uint32_t words[] = { uint32_t(mapping.payload_size), mapping.key.keySize };
|
||||
memcpy(data64, words, sizeof(words));
|
||||
data64++;
|
||||
memcpy(data64, mapping.key.key, sizeof(mapping.key.key));
|
||||
data64 += VK_MAX_PIPELINE_BINARY_KEY_SIZE_KHR / sizeof(uint64_t);
|
||||
|
||||
VK_ASSERT(mapping.binary || mapping.payload);
|
||||
|
||||
if (mapping.binary)
|
||||
{
|
||||
// TODO: Ignore compressed property for now.
|
||||
VkPipelineBinaryDataInfoKHR data_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_DATA_INFO_KHR };
|
||||
VkPipelineBinaryKeyKHR dummy_key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
data_info.pipelineBinary = mapping.binary;
|
||||
size_t payload_size = mapping.payload_size;
|
||||
device.get_device_table().vkGetPipelineBinaryDataKHR(device.get_device(), &data_info, &dummy_key,
|
||||
&payload_size, data64);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(data64, mapping.payload, mapping.payload_size);
|
||||
}
|
||||
|
||||
data64 += (mapping.payload_size + sizeof(uint64_t) - 1) / sizeof(uint64_t);
|
||||
}
|
||||
|
||||
LOGI("Serialized %u pipelines and %u binary blobs.\n", pipeline_count, binary_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline const T *find_pnext(VkStructureType type, const void *pNext)
|
||||
{
|
||||
while (pNext != nullptr)
|
||||
{
|
||||
auto *sin = static_cast<const VkBaseInStructure *>(pNext);
|
||||
if (sin->sType == type)
|
||||
return static_cast<const T*>(pNext);
|
||||
|
||||
pNext = sin->pNext;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VkPipeline PipelineCache::create_pipeline_and_place(Util::Hash pso_key, void *plain_info)
|
||||
{
|
||||
auto *graphics_info = static_cast<VkGraphicsPipelineCreateInfo *>(plain_info);
|
||||
auto *compute_info = static_cast<VkComputePipelineCreateInfo *>(plain_info);
|
||||
if (graphics_info && graphics_info->sType != VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO)
|
||||
graphics_info = nullptr;
|
||||
if (compute_info && compute_info->sType != VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO)
|
||||
compute_info = nullptr;
|
||||
|
||||
VkPipelineCreateFlags2CreateInfoKHR flags2 = { VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO_KHR };
|
||||
VkPipeline pipe = VK_NULL_HANDLE;
|
||||
|
||||
if (!device.get_device_features().pipeline_binary_properties.pipelineBinaryPrefersInternalCache)
|
||||
{
|
||||
auto *existing_flags2 = find_pnext<VkPipelineCreateFlags2CreateInfoKHR>(
|
||||
VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO_KHR, plain_info);
|
||||
|
||||
if (existing_flags2)
|
||||
{
|
||||
const_cast<VkPipelineCreateFlags2CreateInfoKHR *>(existing_flags2)->flags |=
|
||||
VK_PIPELINE_CREATE_2_CAPTURE_DATA_BIT_KHR;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags2.flags = VK_PIPELINE_CREATE_2_CAPTURE_DATA_BIT_KHR;
|
||||
|
||||
if (graphics_info)
|
||||
{
|
||||
flags2.flags |= graphics_info->flags;
|
||||
flags2.pNext = graphics_info->pNext;
|
||||
graphics_info->pNext = &flags2;
|
||||
}
|
||||
else if (compute_info)
|
||||
{
|
||||
flags2.flags |= compute_info->flags;
|
||||
flags2.pNext = compute_info->pNext;
|
||||
compute_info->pNext = &flags2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((compute_info && device.get_device_table().vkCreateComputePipelines(
|
||||
device.get_device(), VK_NULL_HANDLE, 1, compute_info, nullptr, &pipe) != VK_SUCCESS) ||
|
||||
(graphics_info && device.get_device_table().vkCreateGraphicsPipelines(
|
||||
device.get_device(), VK_NULL_HANDLE, 1, graphics_info, nullptr, &pipe) != VK_SUCCESS))
|
||||
{
|
||||
LOGE("Failed to create pipeline from binaries.\n");
|
||||
pipe = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (!device.get_device_features().pipeline_binary_properties.pipelineBinaryPrefersInternalCache &&
|
||||
pipe != VK_NULL_HANDLE)
|
||||
{
|
||||
place_pipeline(pso_key, pipe);
|
||||
}
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
VkPipeline
|
||||
PipelineCache::create_pipeline_from_binaries(
|
||||
void *plain_info, const VkPipelineBinaryKHR *found_binaries,
|
||||
const bool *binaries_owned, size_t binary_count)
|
||||
{
|
||||
auto *graphics_info = static_cast<VkGraphicsPipelineCreateInfo *>(plain_info);
|
||||
auto *compute_info = static_cast<VkComputePipelineCreateInfo *>(plain_info);
|
||||
if (graphics_info && graphics_info->sType != VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO)
|
||||
graphics_info = nullptr;
|
||||
if (compute_info && compute_info->sType != VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO)
|
||||
compute_info = nullptr;
|
||||
|
||||
// Cache hit :3
|
||||
VkPipelineBinaryInfoKHR binary_info = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_INFO_KHR };
|
||||
binary_info.pPipelineBinaries = found_binaries;
|
||||
binary_info.binaryCount = binary_count;
|
||||
|
||||
constexpr VkPipelineCreateFlags invalid_flags =
|
||||
VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT |
|
||||
VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT;
|
||||
|
||||
if (compute_info)
|
||||
{
|
||||
compute_info->stage.module = VK_NULL_HANDLE;
|
||||
binary_info.pNext = compute_info->pNext;
|
||||
compute_info->pNext = &binary_info;
|
||||
compute_info->flags &= ~invalid_flags;
|
||||
}
|
||||
else if (graphics_info)
|
||||
{
|
||||
for (uint32_t i = 0; i < graphics_info->stageCount; i++)
|
||||
const_cast<VkPipelineShaderStageCreateInfo &>(graphics_info->pStages[i]).module = VK_NULL_HANDLE;
|
||||
binary_info.pNext = graphics_info->pNext;
|
||||
graphics_info->pNext = &binary_info;
|
||||
graphics_info->flags &= ~invalid_flags;
|
||||
}
|
||||
|
||||
VkPipeline pipe = VK_NULL_HANDLE;
|
||||
|
||||
if ((compute_info && device.get_device_table().vkCreateComputePipelines(
|
||||
device.get_device(), VK_NULL_HANDLE, 1, compute_info, nullptr, &pipe) != VK_SUCCESS) ||
|
||||
(graphics_info && device.get_device_table().vkCreateGraphicsPipelines(
|
||||
device.get_device(), VK_NULL_HANDLE, 1, graphics_info, nullptr, &pipe) != VK_SUCCESS))
|
||||
{
|
||||
LOGE("Failed to create pipeline from binaries.\n");
|
||||
pipe = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < binary_count; i++)
|
||||
if (binaries_owned[i])
|
||||
device.get_device_table().vkDestroyPipelineBinaryKHR(device.get_device(), found_binaries[i], nullptr);
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
VkResult PipelineCache::create_pipeline(void *plain_info, VkPipelineCache cache, VkPipeline *pipe)
|
||||
{
|
||||
*pipe = VK_NULL_HANDLE;
|
||||
auto *graphics_info = static_cast<VkGraphicsPipelineCreateInfo *>(plain_info);
|
||||
auto *compute_info = static_cast<VkComputePipelineCreateInfo *>(plain_info);
|
||||
if (graphics_info && graphics_info->sType != VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO)
|
||||
graphics_info = nullptr;
|
||||
if (compute_info && compute_info->sType != VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO)
|
||||
compute_info = nullptr;
|
||||
|
||||
if (!device.get_device_features().pipeline_binary_features.pipelineBinaries)
|
||||
{
|
||||
if (compute_info)
|
||||
{
|
||||
return device.get_device_table().vkCreateComputePipelines(
|
||||
device.get_device(), cache, 1, compute_info, nullptr, pipe);
|
||||
}
|
||||
else if (graphics_info)
|
||||
{
|
||||
return device.get_device_table().vkCreateGraphicsPipelines(
|
||||
device.get_device(), cache, 1, graphics_info, nullptr, pipe);
|
||||
}
|
||||
else
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
auto pso_key = get_create_info_key(plain_info);
|
||||
Util::SmallVector<VkPipelineBinaryKHR> pipeline_binaries;
|
||||
Util::SmallVector<bool> pipeline_binaries_owned;
|
||||
|
||||
if (find_pipeline_binaries(pso_key, pipeline_binaries, pipeline_binaries_owned))
|
||||
{
|
||||
*pipe = create_pipeline_from_binaries(plain_info, pipeline_binaries.data(), pipeline_binaries_owned.data(),
|
||||
pipeline_binaries.size());
|
||||
return *pipe ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
|
||||
if (device.get_device_features().pipeline_binary_properties.pipelineBinaryInternalCache &&
|
||||
!device.get_device_features().pipeline_binary_internal_cache_control.disableInternalCache &&
|
||||
find_pipeline_binaries_from_internal_cache(plain_info, pipeline_binaries, pipeline_binaries_owned))
|
||||
{
|
||||
*pipe = create_pipeline_from_binaries(plain_info, pipeline_binaries.data(), pipeline_binaries_owned.data(),
|
||||
pipeline_binaries.size());
|
||||
return *pipe ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
|
||||
if (graphics_info && (graphics_info->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT) != 0)
|
||||
return VK_PIPELINE_COMPILE_REQUIRED;
|
||||
if (compute_info && (compute_info->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT) != 0)
|
||||
return VK_PIPELINE_COMPILE_REQUIRED;
|
||||
|
||||
*pipe = create_pipeline_and_place(pso_key, plain_info);
|
||||
return *pipe ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Copyright (c) 2017-2026 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 <stddef.h>
|
||||
#include "vulkan_headers.hpp"
|
||||
#include "small_vector.hpp"
|
||||
#include "intrusive_hash_map.hpp"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class Device;
|
||||
class PipelineCache
|
||||
{
|
||||
public:
|
||||
explicit PipelineCache(Device *device);
|
||||
~PipelineCache();
|
||||
|
||||
bool init_from_payload(const void *payload, size_t size, bool persistent_mapping);
|
||||
bool has_new_binary_entries() const;
|
||||
size_t get_serialized_size() const;
|
||||
bool serialize(void *data, size_t size) const;
|
||||
|
||||
VkResult create_pipeline(void *info, VkPipelineCache cache, VkPipeline *pipe);
|
||||
|
||||
private:
|
||||
Device &device;
|
||||
std::unique_ptr<uint8_t []> payload_holder;
|
||||
|
||||
struct PipelineBinaryMapping : Util::IntrusiveHashMapEnabled<PipelineBinaryMapping>
|
||||
{
|
||||
explicit PipelineBinaryMapping(Util::SmallVector<Util::Hash> hashes_)
|
||||
: hashes(std::move(hashes_)) {}
|
||||
Util::SmallVector<Util::Hash> hashes;
|
||||
};
|
||||
Util::ThreadSafeIntrusiveHashMap<PipelineBinaryMapping> binary_mapping;
|
||||
|
||||
struct Binary : Util::IntrusiveHashMapEnabled<Binary>
|
||||
{
|
||||
Binary(Device &device, const VkPipelineBinaryKeyKHR &key, VkPipelineBinaryKHR binary);
|
||||
Binary(const VkPipelineBinaryKeyKHR &key, const void *payload, size_t payload_size);
|
||||
~Binary();
|
||||
|
||||
Device *device;
|
||||
VkPipelineBinaryKeyKHR key = { VK_STRUCTURE_TYPE_PIPELINE_BINARY_KEY_KHR };
|
||||
VkPipelineBinaryKHR binary = VK_NULL_HANDLE;
|
||||
const void *payload = nullptr;
|
||||
size_t payload_size = 0;
|
||||
};
|
||||
Util::ThreadSafeIntrusiveHashMap<Binary> binaries;
|
||||
|
||||
bool place_binary(VkPipelineBinaryKHR binary, Util::Hash *hash);
|
||||
bool parse(const void *payload, size_t size);
|
||||
std::atomic_bool new_entries;
|
||||
|
||||
Util::Hash get_create_info_key(const void *create_info) const;
|
||||
bool find_pipeline_binaries(Util::Hash hash,
|
||||
Util::SmallVector<VkPipelineBinaryKHR> &binaries,
|
||||
Util::SmallVector<bool> &binaries_owned);
|
||||
|
||||
bool find_pipeline_binaries_from_internal_cache(const void *create_info,
|
||||
Util::SmallVector<VkPipelineBinaryKHR> &binaries,
|
||||
Util::SmallVector<bool> &binaries_owned);
|
||||
|
||||
void place_pipeline(Util::Hash hash, VkPipeline pipeline);
|
||||
VkPipeline create_pipeline_from_binaries(
|
||||
void *info, const VkPipelineBinaryKHR *binaries, const bool *binaries_owned, size_t binary_count);
|
||||
VkPipeline create_pipeline_and_place(Util::Hash pso_key, void *info);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Copyright (c) 2017-2026 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-2026 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>;
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
add_granite_internal_lib(granite-vulkan-post-mortem
|
||||
post_mortem.cpp post_mortem.hpp)
|
||||
|
||||
target_include_directories(granite-vulkan-post-mortem PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(granite-vulkan-post-mortem PRIVATE granite-util)
|
||||
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
|
||||
set(AFTERMATH_ARCH x64)
|
||||
else()
|
||||
set(AFTERMATH_ARCH x86)
|
||||
endif()
|
||||
|
||||
find_library(GFSDK_LIBRARY GFSDK_Aftermath_Lib.x64 HINTS ${AFTERMATH_SDK_PATH}/lib/${AFTERMATH_ARCH})
|
||||
|
||||
if (GFSDK_LIBRARY)
|
||||
target_sources(granite-vulkan-post-mortem PRIVATE
|
||||
NsightAftermathGpuCrashTracker.cpp NsightAftermathGpuCrashTracker.h
|
||||
NsightAftermathHelpers.h)
|
||||
target_compile_definitions(granite-vulkan-post-mortem PRIVATE HAVE_AFTERMATH_SDK)
|
||||
target_link_libraries(granite-vulkan-post-mortem PRIVATE ${GFSDK_LIBRARY} granite-volk-headers)
|
||||
target_include_directories(granite-vulkan-post-mortem PRIVATE ${AFTERMATH_SDK_PATH}/include)
|
||||
message("Found Aftermath SDK.")
|
||||
else()
|
||||
message("Did not find Aftermath SDK in AFTERMATH_SDK_PATH=${AFTERMATH_SDK_PATH}.")
|
||||
endif()
|
||||
Vendored
+422
@@ -0,0 +1,422 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// 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 <fstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include "logging.hpp"
|
||||
|
||||
#include "NsightAftermathGpuCrashTracker.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
// Modified from the official sample to fit Granite better.
|
||||
|
||||
//*********************************************************
|
||||
// GpuCrashTracker implementation
|
||||
//*********************************************************
|
||||
|
||||
GpuCrashTracker::GpuCrashTracker(const MarkerMap& markerMap)
|
||||
: m_initialized(false)
|
||||
, m_mutex()
|
||||
, m_shaderDebugInfo()
|
||||
, m_markerMap(markerMap)
|
||||
{
|
||||
}
|
||||
|
||||
GpuCrashTracker::~GpuCrashTracker()
|
||||
{
|
||||
// If initialized, disable GPU crash dumps
|
||||
if (m_initialized)
|
||||
{
|
||||
GFSDK_Aftermath_DisableGpuCrashDumps();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the GPU Crash Dump Tracker
|
||||
void GpuCrashTracker::Initialize()
|
||||
{
|
||||
// Enable GPU crash dumps and set up the callbacks for crash dump notifications,
|
||||
// shader debug information notifications, and providing additional crash
|
||||
// dump description data.Only the crash dump callback is mandatory. The other two
|
||||
// callbacks are optional and can be omitted, by passing nullptr, if the corresponding
|
||||
// functionality is not used.
|
||||
// The DeferDebugInfoCallbacks flag enables caching of shader debug information data
|
||||
// in memory. If the flag is set, ShaderDebugInfoCallback will be called only
|
||||
// in the event of a crash, right before GpuCrashDumpCallback. If the flag is not set,
|
||||
// ShaderDebugInfoCallback will be called for every shader that is compiled.
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_EnableGpuCrashDumps(
|
||||
GFSDK_Aftermath_Version_API,
|
||||
GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
|
||||
GFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks, // Let the Nsight Aftermath library cache shader debug information.
|
||||
GpuCrashDumpCallback, // Register callback for GPU crash dumps.
|
||||
ShaderDebugInfoCallback, // Register callback for shader debug information.
|
||||
CrashDumpDescriptionCallback, // Register callback for GPU crash dump description.
|
||||
ResolveMarkerCallback, // Register callback for resolving application-managed markers.
|
||||
this)); // Set the GpuCrashTracker object as user data for the above callbacks.
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
// Handler for GPU crash dump callbacks from Nsight Aftermath
|
||||
void GpuCrashTracker::OnCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize)
|
||||
{
|
||||
// Make sure only one thread at a time...
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// Write to file for later in-depth analysis with Nsight Graphics.
|
||||
WriteGpuCrashDumpToFile(pGpuCrashDump, gpuCrashDumpSize);
|
||||
}
|
||||
|
||||
// Handler for shader debug information callbacks
|
||||
void GpuCrashTracker::OnShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize)
|
||||
{
|
||||
// Make sure only one thread at a time...
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// Get shader debug information identifier
|
||||
GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier = {};
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(
|
||||
GFSDK_Aftermath_Version_API,
|
||||
pShaderDebugInfo,
|
||||
shaderDebugInfoSize,
|
||||
&identifier));
|
||||
|
||||
// Store information for decoding of GPU crash dumps with shader address mapping
|
||||
// from within the application.
|
||||
std::vector<uint8_t> data((uint8_t*)pShaderDebugInfo, (uint8_t*)pShaderDebugInfo + shaderDebugInfoSize);
|
||||
m_shaderDebugInfo[identifier].swap(data);
|
||||
|
||||
// Write to file for later in-depth analysis of crash dumps with Nsight Graphics
|
||||
WriteShaderDebugInformationToFile(identifier, pShaderDebugInfo, shaderDebugInfoSize);
|
||||
}
|
||||
|
||||
// Handler for GPU crash dump description callbacks
|
||||
void GpuCrashTracker::OnDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription)
|
||||
{
|
||||
// Add some basic description about the crash. This is called after the GPU crash happens, but before
|
||||
// the actual GPU crash dump callback. The provided data is included in the crash dump and can be
|
||||
// retrieved using GFSDK_Aftermath_GpuCrashDump_GetDescription().
|
||||
addDescription(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, "Granite");
|
||||
addDescription(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationVersion, "v1.0");
|
||||
}
|
||||
|
||||
// Handler for app-managed marker resolve callback
|
||||
void GpuCrashTracker::OnResolveMarker(const void* pMarkerData, const uint32_t, void** ppResolvedMarkerData, uint32_t* pResolvedMarkerDataSize)
|
||||
{
|
||||
// Important: the pointer passed back via ppResolvedMarkerData must remain valid after this function returns
|
||||
// using references for all of the m_markerMap accesses ensures that the pointers refer to the persistent data
|
||||
for (auto& map : m_markerMap)
|
||||
{
|
||||
const auto& foundMarker = map.find((uint64_t)pMarkerData);
|
||||
if (foundMarker != map.end())
|
||||
{
|
||||
const std::string& foundMarkerData = foundMarker->second;
|
||||
// std::string::data() will return a valid pointer until the string is next modified
|
||||
// we don't modify the string after calling data() here, so the pointer should remain valid
|
||||
*ppResolvedMarkerData = (void*)foundMarkerData.data();
|
||||
*pResolvedMarkerDataSize = (uint32_t)foundMarkerData.length();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for writing a GPU crash dump to a file
|
||||
void GpuCrashTracker::WriteGpuCrashDumpToFile(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize)
|
||||
{
|
||||
// Create a GPU crash dump decoder object for the GPU crash dump.
|
||||
GFSDK_Aftermath_GpuCrashDump_Decoder decoder = {};
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_CreateDecoder(
|
||||
GFSDK_Aftermath_Version_API,
|
||||
pGpuCrashDump,
|
||||
gpuCrashDumpSize,
|
||||
&decoder));
|
||||
|
||||
// Use the decoder object to read basic information, like application
|
||||
// name, PID, etc. from the GPU crash dump.
|
||||
GFSDK_Aftermath_GpuCrashDump_BaseInfo baseInfo = {};
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GetBaseInfo(decoder, &baseInfo));
|
||||
|
||||
// Use the decoder object to query the application name that was set
|
||||
// in the GPU crash dump description.
|
||||
uint32_t applicationNameLength = 0;
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GetDescriptionSize(
|
||||
decoder,
|
||||
GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName,
|
||||
&applicationNameLength));
|
||||
|
||||
std::vector<char> applicationName(applicationNameLength, '\0');
|
||||
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GetDescription(
|
||||
decoder,
|
||||
GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName,
|
||||
uint32_t(applicationName.size()),
|
||||
applicationName.data()));
|
||||
|
||||
// Create a unique file name for writing the crash dump data to a file.
|
||||
// Note: due to an Nsight Aftermath bug (will be fixed in an upcoming
|
||||
// driver release) we may see redundant crash dumps. As a workaround,
|
||||
// attach a unique count to each generated file name.
|
||||
static int count = 0;
|
||||
const std::string baseFileName =
|
||||
std::string(applicationName.data())
|
||||
+ "-"
|
||||
+ std::to_string(baseInfo.pid)
|
||||
+ "-"
|
||||
+ std::to_string(++count);
|
||||
|
||||
// Write the crash dump data to a file using the .nv-gpudmp extension
|
||||
// registered with Nsight Graphics.
|
||||
const std::string crashDumpFileName = baseFileName + ".nv-gpudmp";
|
||||
std::ofstream dumpFile(crashDumpFileName, std::ios::out | std::ios::binary);
|
||||
if (dumpFile)
|
||||
{
|
||||
dumpFile.write((const char*)pGpuCrashDump, gpuCrashDumpSize);
|
||||
dumpFile.close();
|
||||
LOGI("Wrote crash dump file to: %s.\n", crashDumpFileName.c_str());
|
||||
}
|
||||
|
||||
// Decode the crash dump to a JSON string.
|
||||
// Step 1: Generate the JSON and get the size.
|
||||
uint32_t jsonSize = 0;
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
|
||||
decoder,
|
||||
GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
|
||||
GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE,
|
||||
ShaderDebugInfoLookupCallback,
|
||||
ShaderLookupCallback,
|
||||
ShaderSourceDebugInfoLookupCallback,
|
||||
this,
|
||||
&jsonSize));
|
||||
|
||||
// Step 2: Allocate a buffer and fetch the generated JSON.
|
||||
std::vector<char> json(jsonSize);
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GetJSON(
|
||||
decoder,
|
||||
uint32_t(json.size()),
|
||||
json.data()));
|
||||
|
||||
// Write the crash dump data as JSON to a file.
|
||||
const std::string jsonFileName = crashDumpFileName + ".json";
|
||||
std::ofstream jsonFile(jsonFileName, std::ios::out | std::ios::binary);
|
||||
if (jsonFile)
|
||||
{
|
||||
// Write the JSON to the file (excluding string termination)
|
||||
jsonFile.write(json.data(), json.size() - 1);
|
||||
jsonFile.close();
|
||||
LOGI("Wrote crash dump JSON file to: %s.\n", jsonFileName.c_str());
|
||||
}
|
||||
|
||||
// Dump active SPIR-V files.
|
||||
uint32_t shader_count = 0;
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_GetActiveShadersInfoCount(decoder, &shader_count));
|
||||
std::vector<GFSDK_Aftermath_GpuCrashDump_ShaderInfo> shader_infos(shader_count);
|
||||
AFTERMATH_CHECK_ERROR(
|
||||
GFSDK_Aftermath_GpuCrashDump_GetActiveShadersInfo(decoder, shader_count, shader_infos.data()));
|
||||
|
||||
for (auto &shader : shader_infos)
|
||||
{
|
||||
GFSDK_Aftermath_ShaderBinaryHash hash;
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GetShaderHashForShaderInfo(decoder, &shader, &hash));
|
||||
|
||||
std::lock_guard<std::mutex> holder{ shader_lock };
|
||||
auto itr = shader_db.find(hash);
|
||||
if (itr != shader_db.end())
|
||||
{
|
||||
const std::string spirvFilePath = "shader_" + std::to_string(hash) + ".spv";
|
||||
std::ofstream spirvFile(spirvFilePath, std::ios::out | std::ios::binary);
|
||||
if (spirvFile)
|
||||
{
|
||||
spirvFile.write((const char *)itr->second.data(), itr->second.size() * sizeof(uint32_t));
|
||||
LOGI("Wrote SPIR-V shader file to: %s.\n", spirvFilePath.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the GPU crash dump decoder object.
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder));
|
||||
|
||||
#ifdef _WIN32
|
||||
char print_buffer[1024];
|
||||
char current_dir[1024];
|
||||
|
||||
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
||||
snprintf(print_buffer, sizeof(print_buffer),
|
||||
"GPU hang detected with NV Aftermath. Dump files have been written to %s\\%s. Terminating process ...",
|
||||
current_dir, crashDumpFileName.c_str());
|
||||
MessageBoxA(nullptr, print_buffer, "VK_ERROR_DEVICE_LOST", MB_OK);
|
||||
TerminateProcess(GetCurrentProcess(), 1);
|
||||
#else
|
||||
std::terminate();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GpuCrashTracker::RegisterShader(const void *code, size_t size)
|
||||
{
|
||||
std::vector<uint32_t> data(static_cast<const uint32_t *>(code),
|
||||
static_cast<const uint32_t *>(code) + size / sizeof(uint32_t));
|
||||
|
||||
// Create shader hash for the shader
|
||||
const GFSDK_Aftermath_SpirvCode shader{{data.data()}, uint32_t(size)};
|
||||
GFSDK_Aftermath_ShaderBinaryHash shaderHash;
|
||||
AFTERMATH_CHECK_ERROR(GFSDK_Aftermath_GetShaderHashSpirv(
|
||||
GFSDK_Aftermath_Version_API,
|
||||
&shader,
|
||||
&shaderHash));
|
||||
|
||||
// Store the data for shader mapping when decoding GPU crash dumps.
|
||||
// cf. FindShaderBinary()
|
||||
std::lock_guard<std::mutex> holder{shader_lock};
|
||||
shader_db[shaderHash].swap(data);
|
||||
}
|
||||
|
||||
// Helper for writing shader debug information to a file
|
||||
void GpuCrashTracker::WriteShaderDebugInformationToFile(
|
||||
GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier,
|
||||
const void* pShaderDebugInfo,
|
||||
const uint32_t shaderDebugInfoSize)
|
||||
{
|
||||
// Create a unique file name.
|
||||
const std::string filePath = "shader-" + std::to_string(identifier) + ".nvdbg";
|
||||
|
||||
std::ofstream f(filePath, std::ios::out | std::ios::binary);
|
||||
if (f)
|
||||
{
|
||||
f.write((const char*)pShaderDebugInfo, shaderDebugInfoSize);
|
||||
LOGI("Wrote shader file to: %s.\n", filePath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for shader debug information lookup callbacks.
|
||||
// This is used by the JSON decoder for mapping shader instruction
|
||||
// addresses to SPIR-V IL lines or GLSL source lines.
|
||||
void GpuCrashTracker::OnShaderDebugInfoLookup(
|
||||
const GFSDK_Aftermath_ShaderDebugInfoIdentifier& identifier,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderDebugInfo) const
|
||||
{
|
||||
auto itr = m_shaderDebugInfo.find(identifier);
|
||||
if (itr != m_shaderDebugInfo.end())
|
||||
setShaderDebugInfo(itr->second.data(), uint32_t(itr->second.size()));
|
||||
}
|
||||
|
||||
// Handler for shader lookup callbacks.
|
||||
// This is used by the JSON decoder for mapping shader instruction
|
||||
// addresses to SPIR-V IL lines or GLSL source lines.
|
||||
// NOTE: If the application loads stripped shader binaries (ie; --strip-all in spirv-remap),
|
||||
// Aftermath will require access to both the stripped and the not stripped
|
||||
// shader binaries.
|
||||
void GpuCrashTracker::OnShaderLookup(
|
||||
const GFSDK_Aftermath_ShaderBinaryHash& shaderHash,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary) const
|
||||
{
|
||||
std::lock_guard<std::mutex> holder{shader_lock};
|
||||
auto itr = shader_db.find(shaderHash);
|
||||
if (itr != shader_db.end())
|
||||
setShaderBinary(itr->second.data(), itr->second.size() * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
// Handler for shader source debug info lookup callbacks.
|
||||
// This is used by the JSON decoder for mapping shader instruction addresses to
|
||||
// GLSL source lines, if the shaders used by the application were compiled with
|
||||
// separate debug info data files.
|
||||
void GpuCrashTracker::OnShaderSourceDebugInfoLookup(
|
||||
const GFSDK_Aftermath_ShaderDebugName&,
|
||||
PFN_GFSDK_Aftermath_SetData) const
|
||||
{
|
||||
// Granite doesn't do this.
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnCrashDump
|
||||
void GpuCrashTracker::GpuCrashDumpCallback(
|
||||
const void* pGpuCrashDump,
|
||||
const uint32_t gpuCrashDumpSize,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnCrashDump(pGpuCrashDump, gpuCrashDumpSize);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnShaderDebugInfo
|
||||
void GpuCrashTracker::ShaderDebugInfoCallback(
|
||||
const void* pShaderDebugInfo,
|
||||
const uint32_t shaderDebugInfoSize,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnShaderDebugInfo(pShaderDebugInfo, shaderDebugInfoSize);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnDescription
|
||||
void GpuCrashTracker::CrashDumpDescriptionCallback(
|
||||
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnDescription(addDescription);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnResolveMarker
|
||||
void GpuCrashTracker::ResolveMarkerCallback(
|
||||
const void* pMarkerData,
|
||||
const uint32_t markerDataSize,
|
||||
void* pUserData,
|
||||
void** ppResolvedMarkerData,
|
||||
uint32_t* pResolvedMarkerDataSize)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnResolveMarker(pMarkerData, markerDataSize, ppResolvedMarkerData, pResolvedMarkerDataSize);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnShaderDebugInfoLookup
|
||||
void GpuCrashTracker::ShaderDebugInfoLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderDebugInfoIdentifier* pIdentifier,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderDebugInfo,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnShaderDebugInfoLookup(*pIdentifier, setShaderDebugInfo);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnShaderLookup
|
||||
void GpuCrashTracker::ShaderLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderBinaryHash* pShaderHash,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnShaderLookup(*pShaderHash, setShaderBinary);
|
||||
}
|
||||
|
||||
// Static callback wrapper for OnShaderSourceDebugInfoLookup
|
||||
void GpuCrashTracker::ShaderSourceDebugInfoLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderDebugName* pShaderDebugName,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary,
|
||||
void* pUserData)
|
||||
{
|
||||
auto* pGpuCrashTracker = static_cast<GpuCrashTracker*>(pUserData);
|
||||
pGpuCrashTracker->OnShaderSourceDebugInfoLookup(*pShaderDebugName, setShaderBinary);
|
||||
}
|
||||
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// 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/vulkan.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "NsightAftermathHelpers.h"
|
||||
|
||||
//*********************************************************
|
||||
// Implements GPU crash dump tracking using the Nsight
|
||||
// Aftermath API.
|
||||
//
|
||||
class GpuCrashTracker
|
||||
{
|
||||
public:
|
||||
// keep four frames worth of marker history
|
||||
const static unsigned int c_markerFrameHistory = 4;
|
||||
typedef std::array<std::map<uint64_t, std::string>, c_markerFrameHistory> MarkerMap;
|
||||
|
||||
GpuCrashTracker(const MarkerMap& markerMap);
|
||||
~GpuCrashTracker();
|
||||
|
||||
// Initialize the GPU crash dump tracker.
|
||||
void Initialize();
|
||||
|
||||
void RegisterShader(const void *code, size_t size);
|
||||
|
||||
private:
|
||||
|
||||
//*********************************************************
|
||||
// Callback handlers for GPU crash dumps and related data.
|
||||
//
|
||||
|
||||
// Handler for GPU crash dump callbacks.
|
||||
void OnCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize);
|
||||
|
||||
// Handler for shader debug information callbacks.
|
||||
void OnShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize);
|
||||
|
||||
// Handler for GPU crash dump description callbacks.
|
||||
void OnDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription);
|
||||
|
||||
// Handler for app-managed marker resolve callback
|
||||
void OnResolveMarker(const void* pMarkerData, const uint32_t markerDataSize, void** ppResolvedMarkerData, uint32_t* pResolvedMarkerDataSize);
|
||||
|
||||
//*********************************************************
|
||||
// Helpers for writing a GPU crash dump and debug information
|
||||
// data to files.
|
||||
//
|
||||
|
||||
// Helper for writing a GPU crash dump to a file.
|
||||
void WriteGpuCrashDumpToFile(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize);
|
||||
|
||||
// Helper for writing shader debug information to a file
|
||||
void WriteShaderDebugInformationToFile(
|
||||
GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier,
|
||||
const void* pShaderDebugInfo,
|
||||
const uint32_t shaderDebugInfoSize);
|
||||
|
||||
//*********************************************************
|
||||
// Helpers for decoding GPU crash dump to JSON.
|
||||
//
|
||||
|
||||
// Handler for shader debug info lookup callbacks.
|
||||
void OnShaderDebugInfoLookup(
|
||||
const GFSDK_Aftermath_ShaderDebugInfoIdentifier& identifier,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderDebugInfo) const;
|
||||
|
||||
// Handler for shader lookup callbacks.
|
||||
void OnShaderLookup(
|
||||
const GFSDK_Aftermath_ShaderBinaryHash& shaderHash,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary) const;
|
||||
|
||||
// Handler for shader source debug info lookup callbacks.
|
||||
void OnShaderSourceDebugInfoLookup(
|
||||
const GFSDK_Aftermath_ShaderDebugName& shaderDebugName,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary) const;
|
||||
|
||||
//*********************************************************
|
||||
// Static callback wrappers.
|
||||
//
|
||||
|
||||
// GPU crash dump callback.
|
||||
static void GpuCrashDumpCallback(
|
||||
const void* pGpuCrashDump,
|
||||
const uint32_t gpuCrashDumpSize,
|
||||
void* pUserData);
|
||||
|
||||
// Shader debug information callback.
|
||||
static void ShaderDebugInfoCallback(
|
||||
const void* pShaderDebugInfo,
|
||||
const uint32_t shaderDebugInfoSize,
|
||||
void* pUserData);
|
||||
|
||||
// GPU crash dump description callback.
|
||||
static void CrashDumpDescriptionCallback(
|
||||
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription,
|
||||
void* pUserData);
|
||||
|
||||
// App-managed marker resolve callback
|
||||
static void ResolveMarkerCallback(
|
||||
const void* pMarkerData,
|
||||
const uint32_t markerDataSize,
|
||||
void* pUserData,
|
||||
void** ppResolvedMarkerData,
|
||||
uint32_t* pResolvedMarkerDataSize);
|
||||
|
||||
// Shader debug information lookup callback.
|
||||
static void ShaderDebugInfoLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderDebugInfoIdentifier* pIdentifier,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderDebugInfo,
|
||||
void* pUserData);
|
||||
|
||||
// Shader lookup callback.
|
||||
static void ShaderLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderBinaryHash* pShaderHash,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary,
|
||||
void* pUserData);
|
||||
|
||||
// Shader source debug info lookup callback.
|
||||
static void ShaderSourceDebugInfoLookupCallback(
|
||||
const GFSDK_Aftermath_ShaderDebugName* pShaderDebugName,
|
||||
PFN_GFSDK_Aftermath_SetData setShaderBinary,
|
||||
void* pUserData);
|
||||
|
||||
//*********************************************************
|
||||
// GPU crash tracker state.
|
||||
//
|
||||
|
||||
// Is the GPU crash dump tracker initialized?
|
||||
bool m_initialized;
|
||||
|
||||
// For thread-safe access of GPU crash tracker state.
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
// List of Shader Debug Information by ShaderDebugInfoIdentifier.
|
||||
std::map<GFSDK_Aftermath_ShaderDebugInfoIdentifier, std::vector<uint8_t>> m_shaderDebugInfo;
|
||||
|
||||
// App-managed marker tracking
|
||||
const MarkerMap& m_markerMap;
|
||||
|
||||
mutable std::mutex shader_lock;
|
||||
std::map<GFSDK_Aftermath_ShaderBinaryHash, std::vector<uint32_t>> shader_db;
|
||||
};
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved.
|
||||
//
|
||||
// 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 <iomanip>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
|
||||
#include "GFSDK_Aftermath.h"
|
||||
#include "GFSDK_Aftermath_GpuCrashDump.h"
|
||||
#include "GFSDK_Aftermath_GpuCrashDumpDecoding.h"
|
||||
|
||||
//*********************************************************
|
||||
// Some std::to_string overloads for some Nsight Aftermath
|
||||
// API types.
|
||||
//
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<typename T>
|
||||
inline std::string to_hex_string(T n)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << std::setfill('0') << std::setw(2 * sizeof(T)) << std::hex << n;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
inline std::string to_string(GFSDK_Aftermath_Result result)
|
||||
{
|
||||
return std::string("0x") + to_hex_string(static_cast<uint32_t>(result));
|
||||
}
|
||||
|
||||
inline std::string to_string(const GFSDK_Aftermath_ShaderDebugInfoIdentifier& identifier)
|
||||
{
|
||||
return to_hex_string(identifier.id[0]) + "-" + to_hex_string(identifier.id[1]);
|
||||
}
|
||||
|
||||
inline std::string to_string(const GFSDK_Aftermath_ShaderBinaryHash& hash)
|
||||
{
|
||||
return to_hex_string(hash.hash);
|
||||
}
|
||||
} // namespace std
|
||||
|
||||
//*********************************************************
|
||||
// Helper for comparing shader hashes and debug info identifier.
|
||||
//
|
||||
|
||||
// Helper for comparing GFSDK_Aftermath_ShaderDebugInfoIdentifier.
|
||||
inline bool operator<(const GFSDK_Aftermath_ShaderDebugInfoIdentifier& lhs, const GFSDK_Aftermath_ShaderDebugInfoIdentifier& rhs)
|
||||
{
|
||||
if (lhs.id[0] == rhs.id[0])
|
||||
{
|
||||
return lhs.id[1] < rhs.id[1];
|
||||
}
|
||||
return lhs.id[0] < rhs.id[0];
|
||||
}
|
||||
|
||||
// Helper for comparing GFSDK_Aftermath_ShaderBinaryHash.
|
||||
inline bool operator<(const GFSDK_Aftermath_ShaderBinaryHash& lhs, const GFSDK_Aftermath_ShaderBinaryHash& rhs)
|
||||
{
|
||||
return lhs.hash < rhs.hash;
|
||||
}
|
||||
|
||||
// Helper for comparing GFSDK_Aftermath_ShaderDebugName.
|
||||
inline bool operator<(const GFSDK_Aftermath_ShaderDebugName& lhs, const GFSDK_Aftermath_ShaderDebugName& rhs)
|
||||
{
|
||||
return strncmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0;
|
||||
}
|
||||
|
||||
//*********************************************************
|
||||
// Helper for checking Nsight Aftermath failures.
|
||||
//
|
||||
|
||||
inline std::string AftermathErrorMessage(GFSDK_Aftermath_Result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case GFSDK_Aftermath_Result_FAIL_DriverVersionNotSupported:
|
||||
return "Unsupported driver version - requires an NVIDIA R495 display driver or newer.";
|
||||
default:
|
||||
return "Aftermath Error 0x" + std::to_hex_string(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper macro for checking Nsight Aftermath results and throwing exception
|
||||
// in case of a failure.
|
||||
#ifdef _WIN32
|
||||
#define AFTERMATH_CHECK_ERROR(FC) \
|
||||
[&]() { \
|
||||
GFSDK_Aftermath_Result _result = FC; \
|
||||
if (!GFSDK_Aftermath_SUCCEED(_result)) \
|
||||
{ \
|
||||
MessageBoxA(0, AftermathErrorMessage(_result).c_str(), "Aftermath Error", MB_OK); \
|
||||
exit(1); \
|
||||
} \
|
||||
}()
|
||||
#else
|
||||
#define AFTERMATH_CHECK_ERROR(FC) \
|
||||
[&]() { \
|
||||
GFSDK_Aftermath_Result _result = FC; \
|
||||
if (!GFSDK_Aftermath_SUCCEED(_result)) \
|
||||
{ \
|
||||
printf("%s\n", AftermathErrorMessage(_result).c_str()); \
|
||||
fflush(stdout); \
|
||||
exit(1); \
|
||||
} \
|
||||
}()
|
||||
#endif
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2017-2026 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 "post_mortem.hpp"
|
||||
|
||||
#ifdef HAVE_AFTERMATH_SDK
|
||||
#include "NsightAftermathGpuCrashTracker.h"
|
||||
#endif
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
namespace PostMortem
|
||||
{
|
||||
class CrashTracker
|
||||
{
|
||||
public:
|
||||
virtual ~CrashTracker() = default;
|
||||
virtual void register_shader(const void *data, size_t size) = 0;
|
||||
};
|
||||
|
||||
static std::unique_ptr<CrashTracker> global_tracker;
|
||||
|
||||
#ifdef HAVE_AFTERMATH_SDK
|
||||
struct NsightCrashTracker : CrashTracker
|
||||
{
|
||||
GpuCrashTracker::MarkerMap marker;
|
||||
GpuCrashTracker tracker;
|
||||
NsightCrashTracker() : tracker(marker) {}
|
||||
void register_shader(const void *data, size_t size) override { tracker.RegisterShader(data, size); }
|
||||
};
|
||||
#endif
|
||||
|
||||
void init_nv_aftermath()
|
||||
{
|
||||
if (global_tracker)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_AFTERMATH_SDK
|
||||
auto tracker = std::make_unique<NsightCrashTracker>();
|
||||
tracker->tracker.Initialize();
|
||||
global_tracker = std::move(tracker);
|
||||
#endif
|
||||
}
|
||||
|
||||
void register_shader(const void *data, size_t size)
|
||||
{
|
||||
if (global_tracker)
|
||||
global_tracker->register_shader(data, size);
|
||||
}
|
||||
|
||||
void deinit()
|
||||
{
|
||||
global_tracker.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/* Copyright (c) 2017-2026 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 <memory>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
namespace PostMortem
|
||||
{
|
||||
void init_nv_aftermath();
|
||||
void deinit();
|
||||
void register_shader(const void *code, size_t size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
/* Copyright (c) 2017-2026 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();
|
||||
if (device->get_device_features().vk12_features.hostQueryReset)
|
||||
table.vkResetQueryPool(device->get_device(), pool, 0, 1);
|
||||
else
|
||||
table.vkCmdResetQueryPool(cmd, 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_, VkQueryType type_)
|
||||
: device(device_)
|
||||
, table(device_->get_device_table())
|
||||
, type(type_)
|
||||
{
|
||||
supports_type = false;
|
||||
|
||||
if (type == VK_QUERY_TYPE_TIMESTAMP)
|
||||
{
|
||||
// Ignore timestampValidBits and friends for now.
|
||||
supports_type = device->get_gpu_properties().limits.timestampComputeAndGraphics;
|
||||
}
|
||||
else if (type == VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR)
|
||||
{
|
||||
supports_type = device->get_device_features().rtas_features.accelerationStructure == VK_TRUE;
|
||||
}
|
||||
|
||||
if (supports_type)
|
||||
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_value(pool.query_results[j]);
|
||||
|
||||
if (device->get_device_features().vk12_features.hostQueryReset)
|
||||
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 = type;
|
||||
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);
|
||||
|
||||
if (device->get_device_features().vk12_features.hostQueryReset)
|
||||
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_type)
|
||||
{
|
||||
LOGI("Timestamps are not supported on this implementation.\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
VK_ASSERT((stage & (stage - 1)) == 0);
|
||||
|
||||
auto handle = allocate_query(cmd);
|
||||
table.vkCmdWriteTimestamp2(cmd, stage, handle->get_query_pool(), handle->get_query_pool_index());
|
||||
return handle;
|
||||
}
|
||||
|
||||
QueryPoolHandle QueryPool::allocate_query(VkCommandBuffer cmd)
|
||||
{
|
||||
if (!supports_type)
|
||||
{
|
||||
LOGI("Query type %u not supported on this implementation.\n", type);
|
||||
return {};
|
||||
}
|
||||
|
||||
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, type == VK_QUERY_TYPE_TIMESTAMP, type, pool.pool, pool.index));
|
||||
pool.cookies[pool.index] = cookie;
|
||||
|
||||
if (!device->get_device_features().vk12_features.hostQueryReset)
|
||||
table.vkCmdResetQueryPool(cmd, pool.pool, pool.index, 1);
|
||||
|
||||
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,208 @@
|
||||
/* Copyright (c) 2017-2026 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;
|
||||
|
||||
inline void signal_value(uint64_t ticks)
|
||||
{
|
||||
value = ticks;
|
||||
has_value = true;
|
||||
}
|
||||
|
||||
// Compatibility alias.
|
||||
inline uint64_t get_timestamp_ticks() const
|
||||
{
|
||||
VK_ASSERT(type == VK_QUERY_TYPE_TIMESTAMP);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline uint64_t get_value() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool is_signalled() const
|
||||
{
|
||||
return has_value;
|
||||
}
|
||||
|
||||
inline bool is_device_timebase() const
|
||||
{
|
||||
return device_timebase;
|
||||
}
|
||||
|
||||
inline VkQueryPool get_query_pool() const
|
||||
{
|
||||
return pool;
|
||||
}
|
||||
|
||||
inline uint32_t get_query_pool_index() const
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Util::ObjectPool<QueryPoolResult>;
|
||||
|
||||
explicit QueryPoolResult(Device *device_, bool device_timebase_, VkQueryType type_,
|
||||
VkQueryPool pool_, uint32_t index_)
|
||||
: device(device_), device_timebase(device_timebase_), type(type_), pool(pool_), index(index_)
|
||||
{}
|
||||
|
||||
Device *device;
|
||||
uint64_t value = 0;
|
||||
bool has_value = false;
|
||||
bool device_timebase = false;
|
||||
VkQueryType type;
|
||||
VkQueryPool pool;
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
using QueryPoolHandle = Util::IntrusivePtr<QueryPoolResult>;
|
||||
|
||||
class QueryPool
|
||||
{
|
||||
public:
|
||||
QueryPool(Device *device, VkQueryType type);
|
||||
~QueryPool();
|
||||
|
||||
void begin();
|
||||
|
||||
QueryPoolHandle write_timestamp(VkCommandBuffer cmd, VkPipelineStageFlags2 stage);
|
||||
QueryPoolHandle allocate_query(VkCommandBuffer cmd);
|
||||
|
||||
private:
|
||||
Device *device;
|
||||
const VolkDeviceTable &table;
|
||||
VkQueryType type;
|
||||
|
||||
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_type = 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,50 @@
|
||||
/* Copyright (c) 2017-2026 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 broken_present_fence = false;
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,270 @@
|
||||
/* Copyright (c) 2017-2026 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 = {};
|
||||
|
||||
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-2026 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,61 @@
|
||||
/* Copyright (c) 2017-2026 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 "rtas.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
RTAS::RTAS(Device *device_, VkAccelerationStructureKHR rtas_,
|
||||
VkAccelerationStructureTypeKHR type_, BufferHandle backing_)
|
||||
: Cookie(device_)
|
||||
, device(device_)
|
||||
, rtas(rtas_)
|
||||
, type(type_)
|
||||
, backing(std::move(backing_))
|
||||
{
|
||||
VkAccelerationStructureDeviceAddressInfoKHR info =
|
||||
{ VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR };
|
||||
info.accelerationStructure = rtas;
|
||||
bda = device->get_device_table().vkGetAccelerationStructureDeviceAddressKHR(device->get_device(), &info);
|
||||
}
|
||||
|
||||
VkDeviceSize RTAS::get_scratch_size(BuildMode mode) const
|
||||
{
|
||||
return mode == BuildMode::Build ? build_size : update_size;
|
||||
}
|
||||
|
||||
RTAS::~RTAS()
|
||||
{
|
||||
device->destroy_rtas(rtas);
|
||||
}
|
||||
|
||||
void RTASDeleter::operator()(Vulkan::RTAS *rtas)
|
||||
{
|
||||
// Avoid hitting destruction callback inside a callback.
|
||||
rtas->backing.reset();
|
||||
|
||||
rtas->device->handle_pool.rtas.free(rtas);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/* Copyright (c) 2017-2026 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"
|
||||
#include "buffer.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class RTAS;
|
||||
class Device;
|
||||
|
||||
struct RTASDeleter
|
||||
{
|
||||
void operator()(RTAS *rtas);
|
||||
};
|
||||
|
||||
enum class BLASMode
|
||||
{
|
||||
Static, // fast trace, compactable, not updateable
|
||||
Skinned // fast update, updateable
|
||||
};
|
||||
|
||||
enum class BuildMode
|
||||
{
|
||||
Build,
|
||||
Update
|
||||
};
|
||||
|
||||
struct BottomRTASGeometry
|
||||
{
|
||||
VkFormat format;
|
||||
VkDeviceAddress vbo;
|
||||
uint32_t num_vertices;
|
||||
uint32_t stride;
|
||||
|
||||
VkDeviceAddress ibo;
|
||||
VkIndexType index_type;
|
||||
uint32_t num_primitives;
|
||||
|
||||
VkDeviceAddress transform;
|
||||
};
|
||||
|
||||
struct BottomRTASCreateInfo
|
||||
{
|
||||
BLASMode mode;
|
||||
const BottomRTASGeometry *geometries;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct RTASInstance
|
||||
{
|
||||
// One of the two.
|
||||
const VkAccelerationStructureInstanceKHR *instance;
|
||||
VkDeviceAddress bda;
|
||||
};
|
||||
|
||||
struct TopRTASCreateInfo
|
||||
{
|
||||
const RTASInstance *instances;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
class RTAS : public Util::IntrusivePtrEnabled<RTAS, RTASDeleter, HandleCounter>,
|
||||
public Cookie
|
||||
{
|
||||
public:
|
||||
friend struct RTASDeleter;
|
||||
~RTAS();
|
||||
|
||||
inline VkAccelerationStructureKHR get_rtas() const
|
||||
{
|
||||
return rtas;
|
||||
}
|
||||
|
||||
inline VkAccelerationStructureTypeKHR get_type() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
inline VkDeviceAddress get_device_address() const
|
||||
{
|
||||
return bda;
|
||||
}
|
||||
|
||||
VkDeviceSize get_scratch_size(BuildMode mode) const;
|
||||
|
||||
inline void set_scratch_size(VkDeviceSize build_size_, VkDeviceSize update_size_)
|
||||
{
|
||||
build_size = build_size_;
|
||||
update_size = update_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Util::ObjectPool<RTAS>;
|
||||
RTAS(Device *device, VkAccelerationStructureKHR rtas,
|
||||
VkAccelerationStructureTypeKHR type, BufferHandle backing);
|
||||
Device *device;
|
||||
VkAccelerationStructureKHR rtas;
|
||||
VkAccelerationStructureTypeKHR type;
|
||||
BufferHandle backing;
|
||||
VkDeviceSize build_size;
|
||||
VkDeviceSize update_size;
|
||||
VkDeviceAddress bda = 0;
|
||||
};
|
||||
|
||||
using RTASHandle = Util::IntrusivePtr<RTAS>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* Copyright (c) 2017-2026 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_)
|
||||
{
|
||||
// In heap, the VkSampler is a dummy object which is literally just the index into heap.
|
||||
|
||||
if (device->get_device_features().supports_descriptor_buffer &&
|
||||
!device->get_device_features().descriptor_heap_features.descriptorHeap)
|
||||
{
|
||||
payload = device->managers.descriptor_buffer.alloc_sampler();
|
||||
VkDescriptorGetInfoEXT get_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT };
|
||||
get_info.type = VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
get_info.data.pSampler = &sampler;
|
||||
device->get_device_table().vkGetDescriptorEXT(
|
||||
device->get_device(),
|
||||
&get_info,
|
||||
device->get_device_features().descriptor_buffer_properties.samplerDescriptorSize,
|
||||
payload.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
Sampler::~Sampler()
|
||||
{
|
||||
if (sampler)
|
||||
{
|
||||
if (immutable)
|
||||
device->managers.descriptor_buffer.destroy_sampler(sampler);
|
||||
else if (internal_sync)
|
||||
device->destroy_sampler_nolock(sampler);
|
||||
else
|
||||
device->destroy_sampler(sampler);
|
||||
}
|
||||
|
||||
if (payload)
|
||||
{
|
||||
if (internal_sync)
|
||||
device->free_cached_descriptor_payload_nolock(payload);
|
||||
else
|
||||
device->free_cached_descriptor_payload(payload);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
VkSampler vk_sampler = device->managers.descriptor_buffer.create_sampler(&info);
|
||||
|
||||
#ifdef GRANITE_VULKAN_FOSSILIZE
|
||||
// Immutable samplers are on the chopping block ...
|
||||
if (!device->get_device_features().supports_descriptor_buffer_or_heap)
|
||||
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,154 @@
|
||||
/* Copyright (c) 2017-2026 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"
|
||||
#include "memory_allocator.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 CachedDescriptorPayload &get_descriptor_payload() const
|
||||
{
|
||||
VK_ASSERT(payload && payload.type == VK_DESCRIPTOR_TYPE_SAMPLER);
|
||||
return payload;
|
||||
}
|
||||
|
||||
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;
|
||||
CachedDescriptorPayload payload;
|
||||
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-2026 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,211 @@
|
||||
/* Copyright (c) 2017-2026 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;
|
||||
}
|
||||
|
||||
bool is_owned() const
|
||||
{
|
||||
return owned;
|
||||
}
|
||||
|
||||
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-2026 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-2026 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;
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,357 @@
|
||||
/* Copyright (c) 2017-2026 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,
|
||||
Fragment = 4, // Skip over tess/geom to match Vulkan ordering.
|
||||
Compute,
|
||||
Task,
|
||||
Mesh,
|
||||
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 = 7 };
|
||||
|
||||
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 CombinedImageSamplerIndex
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint32_t image_heap_index : 20;
|
||||
uint32_t sampler_heap_index : 12;
|
||||
};
|
||||
uint32_t word;
|
||||
};
|
||||
static_assert(sizeof(CombinedImageSamplerIndex) == sizeof(uint32_t), "Unexpected size of CombinedImageSamplerIndex.");
|
||||
|
||||
union ResourceBinding
|
||||
{
|
||||
VkDescriptorBufferInfo buffer;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorImageInfo fp;
|
||||
VkDescriptorImageInfo integer;
|
||||
const uint8_t *fp_ptr;
|
||||
const uint8_t *integer_ptr;
|
||||
const uint8_t *sampler_ptr;
|
||||
CombinedImageSamplerIndex fp_heap_index;
|
||||
CombinedImageSamplerIndex integer_heap_index;
|
||||
} image;
|
||||
|
||||
VkDescriptorAddressInfoEXT buffer_addr_buffer;
|
||||
VkDeviceAddressRangeEXT buffer_addr_heap;
|
||||
VkAccelerationStructureKHR rtas;
|
||||
|
||||
union
|
||||
{
|
||||
VkBufferView handle;
|
||||
struct
|
||||
{
|
||||
const uint8_t *ptr;
|
||||
uint32_t heap_index;
|
||||
} buffer;
|
||||
} 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];
|
||||
|
||||
union
|
||||
{
|
||||
uint32_t push_data_words[(VULKAN_PUSH_DATA_SIZE - VULKAN_PUSH_CONSTANT_SIZE) / (VULKAN_NUM_DESCRIPTOR_SETS * sizeof(uint32_t))];
|
||||
VkDeviceAddress push_data_addr[(VULKAN_PUSH_DATA_SIZE - VULKAN_PUSH_CONSTANT_SIZE) / (VULKAN_NUM_DESCRIPTOR_SETS * sizeof(VkDeviceAddress))];
|
||||
} inline_descriptors[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Legacy
|
||||
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;
|
||||
}
|
||||
|
||||
// Heap
|
||||
enum class DescriptorStrategy
|
||||
{
|
||||
// For images: a u32 index. For buffers: PUSH_ADDRESS.
|
||||
Inline,
|
||||
// Not compatible with array of samplers or combined image samplers.
|
||||
// Not compatible with SSBO that need ArrayLength.
|
||||
HeapSlice,
|
||||
// Indirect version of inline, for larger sets.
|
||||
IndirectTable,
|
||||
};
|
||||
|
||||
// Allocation size from indirection table UBO.
|
||||
uint32_t get_heap_table_size(uint32_t desc_set) const
|
||||
{
|
||||
return heap.heap_table_size[desc_set];
|
||||
}
|
||||
|
||||
// Allocation size from descriptor heap.
|
||||
// Used when we want to copy descriptors straight into the heap.
|
||||
uint32_t get_heap_slice_size(uint32_t desc_set) const
|
||||
{
|
||||
return heap.heap_slice_size[desc_set];
|
||||
}
|
||||
|
||||
uint32_t get_descriptor_set_push_buffer_offset(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.push_buffer_offsets[desc_set];
|
||||
}
|
||||
|
||||
uint32_t get_descriptor_set_push_image_offset(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.push_image_offsets[desc_set];
|
||||
}
|
||||
|
||||
uint32_t get_descriptor_set_inline_offsets(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.push_inline_offsets[desc_set];
|
||||
}
|
||||
|
||||
uint32_t get_descriptor_set_inline_size(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.push_inline_size[desc_set];
|
||||
}
|
||||
|
||||
DescriptorStrategy get_heap_buffer_descriptor_strategy(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.buffer_strategies[desc_set];
|
||||
}
|
||||
|
||||
DescriptorStrategy get_heap_image_descriptor_strategy(uint32_t desc_set) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
return heap.image_strategies[desc_set];
|
||||
}
|
||||
|
||||
// Inline: local offset into inline push data
|
||||
// HeapSlice: offset into allocated heap slice
|
||||
// IndirectTable: offset into indirect table
|
||||
uint32_t get_descriptor_offset(uint32_t desc_set, uint32_t binding) const
|
||||
{
|
||||
VK_ASSERT(desc_set < VULKAN_NUM_DESCRIPTOR_SETS);
|
||||
VK_ASSERT(binding < VULKAN_NUM_BINDINGS);
|
||||
return heap.desc_offsets[desc_set][binding];
|
||||
}
|
||||
|
||||
// Passed directly to CreatePipeline.
|
||||
const std::vector<VkDescriptorSetAndBindingMappingEXT> &get_heap_mappings() const
|
||||
{
|
||||
return heap.mappings;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
void init_heap();
|
||||
void init_heap(uint32_t set_index);
|
||||
void init_heap_buffers(uint32_t set_index);
|
||||
void init_heap_image(uint32_t set_index);
|
||||
void init_heap_offsets(uint32_t set_index);
|
||||
void init_legacy(const ImmutableSamplerBank *immutable_samplers);
|
||||
|
||||
struct
|
||||
{
|
||||
std::vector<VkDescriptorSetAndBindingMappingEXT> mappings;
|
||||
// Inline descriptors are packed together.
|
||||
uint32_t push_inline_offsets[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
uint32_t push_inline_size[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
// For tables and slices.
|
||||
uint32_t push_buffer_offsets[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
uint32_t push_image_offsets[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
uint32_t heap_table_size[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
uint32_t heap_slice_size[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
DescriptorStrategy buffer_strategies[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
DescriptorStrategy image_strategies[VULKAN_NUM_DESCRIPTOR_SETS];
|
||||
uint32_t desc_offsets[VULKAN_NUM_DESCRIPTOR_SETS][VULKAN_NUM_BINDINGS];
|
||||
uint32_t push_data_size;
|
||||
} heap = {};
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/* Copyright (c) 2017-2026 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_mapped_texture.hpp"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
struct MemoryMappedHeader
|
||||
{
|
||||
char magic[16];
|
||||
VkImageType type;
|
||||
VkFormat format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
uint32_t layers;
|
||||
uint32_t levels;
|
||||
uint32_t flags;
|
||||
uint64_t payload_size;
|
||||
uint64_t reserved1;
|
||||
};
|
||||
static const size_t header_size = 16 + 8 * 4 + 2 * 8;
|
||||
static_assert(sizeof(MemoryMappedHeader) == header_size, "Header size is not properly packed.");
|
||||
|
||||
static const char MAGIC[16] = "GRANITE TEXFMT1";
|
||||
|
||||
void MemoryMappedTexture::set_generate_mipmaps_on_load(bool enable)
|
||||
{
|
||||
mipgen_on_load = enable;
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_flags(MemoryMappedTextureFlags flags)
|
||||
{
|
||||
bool new_cube = (flags & MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT) != 0;
|
||||
if (new_cube != cube)
|
||||
abort();
|
||||
set_generate_mipmaps_on_load((flags & MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT) != 0);
|
||||
}
|
||||
|
||||
MemoryMappedTextureFlags MemoryMappedTexture::get_flags() const
|
||||
{
|
||||
MemoryMappedTextureFlags flags = 0;
|
||||
if (cube)
|
||||
flags |= MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT;
|
||||
if (mipgen_on_load)
|
||||
flags |= MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT;
|
||||
|
||||
flags |= swizzle.r << MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT;
|
||||
flags |= swizzle.g << MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT;
|
||||
flags |= swizzle.b << MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT;
|
||||
flags |= swizzle.a << MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT;
|
||||
return flags;
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_1d(VkFormat format, uint32_t width, uint32_t layers, uint32_t levels)
|
||||
{
|
||||
layout.set_1d(format, width, layers, levels);
|
||||
cube = false;
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t layers, uint32_t levels)
|
||||
{
|
||||
layout.set_2d(format, width, height, layers, levels);
|
||||
cube = false;
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t levels)
|
||||
{
|
||||
layout.set_3d(format, width, height, depth, levels);
|
||||
cube = false;
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_cube(VkFormat format, uint32_t size, uint32_t cube_layers, uint32_t levels)
|
||||
{
|
||||
layout.set_2d(format, size, size, cube_layers * 6, levels);
|
||||
cube = true;
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::copy_to_path(Granite::Filesystem &fs, const std::string &path)
|
||||
{
|
||||
if (layout.get_required_size() == 0 || !mapped)
|
||||
return false;
|
||||
|
||||
auto target_file = fs.open(path, Granite::FileMode::WriteOnly);
|
||||
if (!target_file)
|
||||
return false;
|
||||
|
||||
auto new_mapped = target_file->map_write(get_required_size());
|
||||
if (!new_mapped)
|
||||
return false;
|
||||
|
||||
memcpy(new_mapped->mutable_data(), mapped, get_required_size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_write(Granite::FileMappingHandle new_file)
|
||||
{
|
||||
file = std::move(new_file);
|
||||
mapped = file->mutable_data<uint8_t>();
|
||||
|
||||
MemoryMappedHeader header = {};
|
||||
memcpy(header.magic, MAGIC, sizeof(MAGIC));
|
||||
header.width = layout.get_width();
|
||||
header.height = layout.get_height();
|
||||
header.depth = layout.get_depth();
|
||||
header.flags = get_flags();
|
||||
header.layers = layout.get_layers();
|
||||
header.levels = layout.get_levels();
|
||||
header.payload_size = layout.get_required_size();
|
||||
header.type = layout.get_image_type();
|
||||
header.format = layout.get_format();
|
||||
memcpy(mapped, &header, sizeof(header));
|
||||
|
||||
layout.set_buffer(mapped + sizeof(header), layout.get_required_size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_write(Granite::Filesystem &fs, const std::string &path)
|
||||
{
|
||||
if (layout.get_required_size() == 0)
|
||||
return false;
|
||||
|
||||
auto new_file = fs.open(path, Granite::FileMode::WriteOnly);
|
||||
if (!new_file)
|
||||
return false;
|
||||
|
||||
auto map_handle = new_file->map_write(get_required_size());
|
||||
if (!map_handle)
|
||||
return false;
|
||||
|
||||
return map_write(std::move(map_handle));
|
||||
}
|
||||
|
||||
struct ScratchFile final : Granite::File
|
||||
{
|
||||
ScratchFile(const void *mapped, size_t size)
|
||||
{
|
||||
data.resize(size);
|
||||
if (mapped)
|
||||
memcpy(data.data(), mapped, size);
|
||||
}
|
||||
|
||||
Granite::FileMappingHandle map_subset(uint64_t offset, size_t range) override
|
||||
{
|
||||
if (offset + range > data.size())
|
||||
return {};
|
||||
|
||||
return Util::make_handle<Granite::FileMapping>(
|
||||
reference_from_this(), offset,
|
||||
data.data() + offset, range,
|
||||
0, range);
|
||||
}
|
||||
|
||||
Granite::FileMappingHandle map_write(size_t size) override
|
||||
{
|
||||
data.resize(size);
|
||||
return map_subset(0, size);
|
||||
}
|
||||
|
||||
void unmap(void *, size_t) override
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t get_size() override
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
void MemoryMappedTexture::make_local_copy()
|
||||
{
|
||||
if (empty())
|
||||
return;
|
||||
|
||||
auto new_file = Util::make_handle<ScratchFile>(mapped, get_required_size());
|
||||
file = new_file->map();
|
||||
mapped = file->mutable_data<uint8_t>();
|
||||
layout.set_buffer(mapped + sizeof(MemoryMappedHeader),
|
||||
get_required_size() - sizeof(MemoryMappedHeader));
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_write_scratch()
|
||||
{
|
||||
if (layout.get_required_size() == 0)
|
||||
return false;
|
||||
|
||||
auto new_file = Util::make_handle<ScratchFile>(nullptr, get_required_size());
|
||||
if (new_file->get_size() < sizeof(MemoryMappedHeader))
|
||||
return false;
|
||||
|
||||
auto new_mapped = new_file->map_write(get_required_size());
|
||||
return map_write(std::move(new_mapped));
|
||||
}
|
||||
|
||||
size_t MemoryMappedTexture::get_required_size() const
|
||||
{
|
||||
return layout.get_required_size() + sizeof(MemoryMappedHeader);
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::set_swizzle(const VkComponentMapping &swizzle_)
|
||||
{
|
||||
swizzle = swizzle_;
|
||||
}
|
||||
|
||||
static void remap(VkComponentSwizzle &output, VkComponentSwizzle input,
|
||||
const VkComponentMapping &mapping, VkComponentSwizzle identity)
|
||||
{
|
||||
if (input == VK_COMPONENT_SWIZZLE_IDENTITY)
|
||||
input = identity;
|
||||
|
||||
switch (input)
|
||||
{
|
||||
case VK_COMPONENT_SWIZZLE_R:
|
||||
output = mapping.r;
|
||||
break;
|
||||
|
||||
case VK_COMPONENT_SWIZZLE_G:
|
||||
output = mapping.g;
|
||||
break;
|
||||
|
||||
case VK_COMPONENT_SWIZZLE_B:
|
||||
output = mapping.b;
|
||||
break;
|
||||
|
||||
case VK_COMPONENT_SWIZZLE_A:
|
||||
output = mapping.a;
|
||||
break;
|
||||
|
||||
case VK_COMPONENT_SWIZZLE_ONE:
|
||||
case VK_COMPONENT_SWIZZLE_ZERO:
|
||||
output = input;
|
||||
break;
|
||||
|
||||
default:
|
||||
output = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryMappedTexture::remap_swizzle(VkComponentMapping &mapping) const
|
||||
{
|
||||
VkComponentMapping new_mapping;
|
||||
|
||||
remap(new_mapping.r, swizzle.r, mapping, VK_COMPONENT_SWIZZLE_R);
|
||||
remap(new_mapping.g, swizzle.g, mapping, VK_COMPONENT_SWIZZLE_G);
|
||||
remap(new_mapping.b, swizzle.b, mapping, VK_COMPONENT_SWIZZLE_B);
|
||||
remap(new_mapping.a, swizzle.a, mapping, VK_COMPONENT_SWIZZLE_A);
|
||||
mapping = new_mapping;
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_copy(const void *mapped_, size_t size)
|
||||
{
|
||||
auto new_file = Util::make_handle<ScratchFile>(mapped_, size);
|
||||
if (new_file->get_size() < sizeof(MemoryMappedHeader))
|
||||
return false;
|
||||
|
||||
auto new_mapped = new_file->map();
|
||||
return map_read(std::move(new_mapped));
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_read(Granite::FileMappingHandle new_file)
|
||||
{
|
||||
file = std::move(new_file);
|
||||
mapped = const_cast<uint8_t *>(file->data<uint8_t>());
|
||||
|
||||
auto *header = reinterpret_cast<const MemoryMappedHeader *>(mapped);
|
||||
switch (header->type)
|
||||
{
|
||||
case VK_IMAGE_TYPE_1D:
|
||||
layout.set_1d(header->format, header->width, header->layers, header->levels);
|
||||
break;
|
||||
|
||||
case VK_IMAGE_TYPE_2D:
|
||||
layout.set_2d(header->format, header->width, header->height, header->layers, header->levels);
|
||||
break;
|
||||
|
||||
case VK_IMAGE_TYPE_3D:
|
||||
layout.set_3d(header->format, header->width, header->height, header->depth, header->levels);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
cube = (header->flags & MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT) != 0;
|
||||
mipgen_on_load = (header->flags & MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT) != 0;
|
||||
swizzle.r = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
|
||||
swizzle.g = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
|
||||
swizzle.b = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
|
||||
swizzle.a = static_cast<VkComponentSwizzle>((header->flags >> MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT) & MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK);
|
||||
|
||||
if ((layout.get_required_size() + sizeof(MemoryMappedHeader)) < file->get_size())
|
||||
return false;
|
||||
if (header->payload_size != layout.get_required_size())
|
||||
return false;
|
||||
|
||||
layout.set_buffer(static_cast<uint8_t *>(mapped) + sizeof(MemoryMappedHeader), header->payload_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::map_read(Granite::Filesystem &fs, const std::string &path)
|
||||
{
|
||||
auto loaded_file = fs.open(path, Granite::FileMode::ReadOnly);
|
||||
if (!loaded_file)
|
||||
return false;
|
||||
|
||||
if (loaded_file->get_size() < sizeof(MemoryMappedHeader))
|
||||
return false;
|
||||
|
||||
auto new_mapped = loaded_file->map();
|
||||
if (!new_mapped)
|
||||
return false;
|
||||
|
||||
return map_read(std::move(new_mapped));
|
||||
}
|
||||
|
||||
bool MemoryMappedTexture::is_header(const void *mapped_, size_t size)
|
||||
{
|
||||
if (size < sizeof(MemoryMappedHeader))
|
||||
return false;
|
||||
return memcmp(mapped_, MAGIC, sizeof(MAGIC)) == 0;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/* Copyright (c) 2017-2026 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 "texture_format.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
enum MemoryMappedTextureFlagBits
|
||||
{
|
||||
MEMORY_MAPPED_TEXTURE_CUBE_MAP_COMPATIBLE_BIT = 1 << 0,
|
||||
MEMORY_MAPPED_TEXTURE_GENERATE_MIPMAP_ON_LOAD_BIT = 1 << 1,
|
||||
MEMORY_MAPPED_TEXTURE_SWIZZLE_R_SHIFT = 16,
|
||||
MEMORY_MAPPED_TEXTURE_SWIZZLE_G_SHIFT = 19,
|
||||
MEMORY_MAPPED_TEXTURE_SWIZZLE_B_SHIFT = 22,
|
||||
MEMORY_MAPPED_TEXTURE_SWIZZLE_A_SHIFT = 25,
|
||||
MEMORY_MAPPED_TEXTURE_SWIZZLE_MASK = 0x7
|
||||
};
|
||||
using MemoryMappedTextureFlags = uint32_t;
|
||||
|
||||
class MemoryMappedTexture
|
||||
{
|
||||
public:
|
||||
void set_1d(VkFormat format, uint32_t width, uint32_t layers = 1, uint32_t levels = 1);
|
||||
void set_2d(VkFormat format, uint32_t width, uint32_t height, uint32_t layers = 1, uint32_t levels = 1);
|
||||
void set_3d(VkFormat format, uint32_t width, uint32_t height, uint32_t depth, uint32_t levels = 1);
|
||||
void set_cube(VkFormat format, uint32_t size, uint32_t cube_layers = 1, uint32_t levels = 1);
|
||||
|
||||
static bool is_header(const void *mapped, size_t size);
|
||||
|
||||
bool map_write(Granite::Filesystem &fs, const std::string &path);
|
||||
bool map_write(Granite::FileMappingHandle file);
|
||||
bool map_read(Granite::Filesystem &fs, const std::string &path);
|
||||
bool map_read(Granite::FileMappingHandle file);
|
||||
bool map_copy(const void *mapped, size_t size);
|
||||
bool map_write_scratch();
|
||||
bool copy_to_path(Granite::Filesystem &fs, const std::string &path);
|
||||
void make_local_copy();
|
||||
|
||||
inline const Vulkan::TextureFormatLayout &get_layout() const
|
||||
{
|
||||
return layout;
|
||||
}
|
||||
|
||||
void set_generate_mipmaps_on_load(bool enable = true);
|
||||
|
||||
MemoryMappedTextureFlags get_flags() const;
|
||||
void set_flags(MemoryMappedTextureFlags flags);
|
||||
|
||||
size_t get_required_size() const;
|
||||
void set_swizzle(const VkComponentMapping &swizzle);
|
||||
|
||||
void remap_swizzle(VkComponentMapping &mapping) const;
|
||||
|
||||
inline bool empty() const
|
||||
{
|
||||
return get_layout().get_required_size() == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Vulkan::TextureFormatLayout layout;
|
||||
Granite::FileMappingHandle file;
|
||||
uint8_t *mapped = nullptr;
|
||||
bool cube = false;
|
||||
bool mipgen_on_load = false;
|
||||
VkComponentMapping swizzle = {
|
||||
VK_COMPONENT_SWIZZLE_R,
|
||||
VK_COMPONENT_SWIZZLE_G,
|
||||
VK_COMPONENT_SWIZZLE_B,
|
||||
VK_COMPONENT_SWIZZLE_A,
|
||||
};
|
||||
};
|
||||
}
|
||||
+1425
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
||||
/* Copyright (c) 2017-2026 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 "texture_format.hpp"
|
||||
#include "command_buffer.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace Granite
|
||||
{
|
||||
Vulkan::ImageHandle decode_compressed_image(Vulkan::CommandBuffer &cmd, const Vulkan::TextureFormatLayout &layout,
|
||||
VkFormat preferred_decode_format,
|
||||
const VkComponentMapping &swizzle = {
|
||||
VK_COMPONENT_SWIZZLE_R,
|
||||
VK_COMPONENT_SWIZZLE_G,
|
||||
VK_COMPONENT_SWIZZLE_B,
|
||||
VK_COMPONENT_SWIZZLE_A,
|
||||
});
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/* Copyright (c) 2017-2026 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_files.hpp"
|
||||
#include "stb_image.h"
|
||||
#include "filesystem.hpp"
|
||||
#include "muglm/muglm_impl.hpp"
|
||||
#include <string.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
static MemoryMappedTexture load_stb(const void *data, size_t size, ColorSpace color)
|
||||
{
|
||||
int width, height;
|
||||
int components;
|
||||
auto *buffer = stbi_load_from_memory(static_cast<const stbi_uc *>(data), size, &width, &height, &components, 4);
|
||||
|
||||
if (!buffer)
|
||||
return {};
|
||||
|
||||
MemoryMappedTexture tex;
|
||||
tex.set_2d(color == ColorSpace::sRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM, width, height);
|
||||
tex.set_generate_mipmaps_on_load(true);
|
||||
if (!tex.map_write_scratch())
|
||||
return {};
|
||||
|
||||
memcpy(tex.get_layout().data(), buffer, width * height * 4);
|
||||
stbi_image_free(buffer);
|
||||
return tex;
|
||||
}
|
||||
|
||||
static MemoryMappedTexture load_hdr(const void *data, size_t size)
|
||||
{
|
||||
int width, height;
|
||||
int components;
|
||||
auto *buffer = stbi_loadf_from_memory(static_cast<const stbi_uc *>(data), size, &width, &height, &components, 3);
|
||||
|
||||
MemoryMappedTexture tex;
|
||||
tex.set_2d(VK_FORMAT_R16G16B16A16_SFLOAT, width, height);
|
||||
if (!tex.map_write_scratch())
|
||||
return {};
|
||||
tex.set_generate_mipmaps_on_load(true);
|
||||
|
||||
auto *converted = static_cast<muglm::u16vec4 *>(tex.get_layout().data());
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
converted[i] = muglm::floatToHalf(muglm::vec4(buffer[3 * i + 0], buffer[3 * i + 1], buffer[3 * i + 2], 1.0f));
|
||||
}
|
||||
stbi_image_free(buffer);
|
||||
return tex;
|
||||
}
|
||||
|
||||
MemoryMappedTexture load_texture_from_memory(const void *data, size_t size, ColorSpace color)
|
||||
{
|
||||
static const uint8_t png_magic[] = {
|
||||
0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a,
|
||||
};
|
||||
|
||||
static const uint8_t jpg_magic[] = {
|
||||
0xff, 0xd8,
|
||||
};
|
||||
|
||||
static const uint8_t hdr_magic[] = {
|
||||
0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x0a,
|
||||
};
|
||||
|
||||
if (size >= sizeof(png_magic) && memcmp(data, png_magic, sizeof(png_magic)) == 0)
|
||||
return load_stb(data, size, color);
|
||||
else if (size >= 2 && memcmp(data, jpg_magic, sizeof(jpg_magic)) == 0)
|
||||
return load_stb(data, size, color);
|
||||
else if (size >= sizeof(hdr_magic) && memcmp(data, hdr_magic, sizeof(hdr_magic)) == 0)
|
||||
return load_hdr(data, size);
|
||||
else if (MemoryMappedTexture::is_header(data, size))
|
||||
{
|
||||
MemoryMappedTexture mapped;
|
||||
mapped.map_copy(data, size);
|
||||
return mapped;
|
||||
}
|
||||
else
|
||||
{
|
||||
// YOLO!
|
||||
return load_stb(data, size, color);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryMappedTexture load_texture_from_file(Granite::Filesystem &fs, const std::string &path, ColorSpace color)
|
||||
{
|
||||
auto file = fs.open(path, Granite::FileMode::ReadOnly);
|
||||
if (!file)
|
||||
return {};
|
||||
|
||||
auto mapped = file->map();
|
||||
if (!mapped)
|
||||
return {};
|
||||
|
||||
if (MemoryMappedTexture::is_header(mapped->data(), mapped->get_size()))
|
||||
{
|
||||
MemoryMappedTexture tex;
|
||||
tex.map_read(std::move(mapped));
|
||||
return tex;
|
||||
}
|
||||
|
||||
return load_texture_from_memory(mapped->data(), mapped->get_size(), color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Copyright (c) 2017-2026 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 "format.hpp"
|
||||
#include "memory_mapped_texture.hpp"
|
||||
|
||||
namespace Granite
|
||||
{
|
||||
class Filesystem;
|
||||
}
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
enum class ColorSpace
|
||||
{
|
||||
Linear,
|
||||
sRGB
|
||||
};
|
||||
|
||||
MemoryMappedTexture load_texture_from_file(Granite::Filesystem &fs, const std::string &path, ColorSpace color = ColorSpace::sRGB);
|
||||
MemoryMappedTexture load_texture_from_memory(const void *data, size_t size,
|
||||
ColorSpace color = ColorSpace::sRGB);
|
||||
}
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
/* Copyright (c) 2017-2026 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.
|
||||
*/
|
||||
|
||||
#define NOMINMAX
|
||||
#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-2026 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,117 @@
|
||||
/* Copyright (c) 2017-2026 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_ATTACHMENT_OPTIMAL:
|
||||
return "ATTACHMENT";
|
||||
case VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL:
|
||||
return "READ_ONLY";
|
||||
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,141 @@
|
||||
/* Copyright (c) 2017-2026 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_OPAQUE_WIN32_KMT_BIT ||
|
||||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT ||
|
||||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT ||
|
||||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT ||
|
||||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT ||
|
||||
type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT);
|
||||
|
||||
return type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT &&
|
||||
type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT &&
|
||||
type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT &&
|
||||
type != VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||
}
|
||||
|
||||
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_OPAQUE_WIN32_KMT_BIT ||
|
||||
type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT ||
|
||||
type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_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 &&
|
||||
type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT &&
|
||||
type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static inline const T *find_pnext(const void *pnext, VkStructureType sType)
|
||||
{
|
||||
auto *chain = static_cast<const VkBaseInStructure *>(pnext);
|
||||
while (chain)
|
||||
{
|
||||
if (chain->sType == sType)
|
||||
break;
|
||||
chain = static_cast<const VkBaseInStructure *>(chain->pNext);
|
||||
}
|
||||
return reinterpret_cast<const T *>(chain);
|
||||
}
|
||||
|
||||
struct BufferMarkerHandle
|
||||
{
|
||||
enum : uint32_t { Invalid = UINT32_MAX };
|
||||
uint32_t index = Invalid;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/* Copyright (c) 2017-2026 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-2026 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,576 @@
|
||||
/* Copyright (c) 2017-2026 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 "wsi_pacer.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();
|
||||
virtual void begin_soft_keyboard(const std::string &initial);
|
||||
virtual void end_soft_keyboard();
|
||||
|
||||
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,
|
||||
scRGB,
|
||||
Custom
|
||||
};
|
||||
|
||||
struct PresentationStats
|
||||
{
|
||||
// Correlate with WSI::get_last_submitted_present_id() + 1.
|
||||
uint64_t feedback_present_id;
|
||||
|
||||
// QUEUE_COMPLETE query. May be 0 if implementation does not support it.
|
||||
// Application can trivially implement this on its own if needed.
|
||||
uint64_t gpu_done_ts;
|
||||
|
||||
// This is the latest stage that is reported.
|
||||
uint64_t present_done_ts;
|
||||
|
||||
// actual presented - intended target presentation.
|
||||
// Intended target presentation may be adjusted internally,
|
||||
// especially when emulating absolute over relative and vice versa.
|
||||
int64_t error;
|
||||
};
|
||||
|
||||
enum class RefreshMode { Unknown, FRR, VRR };
|
||||
|
||||
struct RefreshRateInfo
|
||||
{
|
||||
RefreshMode mode;
|
||||
uint64_t refresh_duration;
|
||||
uint64_t refresh_interval;
|
||||
};
|
||||
|
||||
class WSI
|
||||
{
|
||||
public:
|
||||
WSI();
|
||||
void set_platform(WSIPlatform *platform);
|
||||
void set_present_mode(PresentMode mode);
|
||||
void set_backbuffer_format(BackbufferFormat format);
|
||||
// This is mostly for debug/development, ignores exposed formats and forces it.
|
||||
void set_custom_backbuffer_format(VkSurfaceFormatKHR format);
|
||||
|
||||
struct ImageCompression
|
||||
{
|
||||
VkImageCompressionFlagBitsEXT type = VK_IMAGE_COMPRESSION_DEFAULT_EXT;
|
||||
VkImageCompressionFixedRateFlagsEXT fixed_rates = 0;
|
||||
};
|
||||
void set_image_compression_control(const ImageCompression &compression);
|
||||
|
||||
// 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_present_low_latency_mode(bool enable);
|
||||
// Engages NV_low_latency2 / AMD_anti_lag, etc, which aim to reduce CPU <-> GPU submit delays.
|
||||
void set_gpu_submit_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();
|
||||
|
||||
// Signals that the next present is merely a dupe (or generated) of a previous one,
|
||||
// and that frame should not participate in present wait.
|
||||
void set_next_present_is_duplicated();
|
||||
|
||||
// If true, and present wait is supported, the implementation will use more swapchain images than normal,
|
||||
// and make it feasible to render duplicate frames without needlessly draining the GPU of work.
|
||||
// This is mostly just a thing for an emulator which may be outputting at 30 unique FPS, but at 60 VI/s,
|
||||
// meaning the same frames is duplicated.
|
||||
// With a large enough image count, can feasibly be used for multi-frame-gen or similar shenanigans.
|
||||
void set_frame_duplication_aware(bool enable, uint32_t target_swapchain_images = 5);
|
||||
|
||||
// Overrides the present wait latency.
|
||||
// 0 -> waits until last frame was presented before polling input. Hardest low-latency situation.
|
||||
// Forced when low-latency present is enabled.
|
||||
// 1 -> Normal ideal situation where we wait for previous frame's present to complete.
|
||||
// Allows 1 frame worth of work for CPU to keep GPU fed properly.
|
||||
// 2 -> Conservative, but useful when GPU bound to avoid pumping. Forced when using low gpu submit latency path.
|
||||
void set_present_wait_latency(uint32_t latency);
|
||||
|
||||
// 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;
|
||||
|
||||
bool get_presentation_stats(PresentationStats &stats) const;
|
||||
bool get_refresh_rate_info(RefreshRateInfo &info) const;
|
||||
uint64_t get_last_submitted_present_id() const;
|
||||
|
||||
// Absolute time is in terms of the Util::get_current_time_nsec() domain,
|
||||
// i.e., time reported in presentation stats.
|
||||
//
|
||||
// For absolute time, the image must not be presented before the time given.
|
||||
// For relative time, the time is intended to be abs_time = last_present + rel_time.
|
||||
//
|
||||
// If relative time is supported by implementation and relative_time_ns is not 0,
|
||||
// absolute time requests are ignored. If absolute time is supported by implementation,
|
||||
// a target absolute time will be computed based on absolute_time_ns and relative_time_ns.
|
||||
//
|
||||
// Rounding adjustments are allowed when the absolute time aligns closely
|
||||
// with a refresh cycle, up to half a refresh cycle for FRR. For VRR, no adjustments are made.
|
||||
//
|
||||
// If this returns true, the request is expected to work as intended.
|
||||
// If false, either it's unsupported, or we're not yet in a steady state where
|
||||
// presentation timing reliably work (the first few frames after a swapchain is created may hit this).
|
||||
// This state remains set until another request is made.
|
||||
// For relative timings, this will work as expected as a method to set absolute target automatically,
|
||||
// but absolute time obviously will not, since application is expected to set a new target time every frame.
|
||||
// If force_vrr is true, the relative or absolute times are passed down with no bias.
|
||||
bool set_target_presentation_time(uint64_t absolute_time_ns, uint64_t relative_time_ns, bool force_vrr);
|
||||
void set_enable_timing_feedback(bool enable);
|
||||
|
||||
FixedRefreshRatePacer &get_fixed_rate_pacer();
|
||||
void set_fixed_rate_low_latency_pacer(bool enable);
|
||||
|
||||
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;
|
||||
FixedRefreshRatePacer frr_pacer;
|
||||
bool frr_pacer_enable = false;
|
||||
|
||||
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_present = false;
|
||||
bool low_latency_mode_enable_gpu_submit = false;
|
||||
bool low_latency_anti_lag_present_valid = false;
|
||||
|
||||
void emit_marker_pre_present();
|
||||
void emit_marker_post_present();
|
||||
void emit_end_of_frame_markers();
|
||||
|
||||
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;
|
||||
ImageCompression current_compression, compression;
|
||||
|
||||
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;
|
||||
VkSurfaceFormatKHR current_custom_backbuffer_format = {};
|
||||
VkSurfaceFormatKHR custom_backbuffer_format = {};
|
||||
|
||||
bool has_backbuffer_format_delta() const;
|
||||
|
||||
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 next_present_id = 1;
|
||||
uint64_t present_last_id = 0;
|
||||
unsigned present_frame_latency = 0;
|
||||
bool supports_present_wait2 = false;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPresentStageFlagsEXT feedback;
|
||||
bool absolute;
|
||||
bool relative;
|
||||
} supports_present_timing = {};
|
||||
|
||||
struct CalibratedTimestamp
|
||||
{
|
||||
uint64_t host_time;
|
||||
uint64_t stage_times[4];
|
||||
};
|
||||
|
||||
struct ErrorStats
|
||||
{
|
||||
uint64_t present_id;
|
||||
uint64_t target_absolute;
|
||||
int64_t compensation;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
// Used for setting target time.
|
||||
VkPresentStageFlagsEXT present_stage;
|
||||
uint64_t reference_time;
|
||||
VkTimeDomainKHR time_domain;
|
||||
uint64_t time_domain_id;
|
||||
|
||||
// Target
|
||||
uint64_t target_absolute_time;
|
||||
uint64_t target_relative_time;
|
||||
uint64_t last_absolute_target_time;
|
||||
bool force_vrr;
|
||||
|
||||
// Feedback.
|
||||
uint64_t gpu_done_host_time;
|
||||
uint64_t present_done_host_time;
|
||||
uint64_t present_id;
|
||||
|
||||
// Display refresh rate information.
|
||||
uint64_t refresh_duration;
|
||||
uint64_t refresh_interval;
|
||||
uint64_t refresh_counter;
|
||||
bool has_refresh_feedback;
|
||||
RefreshMode refresh_mode = RefreshMode::Unknown;
|
||||
|
||||
// Calibration information.
|
||||
uint64_t time_domain_counter;
|
||||
bool has_time_domain_props;
|
||||
Util::SmallVector<VkTimeDomainKHR> time_domains;
|
||||
Util::SmallVector<uint64_t> time_domain_ids;
|
||||
Util::SmallVector<CalibratedTimestamp> calibration;
|
||||
|
||||
bool need_recalibration;
|
||||
int64_t last_recalibration_time;
|
||||
|
||||
int64_t presentation_time_error;
|
||||
int64_t pending_compensation;
|
||||
Util::SmallVector<ErrorStats, 16> error_stats;
|
||||
} present_timing = {};
|
||||
|
||||
bool present_feedback_enable = false;
|
||||
|
||||
void update_present_timing_properties();
|
||||
void poll_present_timing_feedback();
|
||||
void recalibrate_present_timing_domains();
|
||||
void update_time_domain_properties();
|
||||
void set_present_timing_request(VkPresentTimingInfoEXT &timing);
|
||||
|
||||
Semaphore low_latency_semaphore;
|
||||
uint64_t low_latency_semaphore_value = 0;
|
||||
|
||||
bool next_present_is_dupe = false;
|
||||
bool frame_dupe_aware = false;
|
||||
bool current_frame_dupe_aware = false;
|
||||
unsigned frame_dupe_target_images = 5;
|
||||
unsigned current_frame_dupe_target_images = 5;
|
||||
unsigned duplicated_frames = 0;
|
||||
unsigned last_duplicated_frames = 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 };
|
||||
bool valid_hdr_metadata = false;
|
||||
|
||||
struct DeferredDeletionSwapchain
|
||||
{
|
||||
VkSwapchainKHR swapchain;
|
||||
Fence fence;
|
||||
};
|
||||
|
||||
struct DeferredDeletionSemaphore
|
||||
{
|
||||
Semaphore semaphore;
|
||||
Fence fence;
|
||||
};
|
||||
|
||||
Util::SmallVector<DeferredDeletionSwapchain> deferred_swapchains;
|
||||
Util::SmallVector<DeferredDeletionSemaphore> deferred_semaphore;
|
||||
Vulkan::Fence last_present_fence;
|
||||
void nonblock_delete_swapchain_resources();
|
||||
|
||||
VkSurfaceFormatKHR find_suitable_present_format(const std::vector<VkSurfaceFormatKHR> &formats, BackbufferFormat desired_format) const;
|
||||
|
||||
VkResult wait_for_present(uint64_t id, uint64_t timeout = UINT64_MAX);
|
||||
|
||||
#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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
/* Copyright (c) 2017-2026 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 "wsi_dxgi.hpp"
|
||||
#include <windows.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
DXGIInteropSwapchain::~DXGIInteropSwapchain()
|
||||
{
|
||||
// Wait-for-idle before teardown.
|
||||
if (fence)
|
||||
fence->SetEventOnCompletion(fence_value, nullptr);
|
||||
if (latency_handle)
|
||||
CloseHandle(latency_handle);
|
||||
}
|
||||
|
||||
static bool is_running_on_wine()
|
||||
{
|
||||
// If we're running in Wine for whatever reason, interop like this is completely useless.
|
||||
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
||||
return !ntdll || GetProcAddress(ntdll, "wine_get_version");
|
||||
}
|
||||
|
||||
static bool is_running_in_tool(Device &device)
|
||||
{
|
||||
auto &ext = device.get_device_features();
|
||||
if (ext.supports_tooling_info && vkGetPhysicalDeviceToolPropertiesEXT)
|
||||
{
|
||||
auto gpu = device.get_physical_device();
|
||||
uint32_t count = 0;
|
||||
vkGetPhysicalDeviceToolPropertiesEXT(gpu, &count, nullptr);
|
||||
Util::SmallVector<VkPhysicalDeviceToolPropertiesEXT> tool_props(count);
|
||||
for (auto &t : tool_props)
|
||||
t = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT };
|
||||
vkGetPhysicalDeviceToolPropertiesEXT(gpu, &count, tool_props.data());
|
||||
|
||||
// It's okay for validation to not force this path. We're mostly concerned with RenderDoc, RGP and Nsight.
|
||||
for (auto &t : tool_props)
|
||||
if (t.purposes & (VK_TOOL_PURPOSE_PROFILING_BIT | VK_TOOL_PURPOSE_TRACING_BIT))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::init_interop_device(Device &vk_device_)
|
||||
{
|
||||
vk_device = &vk_device_;
|
||||
|
||||
// If we're running in Wine for whatever reason, interop like this is more harmful than good.
|
||||
if (is_running_on_wine())
|
||||
return false;
|
||||
|
||||
// If we're running in some capture tool, we need to use Vulkan WSI to avoid confusing it.
|
||||
if (is_running_in_tool(*vk_device))
|
||||
return false;
|
||||
|
||||
if (!vk_device->get_device_features().vk11_props.deviceLUIDValid)
|
||||
return false;
|
||||
|
||||
d3d12_lib = Util::DynamicLibrary("d3d12.dll");
|
||||
dxgi_lib = Util::DynamicLibrary("dxgi.dll");
|
||||
|
||||
if (!d3d12_lib)
|
||||
{
|
||||
LOGE("Failed to find d3d12.dll. Ignoring interop device.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dxgi_lib)
|
||||
{
|
||||
LOGE("Failed to find dxgi.dll. Ignoring interop device.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pfn_CreateDXGIFactory1 =
|
||||
dxgi_lib.get_symbol<decltype(&CreateDXGIFactory1)>("CreateDXGIFactory1");
|
||||
auto pfn_D3D12CreateDevice =
|
||||
d3d12_lib.get_symbol<decltype(&D3D12CreateDevice)>("D3D12CreateDevice");
|
||||
|
||||
if (!pfn_CreateDXGIFactory1 || !pfn_D3D12CreateDevice)
|
||||
{
|
||||
LOGE("Failed to find entry points.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = pfn_CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory))))
|
||||
{
|
||||
LOGE("Failed to create DXGI factory, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
LUID luid = {};
|
||||
ComPtr<IDXGIAdapter> adapter;
|
||||
memcpy(&luid, vk_device->get_device_features().vk11_props.deviceLUID, VK_LUID_SIZE);
|
||||
if (FAILED(hr = dxgi_factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(&adapter))))
|
||||
{
|
||||
LOGE("Failed to enumerate DXGI adapter by LUID.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(hr = pfn_D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device))))
|
||||
{
|
||||
LOGE("Failed to create D3D12Device, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12_COMMAND_QUEUE_DESC queue_desc = {};
|
||||
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
if (FAILED(hr = device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&queue))))
|
||||
{
|
||||
LOGE("Failed to create command queue, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(hr = device->CreateCommandList1(
|
||||
0, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&list))))
|
||||
{
|
||||
LOGE("Failed to create command list, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(hr = device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&fence))))
|
||||
{
|
||||
LOGE("Failed to create shared fence, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Import D3D12 timeline into Vulkan.
|
||||
// Other way around is not as well-supported.
|
||||
vk_fence = vk_device->request_semaphore_external(
|
||||
VK_SEMAPHORE_TYPE_TIMELINE, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT);
|
||||
if (!vk_fence)
|
||||
{
|
||||
LOGE("Failed to create timeline.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ExternalHandle fence_handle;
|
||||
fence_handle.semaphore_handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT;
|
||||
if (FAILED(device->CreateSharedHandle(fence.Get(), nullptr,
|
||||
GENERIC_ALL, nullptr, &fence_handle.handle)))
|
||||
{
|
||||
LOGE("Failed to create shared fence handle.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!vk_fence->import_from_handle(fence_handle))
|
||||
{
|
||||
LOGE("Failed to import timeline.\n");
|
||||
CloseHandle(fence_handle.handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImage DXGIInteropSwapchain::get_vulkan_image() const
|
||||
{
|
||||
return vulkan_backbuffer->get_image();
|
||||
}
|
||||
|
||||
static DXGI_FORMAT convert_vk_format(VkFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
// D3D12 fails to create SRGB swapchain for some reason.
|
||||
// We'll import the memory as sRGB however, and it works fine ...
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||
return DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static DXGI_COLOR_SPACE_TYPE convert_vk_color_space(VkColorSpaceKHR colspace)
|
||||
{
|
||||
switch (colspace)
|
||||
{
|
||||
case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
|
||||
return DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
|
||||
case VK_COLOR_SPACE_HDR10_ST2084_EXT:
|
||||
return DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
|
||||
case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
|
||||
return DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
|
||||
default:
|
||||
return DXGI_COLOR_SPACE_RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
void DXGIInteropSwapchain::reset_backbuffer_state()
|
||||
{
|
||||
for (auto &buf : backbuffers)
|
||||
if (fence)
|
||||
fence->SetEventOnCompletion(buf.wait_fence_value, nullptr);
|
||||
backbuffers.clear();
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::setup_per_frame_state(PerFrameState &state, unsigned index)
|
||||
{
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = swapchain->GetBuffer(index, IID_PPV_ARGS(&state.backbuffer))))
|
||||
{
|
||||
LOGE("Failed to get backbuffer, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||
IID_PPV_ARGS(&state.allocator))))
|
||||
{
|
||||
LOGE("Failed to create command allocator, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::init_swapchain(HWND hwnd_, VkSurfaceFormatKHR format,
|
||||
unsigned width, unsigned height, unsigned count)
|
||||
{
|
||||
if (hwnd && hwnd_ != hwnd)
|
||||
{
|
||||
reset_backbuffer_state();
|
||||
swapchain.Reset();
|
||||
}
|
||||
|
||||
hwnd = hwnd_;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 desc = {};
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.BufferCount = count;
|
||||
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
desc.Format = convert_vk_format(format.format);
|
||||
if (!desc.Format)
|
||||
return false;
|
||||
|
||||
auto color_space = convert_vk_color_space(format.colorSpace);
|
||||
if (color_space == DXGI_COLOR_SPACE_RESERVED)
|
||||
return false;
|
||||
|
||||
BOOL allow_tear = FALSE;
|
||||
if (SUCCEEDED(dxgi_factory->CheckFeatureSupport(
|
||||
DXGI_FEATURE_PRESENT_ALLOW_TEARING,
|
||||
&allow_tear, sizeof(allow_tear)) && allow_tear))
|
||||
{
|
||||
desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
allow_tearing = true;
|
||||
}
|
||||
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||
|
||||
ComPtr<IDXGISwapChain1> swap;
|
||||
HRESULT hr;
|
||||
|
||||
reset_backbuffer_state();
|
||||
|
||||
// If we already have a swapchain we can just use ResizeBuffers.
|
||||
if (!swapchain)
|
||||
{
|
||||
if (FAILED(hr = dxgi_factory->CreateSwapChainForHwnd(
|
||||
queue.Get(), hwnd, &desc, nullptr, nullptr, &swap)))
|
||||
{
|
||||
LOGE("Failed to create swapchain, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
completed_presents = 0;
|
||||
completed_waits = 0;
|
||||
|
||||
if (FAILED(swap.As(&swapchain)))
|
||||
{
|
||||
LOGE("Failed to query swapchain interface.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (latency_handle)
|
||||
CloseHandle(latency_handle);
|
||||
latency_handle = swapchain->GetFrameLatencyWaitableObject();
|
||||
|
||||
if (!latency_handle)
|
||||
{
|
||||
LOGE("Failed to query latency handle.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Drop semaphore to 0 right away to make code less awkward later.
|
||||
if (WaitForSingleObject(latency_handle, INFINITE) != WAIT_OBJECT_0)
|
||||
{
|
||||
LOGE("Failed to wait for latency object.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FAILED(hr = swapchain->ResizeBuffers(count, width, height, desc.Format, desc.Flags)))
|
||||
{
|
||||
LOGE("Failed to resize buffers, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(dxgi_factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES)))
|
||||
{
|
||||
LOGE("Failed to make window association.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
surface_format = format;
|
||||
|
||||
UINT space_support = 0;
|
||||
if (FAILED(swapchain->CheckColorSpaceSupport(color_space, &space_support)) ||
|
||||
((space_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) == 0))
|
||||
{
|
||||
// Fallback to SDR if HDR doesn't pass check.
|
||||
if (FAILED(swapchain->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, &space_support)) ||
|
||||
((space_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGW("HDR10 not supported by DXGI swapchain, falling back to SDR.\n");
|
||||
surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
color_space = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
|
||||
}
|
||||
|
||||
if (FAILED(swapchain->SetColorSpace1(color_space)))
|
||||
{
|
||||
LOGE("Failed to set color space.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
backbuffers.resize(desc.BufferCount);
|
||||
for (unsigned i = 0; i < desc.BufferCount; i++)
|
||||
if (!setup_per_frame_state(backbuffers[i], i))
|
||||
return false;
|
||||
|
||||
ExternalHandle imported_image;
|
||||
imported_image.memory_handle_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT;
|
||||
|
||||
D3D12_RESOURCE_DESC blit_desc = {};
|
||||
blit_desc.Width = width;
|
||||
blit_desc.Height = height;
|
||||
blit_desc.Format = desc.Format;
|
||||
blit_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
||||
blit_desc.SampleDesc.Count = 1;
|
||||
blit_desc.DepthOrArraySize = 1;
|
||||
blit_desc.MipLevels = 1;
|
||||
blit_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
||||
blit_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
|
||||
D3D12_HEAP_PROPERTIES heap_props = {};
|
||||
heap_props.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
|
||||
blit_backbuffer.Reset();
|
||||
if (FAILED(hr = device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_SHARED, &blit_desc,
|
||||
D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&blit_backbuffer))))
|
||||
{
|
||||
LOGE("Failed to create blit render target, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(hr = device->CreateSharedHandle(blit_backbuffer.Get(), nullptr, GENERIC_ALL, nullptr,
|
||||
&imported_image.handle)))
|
||||
{
|
||||
LOGE("Failed to create shared handle, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto image_info = ImageCreateInfo::render_target(width, height, format.format);
|
||||
image_info.initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
image_info.misc = IMAGE_MISC_EXTERNAL_MEMORY_BIT;
|
||||
image_info.external = imported_image;
|
||||
|
||||
vulkan_backbuffer = vk_device->create_image(image_info);
|
||||
if (!vulkan_backbuffer)
|
||||
{
|
||||
LOGE("Failed to create shared Vulkan image, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
vulkan_backbuffer->set_swapchain_layout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VkSurfaceFormatKHR DXGIInteropSwapchain::get_current_surface_format() const
|
||||
{
|
||||
return surface_format;
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::wait_latency(unsigned latency_frames)
|
||||
{
|
||||
uint64_t target_wait_count = completed_presents - latency_frames;
|
||||
|
||||
if (latency_handle && (target_wait_count & (1ull << 63)) == 0)
|
||||
{
|
||||
while (completed_waits < target_wait_count)
|
||||
{
|
||||
if (WaitForSingleObject(latency_handle, INFINITE) != WAIT_OBJECT_0)
|
||||
{
|
||||
LOGE("Failed to wait for latency object.\n");
|
||||
return false;
|
||||
}
|
||||
completed_waits++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::acquire(Semaphore &acquire_semaphore)
|
||||
{
|
||||
// AMD workaround. Driver freaks out if trying to wait for D3D12 timeline value of 0.
|
||||
queue->Signal(fence.Get(), ++fence_value);
|
||||
|
||||
acquire_semaphore = vk_device->request_timeline_semaphore_as_binary(*vk_fence, fence_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXGIInteropSwapchain::present(Vulkan::Semaphore release_semaphore, bool vsync)
|
||||
{
|
||||
unsigned index = swapchain->GetCurrentBackBufferIndex();
|
||||
auto &per_frame = backbuffers[index];
|
||||
|
||||
vk_device->add_wait_semaphore(CommandBuffer::Type::Generic, std::move(release_semaphore),
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, true);
|
||||
|
||||
auto cmd = vk_device->request_command_buffer();
|
||||
cmd->release_image_barrier(*vulkan_backbuffer,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0);
|
||||
|
||||
vk_device->submit(cmd);
|
||||
auto timeline_signal = vk_device->request_timeline_semaphore_as_binary(*vk_fence, ++fence_value);
|
||||
vk_device->submit_empty(CommandBuffer::Type::Generic, nullptr, timeline_signal.get());
|
||||
queue->Wait(fence.Get(), fence_value);
|
||||
|
||||
fence->SetEventOnCompletion(per_frame.wait_fence_value, nullptr);
|
||||
|
||||
if (FAILED(per_frame.allocator->Reset()))
|
||||
{
|
||||
LOGE("Failed to reset command allocator.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
list->Reset(per_frame.allocator.Get(), nullptr);
|
||||
|
||||
D3D12_RESOURCE_BARRIER barrier = {};
|
||||
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
barrier.Transition.pResource = per_frame.backbuffer.Get();
|
||||
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
|
||||
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
list->ResourceBarrier(1, &barrier);
|
||||
|
||||
D3D12_TEXTURE_COPY_LOCATION dst = {}, src = {};
|
||||
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
dst.pResource = per_frame.backbuffer.Get();
|
||||
src.pResource = blit_backbuffer.Get();
|
||||
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
|
||||
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
|
||||
list->ResourceBarrier(1, &barrier);
|
||||
|
||||
if (FAILED(list->Close()))
|
||||
{
|
||||
LOGE("Failed to close command list.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D12CommandList *cmdlist = list.Get();
|
||||
queue->ExecuteCommandLists(1, &cmdlist);
|
||||
queue->Signal(fence.Get(), ++fence_value);
|
||||
per_frame.wait_fence_value = fence_value;
|
||||
|
||||
HRESULT hr = swapchain->Present(vsync ? 1 : 0, !vsync && allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOGE("Failed to present, hr #%x.\n", unsigned(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
completed_presents++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Copyright (c) 2017-2026 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 "image.hpp"
|
||||
#include "d3d12.h"
|
||||
#include "dxgi1_6.h"
|
||||
#include "small_vector.hpp"
|
||||
#include "dynamic_library.hpp"
|
||||
#include <wrl.h>
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
template <typename T>
|
||||
using ComPtr = Microsoft::WRL::ComPtr<T>;
|
||||
|
||||
class DXGIInteropSwapchain
|
||||
{
|
||||
public:
|
||||
bool init_interop_device(Device &device);
|
||||
~DXGIInteropSwapchain();
|
||||
|
||||
bool init_swapchain(HWND hwnd, VkSurfaceFormatKHR format, unsigned width, unsigned height, unsigned count);
|
||||
VkImage get_vulkan_image() const;
|
||||
VkSurfaceFormatKHR get_current_surface_format() const;
|
||||
|
||||
bool acquire(Semaphore &acquire_semaphore);
|
||||
bool present(Semaphore release_semaphore, bool vsync);
|
||||
bool wait_latency(unsigned latency_frames);
|
||||
|
||||
private:
|
||||
Device *vk_device = nullptr;
|
||||
Util::DynamicLibrary d3d12_lib, dxgi_lib;
|
||||
HWND hwnd = nullptr;
|
||||
HANDLE latency_handle = nullptr;
|
||||
ComPtr<ID3D12Device4> device;
|
||||
ComPtr<ID3D12CommandQueue> queue;
|
||||
ComPtr<IDXGIFactory5> dxgi_factory;
|
||||
ComPtr<IDXGISwapChain3> swapchain;
|
||||
ComPtr<ID3D12GraphicsCommandList> list;
|
||||
ComPtr<ID3D12Fence> fence;
|
||||
Semaphore vk_fence;
|
||||
uint64_t fence_value = 0;
|
||||
VkSurfaceFormatKHR surface_format = {};
|
||||
bool allow_tearing = false;
|
||||
|
||||
struct PerFrameState
|
||||
{
|
||||
ComPtr<ID3D12CommandAllocator> allocator;
|
||||
ComPtr<ID3D12Resource> backbuffer;
|
||||
uint64_t wait_fence_value = 0;
|
||||
};
|
||||
Util::SmallVector<PerFrameState> backbuffers;
|
||||
ComPtr<ID3D12Resource> blit_backbuffer;
|
||||
ImageHandle vulkan_backbuffer;
|
||||
|
||||
bool setup_per_frame_state(PerFrameState &state, unsigned index);
|
||||
void reset_backbuffer_state();
|
||||
|
||||
uint64_t completed_presents = 0;
|
||||
uint64_t completed_waits = 0;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/* Copyright (c) 2017-2026 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 "wsi_pacer.hpp"
|
||||
#include "timer.hpp"
|
||||
|
||||
FixedRefreshRatePacer::FixedRefreshRatePacer()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
FixedRefreshRatePacer::~FixedRefreshRatePacer()
|
||||
{
|
||||
#if 0
|
||||
for (int i = 0; i < NumHistogramEntries; i++)
|
||||
{
|
||||
LOGI("Success rate: band [%.3f ms - %.3f ms] -> %.3f %% (last failure %llu) (overall %llu failures)\n", double(i * GapQuantumNS) * 1e-6,
|
||||
double((i + 1) * GapQuantumNS) * 1e-6, histogram[i].confidence * 100.0,
|
||||
static_cast<unsigned long long>(histogram[i].last_failure_present_id),
|
||||
static_cast<unsigned long long>(histogram[i].num_relevant_present_failures));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::reset()
|
||||
{
|
||||
feedbacks.clear();
|
||||
last_feedback = {};
|
||||
frame_time_ns = 0;
|
||||
estimated_present_gap_ns = UINT64_MAX;
|
||||
estimated_frame_latency_ns = UINT64_MAX;
|
||||
for (auto &entry : histogram)
|
||||
entry = {};
|
||||
overall = {};
|
||||
minimum_confidence_for_promotion = 0.0;
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::discard_pacing_statistics(uint64_t present_id)
|
||||
{
|
||||
find_and_remove_feedback(present_id);
|
||||
}
|
||||
|
||||
FixedRefreshRatePacer::Feedback FixedRefreshRatePacer::find_and_remove_feedback(uint64_t present_id)
|
||||
{
|
||||
auto itr = std::find_if(feedbacks.begin(), feedbacks.end(), [=](const Feedback &f)
|
||||
{
|
||||
return f.present_id == present_id;
|
||||
});
|
||||
|
||||
if (itr == feedbacks.end())
|
||||
return {};
|
||||
|
||||
auto ret = *itr;
|
||||
feedbacks.erase(itr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::set_frame_time_ns(uint64_t frame_time)
|
||||
{
|
||||
frame_time_ns = frame_time;
|
||||
|
||||
// Dynamically adjust as we get completed frames coming in.
|
||||
estimated_present_gap_ns = std::min<uint64_t>(frame_time_ns / 2, estimated_present_gap_ns);
|
||||
|
||||
// Stay in the center of the histogram range to avoid annoying rounding errors when quantizing results
|
||||
// to histogram.
|
||||
estimated_present_gap_ns = (estimated_present_gap_ns / GapQuantumNS) * GapQuantumNS + (GapQuantumNS / 2);
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::override_gpu_done_time(uint64_t present_id, uint64_t queue_done_ns)
|
||||
{
|
||||
auto itr =
|
||||
std::find_if(feedbacks.begin(), feedbacks.end(), [=](const Feedback &f) { return f.present_id == present_id; });
|
||||
if (itr != feedbacks.end())
|
||||
itr->queue_done_ns = queue_done_ns;
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::update_feedback(uint64_t present_id, uint64_t queue_done_ns, uint64_t complete_ns)
|
||||
{
|
||||
auto feedback = find_and_remove_feedback(present_id);
|
||||
|
||||
if (feedback.queue_done_ns)
|
||||
queue_done_ns = feedback.queue_done_ns;
|
||||
|
||||
last_feedback.present_id = present_id;
|
||||
last_feedback.queue_done_ns = queue_done_ns;
|
||||
last_feedback.complete_ns = complete_ns;
|
||||
|
||||
if (feedback.present_id != present_id)
|
||||
return;
|
||||
|
||||
// Safety.
|
||||
queue_done_ns = std::min<uint64_t>(queue_done_ns, complete_ns);
|
||||
|
||||
// The present happened when we expected it to. If it falls meaningfully outside of this, we have VRR
|
||||
// and a FRR pacer isn't super useful to begin with.
|
||||
if (complete_ns <= feedback.estimated_complete_ns + frame_time_ns / 8)
|
||||
{
|
||||
// With an ideal setup queue_done_ns can be arbitrarily close to complete_ns before we start dropping frames,
|
||||
// but that's not realistic.
|
||||
uint64_t present_gap_ns = complete_ns - queue_done_ns;
|
||||
|
||||
if (estimated_present_gap_ns == UINT64_MAX)
|
||||
{
|
||||
// Initialize the estimate.
|
||||
estimated_present_gap_ns = frame_time_ns / 2;
|
||||
}
|
||||
|
||||
register_present_gap(present_id, present_gap_ns, true);
|
||||
|
||||
if (present_gap_ns <= estimated_present_gap_ns)
|
||||
{
|
||||
// The compositor was able to deal with current tight timings, update our estimate accordingly (slowly).
|
||||
uint64_t candidate_ns = std::max<uint64_t>(estimated_present_gap_ns, GapQuantumNS) - GapQuantumNS;
|
||||
|
||||
// Only lower if history shows this to be a sound decision.
|
||||
if (safe_to_lower_gap_to(present_id, present_gap_ns, candidate_ns))
|
||||
estimated_present_gap_ns = candidate_ns;
|
||||
}
|
||||
}
|
||||
else if (complete_ns > feedback.estimated_complete_ns + frame_time_ns / 2 &&
|
||||
queue_done_ns < feedback.estimated_complete_ns)
|
||||
{
|
||||
// The GPU was done in time, but we dropped a frame regardless.
|
||||
// Likely the compositor has a queueing delay we need to consider.
|
||||
|
||||
estimated_present_gap_ns += GapQuantumNS;
|
||||
uint64_t potential_present_gap_ns = feedback.estimated_complete_ns - queue_done_ns;
|
||||
|
||||
// If the present gap is over half a frame, it's likely the compositor just derping out for no good reason.
|
||||
if (potential_present_gap_ns < frame_time_ns / 2)
|
||||
{
|
||||
// We have a potential present gap that failed to flip properly.
|
||||
register_present_gap(present_id, potential_present_gap_ns, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the GPU was simply too slow to render.
|
||||
// The frame latency will have to increase as expected.
|
||||
|
||||
uint64_t frame_latency_ns = std::max<uint64_t>(feedback.frame_submission_ns, queue_done_ns) -
|
||||
feedback.frame_submission_ns;
|
||||
|
||||
// Keep a running estimate of how long time it takes to process CPU -> GPU.
|
||||
// This will inform sleep targets.
|
||||
if (estimated_frame_latency_ns != UINT64_MAX)
|
||||
estimated_frame_latency_ns = (estimated_frame_latency_ns * 15 + frame_latency_ns) / 16;
|
||||
else
|
||||
estimated_frame_latency_ns = frame_latency_ns;
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::register_present_gap(uint64_t present_id, uint64_t gap_ns, bool success)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
overall.confidence *= 0.5;
|
||||
overall.last_failure_present_id = present_id;
|
||||
overall.num_relevant_present_failures++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This estimate should move very slowly.
|
||||
overall.confidence = overall.confidence * 0.99 + 0.01;
|
||||
if (overall.confidence > 0.9999999)
|
||||
{
|
||||
if (overall.num_relevant_present_failures)
|
||||
{
|
||||
// If we're resetting, put a higher bar of confidence if we want to promote so
|
||||
// that we don't instantly start to see failures.
|
||||
minimum_confidence_for_promotion = 0.7499 + minimum_confidence_for_promotion * 0.25;
|
||||
}
|
||||
|
||||
overall.num_relevant_present_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int index = int(gap_ns / GapQuantumNS);
|
||||
if (index >= NumHistogramEntries)
|
||||
return;
|
||||
|
||||
auto &entry = histogram[index];
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Avoid denorm hell, but also reset the counter if we cannot get reliable successes multiple times in a row.
|
||||
if (entry.confidence < 0.25)
|
||||
entry.confidence = 0.0;
|
||||
entry.confidence = entry.confidence * 0.5;
|
||||
entry.last_failure_present_id = present_id;
|
||||
entry.num_relevant_present_failures++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This gap seems super stable now.
|
||||
entry.confidence = entry.confidence * 0.95 + 0.05;
|
||||
if (entry.confidence > 0.999999)
|
||||
entry.num_relevant_present_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
FixedRefreshRatePacer::HistogramStats FixedRefreshRatePacer::get_current_histogram_stats() const
|
||||
{
|
||||
if (estimated_present_gap_ns == UINT64_MAX)
|
||||
return {};
|
||||
|
||||
int index = int(estimated_present_gap_ns / GapQuantumNS);
|
||||
if (index >= NumHistogramEntries)
|
||||
return {};
|
||||
return histogram[index];
|
||||
}
|
||||
|
||||
double FixedRefreshRatePacer::get_minimum_confidence_for_promotion() const
|
||||
{
|
||||
return minimum_confidence_for_promotion;
|
||||
}
|
||||
|
||||
FixedRefreshRatePacer::HistogramStats FixedRefreshRatePacer::get_candidate_histogram_stats() const
|
||||
{
|
||||
if (estimated_present_gap_ns == UINT64_MAX)
|
||||
return {};
|
||||
|
||||
int index = int(estimated_present_gap_ns / GapQuantumNS) - 1;
|
||||
if (index >= NumHistogramEntries || index < 0)
|
||||
return {};
|
||||
return histogram[index];
|
||||
}
|
||||
|
||||
FixedRefreshRatePacer::HistogramStats FixedRefreshRatePacer::get_overall_histogram_stats() const
|
||||
{
|
||||
return overall;
|
||||
}
|
||||
|
||||
uint64_t FixedRefreshRatePacer::get_estimated_present_gap_ns() const
|
||||
{
|
||||
return estimated_present_gap_ns;
|
||||
}
|
||||
|
||||
uint64_t FixedRefreshRatePacer::get_estimated_cpu_gpu_idle_latency_ns() const
|
||||
{
|
||||
return estimated_frame_latency_ns;
|
||||
}
|
||||
|
||||
bool FixedRefreshRatePacer::safe_to_lower_gap_to(uint64_t present_id, uint64_t observed_gap_ns, uint64_t to_ns) const
|
||||
{
|
||||
// If we can prove some safety of the target gap, go for it.
|
||||
int to_index = int(to_ns / GapQuantumNS);
|
||||
int from_index = int(observed_gap_ns / GapQuantumNS);
|
||||
if (to_index >= NumHistogramEntries || from_index >= NumHistogramEntries)
|
||||
return true;
|
||||
|
||||
// Don't lower until overall success probability is solid.
|
||||
uint64_t frames_since_last_observed_failure = present_id - overall.last_failure_present_id;
|
||||
auto num_failures = std::min<unsigned>(16, overall.num_relevant_present_failures);
|
||||
if (frames_since_last_observed_failure < (1u << num_failures))
|
||||
return false;
|
||||
|
||||
// We're confident the update will work well.
|
||||
if (histogram[to_index].confidence >= 0.999)
|
||||
return true;
|
||||
|
||||
// The bar to clear is higher now. If we never saw any failures, go ahead, we need to learn.
|
||||
if (histogram[to_index].confidence < minimum_confidence_for_promotion &&
|
||||
histogram[to_index].num_relevant_present_failures != 0)
|
||||
return false;
|
||||
|
||||
// We haven't proved this rate is solid.
|
||||
if (histogram[from_index].confidence < 0.999)
|
||||
return false;
|
||||
|
||||
// We've never observed a failure at this gap before, should be good to test until we observe failures.
|
||||
if (histogram[to_index].last_failure_present_id == 0)
|
||||
return true;
|
||||
|
||||
// Check statistics per histogram.
|
||||
frames_since_last_observed_failure = present_id - histogram[to_index].last_failure_present_id;
|
||||
|
||||
// Backoff algorithm which scales the number of safety frames based on failures observed overall.
|
||||
// Eventually, we have enough failures that we never trust it blindly.
|
||||
num_failures = std::min<unsigned>(16, histogram[to_index].num_relevant_present_failures);
|
||||
return frames_since_last_observed_failure >= (60u << num_failures);
|
||||
}
|
||||
|
||||
void FixedRefreshRatePacer::begin_frame_submission(uint64_t current_present_id)
|
||||
{
|
||||
if (last_feedback.present_id == 0 || frame_time_ns == 0)
|
||||
return;
|
||||
|
||||
uint64_t estimated_complete_ns =
|
||||
(current_present_id - last_feedback.present_id) * frame_time_ns +
|
||||
last_feedback.complete_ns;
|
||||
|
||||
if (estimated_present_gap_ns != UINT64_MAX)
|
||||
{
|
||||
uint64_t target_gap_ns = estimated_present_gap_ns;
|
||||
target_gap_ns += estimated_frame_latency_ns;
|
||||
int64_t sleep_target_ns = int64_t(estimated_complete_ns) - int64_t(target_gap_ns);
|
||||
Util::sleep_until_nsecs(sleep_target_ns);
|
||||
}
|
||||
|
||||
// Safety clear in case it's never polled.
|
||||
if (feedbacks.size() > 16)
|
||||
feedbacks.clear();
|
||||
|
||||
feedbacks.push_back({ current_present_id, uint64_t(Util::get_current_time_nsecs()), estimated_complete_ns });
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Copyright (c) 2017-2026 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 "small_vector.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
// Designed for lowest possible latency on fixed rate displays.
|
||||
// The GPU workload is expected to be very light and stable (e.g. video decoding).
|
||||
class FixedRefreshRatePacer
|
||||
{
|
||||
public:
|
||||
FixedRefreshRatePacer();
|
||||
~FixedRefreshRatePacer();
|
||||
|
||||
void set_frame_time_ns(uint64_t frame_time);
|
||||
void update_feedback(uint64_t present_id, uint64_t queue_done_ns, uint64_t complete_ns);
|
||||
|
||||
// Sleeps as needed. Should be called before sampling inputs, etc.
|
||||
void begin_frame_submission(uint64_t current_present_id);
|
||||
|
||||
// This frame will not be valid for purposes of statistics.
|
||||
void discard_pacing_statistics(uint64_t present_id);
|
||||
|
||||
// Can avoid some weird interactions with async compute and such.
|
||||
void override_gpu_done_time(uint64_t present_id, uint64_t queue_done_ns);
|
||||
|
||||
void reset();
|
||||
|
||||
struct HistogramStats
|
||||
{
|
||||
double confidence = 0.0;
|
||||
uint64_t last_failure_present_id = 0;
|
||||
uint64_t num_relevant_present_failures = 0;
|
||||
};
|
||||
|
||||
// Debugging.
|
||||
HistogramStats get_current_histogram_stats() const;
|
||||
HistogramStats get_candidate_histogram_stats() const;
|
||||
HistogramStats get_overall_histogram_stats() const;
|
||||
uint64_t get_estimated_present_gap_ns() const;
|
||||
uint64_t get_estimated_cpu_gpu_idle_latency_ns() const;
|
||||
double get_minimum_confidence_for_promotion() const;
|
||||
|
||||
private:
|
||||
uint64_t frame_time_ns = 0;
|
||||
uint64_t estimated_present_gap_ns = UINT64_MAX;
|
||||
uint64_t estimated_frame_latency_ns = UINT64_MAX;
|
||||
double minimum_confidence_for_promotion = 0.0;
|
||||
|
||||
struct Feedback
|
||||
{
|
||||
uint64_t present_id;
|
||||
uint64_t frame_submission_ns;
|
||||
uint64_t estimated_complete_ns;
|
||||
uint64_t queue_done_ns;
|
||||
uint64_t complete_ns;
|
||||
};
|
||||
|
||||
Util::SmallVector<Feedback> feedbacks;
|
||||
Feedback last_feedback = {};
|
||||
Feedback find_and_remove_feedback(uint64_t present_id);
|
||||
|
||||
// Don't make the quant interval too narrow, or we won't be able capture statistics well.
|
||||
enum { GapQuantumNS = 250 * 1000, NumHistogramEntries = 64 };
|
||||
|
||||
HistogramStats histogram[NumHistogramEntries];
|
||||
HistogramStats overall;
|
||||
|
||||
void register_present_gap(uint64_t present_id, uint64_t gap_ns, bool success);
|
||||
bool safe_to_lower_gap_to(uint64_t present_id, uint64_t observed_gap_ns, uint64_t to_ns) const;
|
||||
};
|
||||
Reference in New Issue
Block a user