Merge pull request #3653 from ReinUsesLisp/nsight-aftermath
renderer_vulkan: Integrate Nvidia Nsight Aftermath on Windows
This commit is contained in:
commit
afae40a99e
|
@ -160,6 +160,8 @@ if (ENABLE_VULKAN)
|
||||||
renderer_vulkan/fixed_pipeline_state.h
|
renderer_vulkan/fixed_pipeline_state.h
|
||||||
renderer_vulkan/maxwell_to_vk.cpp
|
renderer_vulkan/maxwell_to_vk.cpp
|
||||||
renderer_vulkan/maxwell_to_vk.h
|
renderer_vulkan/maxwell_to_vk.h
|
||||||
|
renderer_vulkan/nsight_aftermath_tracker.cpp
|
||||||
|
renderer_vulkan/nsight_aftermath_tracker.h
|
||||||
renderer_vulkan/renderer_vulkan.h
|
renderer_vulkan/renderer_vulkan.h
|
||||||
renderer_vulkan/renderer_vulkan.cpp
|
renderer_vulkan/renderer_vulkan.cpp
|
||||||
renderer_vulkan/vk_blit_screen.cpp
|
renderer_vulkan/vk_blit_screen.cpp
|
||||||
|
@ -213,19 +215,30 @@ if (ENABLE_VULKAN)
|
||||||
renderer_vulkan/wrapper.cpp
|
renderer_vulkan/wrapper.cpp
|
||||||
renderer_vulkan/wrapper.h
|
renderer_vulkan/wrapper.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
|
|
||||||
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(video_core)
|
create_target_directory_groups(video_core)
|
||||||
|
|
||||||
target_link_libraries(video_core PUBLIC common core)
|
target_link_libraries(video_core PUBLIC common core)
|
||||||
target_link_libraries(video_core PRIVATE glad)
|
target_link_libraries(video_core PRIVATE glad)
|
||||||
|
|
||||||
if (ENABLE_VULKAN)
|
if (ENABLE_VULKAN)
|
||||||
|
target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
|
||||||
|
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
||||||
target_link_libraries(video_core PRIVATE sirit)
|
target_link_libraries(video_core PRIVATE sirit)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_NSIGHT_AFTERMATH)
|
||||||
|
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
|
||||||
|
message(ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided")
|
||||||
|
endif()
|
||||||
|
if (NOT WIN32)
|
||||||
|
message(ERROR "Nsight Aftermath doesn't support non-Windows platforms")
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(video_core PRIVATE HAS_NSIGHT_AFTERMATH)
|
||||||
|
target_include_directories(video_core PRIVATE "$ENV{NSIGHT_AFTERMATH_SDK}/include")
|
||||||
|
endif()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(video_core PRIVATE /we4267)
|
target_compile_options(video_core PRIVATE /we4267)
|
||||||
else()
|
else()
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#ifdef HAS_NSIGHT_AFTERMATH
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <GFSDK_Aftermath.h>
|
||||||
|
#include <GFSDK_Aftermath_Defines.h>
|
||||||
|
#include <GFSDK_Aftermath_GpuCrashDump.h>
|
||||||
|
#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
|
||||||
|
|
||||||
|
#include "common/common_paths.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
|
||||||
|
#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
static constexpr char AFTERMATH_LIB_NAME[] = "GFSDK_Aftermath_Lib.x64.dll";
|
||||||
|
|
||||||
|
NsightAftermathTracker::NsightAftermathTracker() = default;
|
||||||
|
|
||||||
|
NsightAftermathTracker::~NsightAftermathTracker() {
|
||||||
|
if (initialized) {
|
||||||
|
(void)GFSDK_Aftermath_DisableGpuCrashDumps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NsightAftermathTracker::Initialize() {
|
||||||
|
if (!dl.Open(AFTERMATH_LIB_NAME)) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath DLL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dl.GetSymbol("GFSDK_Aftermath_DisableGpuCrashDumps",
|
||||||
|
&GFSDK_Aftermath_DisableGpuCrashDumps) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_EnableGpuCrashDumps",
|
||||||
|
&GFSDK_Aftermath_EnableGpuCrashDumps) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GetShaderDebugInfoIdentifier",
|
||||||
|
&GFSDK_Aftermath_GetShaderDebugInfoIdentifier) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GetShaderHashSpirv", &GFSDK_Aftermath_GetShaderHashSpirv) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_CreateDecoder",
|
||||||
|
&GFSDK_Aftermath_GpuCrashDump_CreateDecoder) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_DestroyDecoder",
|
||||||
|
&GFSDK_Aftermath_GpuCrashDump_DestroyDecoder) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GenerateJSON",
|
||||||
|
&GFSDK_Aftermath_GpuCrashDump_GenerateJSON) ||
|
||||||
|
!dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GetJSON",
|
||||||
|
&GFSDK_Aftermath_GpuCrashDump_GetJSON)) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "gpucrash";
|
||||||
|
|
||||||
|
(void)FileUtil::DeleteDirRecursively(dump_dir);
|
||||||
|
if (!FileUtil::CreateDir(dump_dir)) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps(
|
||||||
|
GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
|
||||||
|
GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback,
|
||||||
|
ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir);
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> spirv_copy = spirv;
|
||||||
|
GFSDK_Aftermath_SpirvCode shader;
|
||||||
|
shader.pData = spirv_copy.data();
|
||||||
|
shader.size = static_cast<u32>(spirv_copy.size() * 4);
|
||||||
|
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
|
||||||
|
GFSDK_Aftermath_ShaderHash hash;
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(
|
||||||
|
GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
|
||||||
|
u32 gpu_crash_dump_size) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
|
||||||
|
LOG_CRITICAL(Render_Vulkan, "called");
|
||||||
|
|
||||||
|
GFSDK_Aftermath_GpuCrashDump_Decoder decoder;
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_CreateDecoder(
|
||||||
|
GFSDK_Aftermath_Version_API, gpu_crash_dump, gpu_crash_dump_size, &decoder))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to create decoder");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SCOPE_EXIT({ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder); });
|
||||||
|
|
||||||
|
u32 json_size = 0;
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
|
||||||
|
decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
|
||||||
|
GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr,
|
||||||
|
this, &json_size))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to generate JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<char> json(json_size);
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(
|
||||||
|
GFSDK_Aftermath_GpuCrashDump_GetJSON(decoder, json_size, json.data()))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to query JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string base_name = [this] {
|
||||||
|
const int id = dump_id++;
|
||||||
|
if (id == 0) {
|
||||||
|
return fmt::format("{}/crash.nv-gpudmp", dump_dir);
|
||||||
|
} else {
|
||||||
|
return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
|
||||||
|
if (FileUtil::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to write dump file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string_view json_view(json.data(), json.size());
|
||||||
|
if (FileUtil::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to write JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_info,
|
||||||
|
u32 shader_debug_info_size) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
|
||||||
|
GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier;
|
||||||
|
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(
|
||||||
|
GFSDK_Aftermath_Version_API, shader_debug_info, shader_debug_info_size, &identifier))) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_GetShaderDebugInfoIdentifier failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string path =
|
||||||
|
fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
|
||||||
|
FileUtil::IOFile file(path, "wb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) !=
|
||||||
|
shader_debug_info_size) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Failed to write file {}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::OnCrashDumpDescriptionCallback(
|
||||||
|
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description) {
|
||||||
|
add_description(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, "yuzu");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::GpuCrashDumpCallback(const void* gpu_crash_dump,
|
||||||
|
u32 gpu_crash_dump_size, void* user_data) {
|
||||||
|
static_cast<NsightAftermathTracker*>(user_data)->OnGpuCrashDumpCallback(gpu_crash_dump,
|
||||||
|
gpu_crash_dump_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::ShaderDebugInfoCallback(const void* shader_debug_info,
|
||||||
|
u32 shader_debug_info_size, void* user_data) {
|
||||||
|
static_cast<NsightAftermathTracker*>(user_data)->OnShaderDebugInfoCallback(
|
||||||
|
shader_debug_info, shader_debug_info_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsightAftermathTracker::CrashDumpDescriptionCallback(
|
||||||
|
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data) {
|
||||||
|
static_cast<NsightAftermathTracker*>(user_data)->OnCrashDumpDescriptionCallback(
|
||||||
|
add_description);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
|
|
||||||
|
#endif // HAS_NSIGHT_AFTERMATH
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#ifdef HAS_NSIGHT_AFTERMATH
|
||||||
|
#include <GFSDK_Aftermath_Defines.h>
|
||||||
|
#include <GFSDK_Aftermath_GpuCrashDump.h>
|
||||||
|
#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/dynamic_library.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class NsightAftermathTracker {
|
||||||
|
public:
|
||||||
|
NsightAftermathTracker();
|
||||||
|
~NsightAftermathTracker();
|
||||||
|
|
||||||
|
NsightAftermathTracker(const NsightAftermathTracker&) = delete;
|
||||||
|
NsightAftermathTracker& operator=(const NsightAftermathTracker&) = delete;
|
||||||
|
|
||||||
|
// Delete move semantics because Aftermath initialization uses a pointer to this.
|
||||||
|
NsightAftermathTracker(NsightAftermathTracker&&) = delete;
|
||||||
|
NsightAftermathTracker& operator=(NsightAftermathTracker&&) = delete;
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void SaveShader(const std::vector<u32>& spirv) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef HAS_NSIGHT_AFTERMATH
|
||||||
|
static void GpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
|
static void ShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
|
static void CrashDumpDescriptionCallback(
|
||||||
|
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data);
|
||||||
|
|
||||||
|
void OnGpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size);
|
||||||
|
|
||||||
|
void OnShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size);
|
||||||
|
|
||||||
|
void OnCrashDumpDescriptionCallback(
|
||||||
|
PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description);
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
|
||||||
|
std::string dump_dir;
|
||||||
|
int dump_id = 0;
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
Common::DynamicLibrary dl;
|
||||||
|
PFN_GFSDK_Aftermath_DisableGpuCrashDumps GFSDK_Aftermath_DisableGpuCrashDumps;
|
||||||
|
PFN_GFSDK_Aftermath_EnableGpuCrashDumps GFSDK_Aftermath_EnableGpuCrashDumps;
|
||||||
|
PFN_GFSDK_Aftermath_GetShaderDebugInfoIdentifier GFSDK_Aftermath_GetShaderDebugInfoIdentifier;
|
||||||
|
PFN_GFSDK_Aftermath_GetShaderHashSpirv GFSDK_Aftermath_GetShaderHashSpirv;
|
||||||
|
PFN_GFSDK_Aftermath_GpuCrashDump_CreateDecoder GFSDK_Aftermath_GpuCrashDump_CreateDecoder;
|
||||||
|
PFN_GFSDK_Aftermath_GpuCrashDump_DestroyDecoder GFSDK_Aftermath_GpuCrashDump_DestroyDecoder;
|
||||||
|
PFN_GFSDK_Aftermath_GpuCrashDump_GenerateJSON GFSDK_Aftermath_GpuCrashDump_GenerateJSON;
|
||||||
|
PFN_GFSDK_Aftermath_GpuCrashDump_GetJSON GFSDK_Aftermath_GpuCrashDump_GetJSON;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef HAS_NSIGHT_AFTERMATH
|
||||||
|
inline NsightAftermathTracker::NsightAftermathTracker() = default;
|
||||||
|
inline NsightAftermathTracker::~NsightAftermathTracker() = default;
|
||||||
|
inline bool NsightAftermathTracker::Initialize() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inline void NsightAftermathTracker::SaveShader(const std::vector<u32>&) const {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -105,6 +105,8 @@ vk::DescriptorUpdateTemplateKHR VKComputePipeline::CreateDescriptorUpdateTemplat
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::ShaderModule VKComputePipeline::CreateShaderModule(const std::vector<u32>& code) const {
|
vk::ShaderModule VKComputePipeline::CreateShaderModule(const std::vector<u32>& code) const {
|
||||||
|
device.SaveShader(code);
|
||||||
|
|
||||||
VkShaderModuleCreateInfo ci;
|
VkShaderModuleCreateInfo ci;
|
||||||
ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||||
ci.pNext = nullptr;
|
ci.pNext = nullptr;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
@ -167,6 +168,7 @@ bool VKDevice::Create() {
|
||||||
VkPhysicalDeviceFeatures2 features2;
|
VkPhysicalDeviceFeatures2 features2;
|
||||||
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||||
features2.pNext = nullptr;
|
features2.pNext = nullptr;
|
||||||
|
const void* first_next = &features2;
|
||||||
void** next = &features2.pNext;
|
void** next = &features2.pNext;
|
||||||
|
|
||||||
auto& features = features2.features;
|
auto& features = features2.features;
|
||||||
|
@ -296,7 +298,19 @@ bool VKDevice::Create() {
|
||||||
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
|
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
|
||||||
}
|
}
|
||||||
|
|
||||||
logical = vk::Device::Create(physical, queue_cis, extensions, features2, dld);
|
VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv;
|
||||||
|
if (nv_device_diagnostics_config) {
|
||||||
|
nsight_aftermath_tracker.Initialize();
|
||||||
|
|
||||||
|
diagnostics_nv.sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV;
|
||||||
|
diagnostics_nv.pNext = &features2;
|
||||||
|
diagnostics_nv.flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV |
|
||||||
|
VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV |
|
||||||
|
VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV;
|
||||||
|
first_next = &diagnostics_nv;
|
||||||
|
}
|
||||||
|
|
||||||
|
logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
|
||||||
if (!logical) {
|
if (!logical) {
|
||||||
LOG_ERROR(Render_Vulkan, "Failed to create logical device");
|
LOG_ERROR(Render_Vulkan, "Failed to create logical device");
|
||||||
return false;
|
return false;
|
||||||
|
@ -344,17 +358,12 @@ VkFormat VKDevice::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFla
|
||||||
void VKDevice::ReportLoss() const {
|
void VKDevice::ReportLoss() const {
|
||||||
LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
|
LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
|
||||||
|
|
||||||
// Wait some time to let the log flush
|
// Wait for the log to flush and for Nsight Aftermath to dump the results
|
||||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||||
|
}
|
||||||
|
|
||||||
if (!nv_device_diagnostic_checkpoints) {
|
void VKDevice::SaveShader(const std::vector<u32>& spirv) const {
|
||||||
return;
|
nsight_aftermath_tracker.SaveShader(spirv);
|
||||||
}
|
|
||||||
|
|
||||||
[[maybe_unused]] const std::vector data = graphics_queue.GetCheckpointDataNV(dld);
|
|
||||||
// Catch here in debug builds (or with optimizations disabled) the last graphics pipeline to be
|
|
||||||
// executed. It can be done on a debugger by evaluating the expression:
|
|
||||||
// *(VKGraphicsPipeline*)data[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const {
|
bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const {
|
||||||
|
@ -527,8 +536,8 @@ std::vector<const char*> VKDevice::LoadExtensions() {
|
||||||
Test(extension, has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME,
|
Test(extension, has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME,
|
||||||
false);
|
false);
|
||||||
if (Settings::values.renderer_debug) {
|
if (Settings::values.renderer_debug) {
|
||||||
Test(extension, nv_device_diagnostic_checkpoints,
|
Test(extension, nv_device_diagnostics_config,
|
||||||
VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
|
VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
|
||||||
#include "video_core/renderer_vulkan/wrapper.h"
|
#include "video_core/renderer_vulkan/wrapper.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
@ -43,6 +44,9 @@ public:
|
||||||
/// Reports a device loss.
|
/// Reports a device loss.
|
||||||
void ReportLoss() const;
|
void ReportLoss() const;
|
||||||
|
|
||||||
|
/// Reports a shader to Nsight Aftermath.
|
||||||
|
void SaveShader(const std::vector<u32>& spirv) const;
|
||||||
|
|
||||||
/// Returns the dispatch loader with direct function pointers of the device.
|
/// Returns the dispatch loader with direct function pointers of the device.
|
||||||
const vk::DeviceDispatch& GetDispatchLoader() const {
|
const vk::DeviceDispatch& GetDispatchLoader() const {
|
||||||
return dld;
|
return dld;
|
||||||
|
@ -173,11 +177,6 @@ public:
|
||||||
return ext_transform_feedback;
|
return ext_transform_feedback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports VK_NV_device_diagnostic_checkpoints.
|
|
||||||
bool IsNvDeviceDiagnosticCheckpoints() const {
|
|
||||||
return nv_device_diagnostic_checkpoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the vendor name reported from Vulkan.
|
/// Returns the vendor name reported from Vulkan.
|
||||||
std::string_view GetVendorName() const {
|
std::string_view GetVendorName() const {
|
||||||
return vendor_name;
|
return vendor_name;
|
||||||
|
@ -233,7 +232,7 @@ private:
|
||||||
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
|
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
|
||||||
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
|
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
|
||||||
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
|
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
|
||||||
bool nv_device_diagnostic_checkpoints{}; ///< Support for VK_NV_device_diagnostic_checkpoints.
|
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
|
||||||
|
|
||||||
// Telemetry parameters
|
// Telemetry parameters
|
||||||
std::string vendor_name; ///< Device's driver name.
|
std::string vendor_name; ///< Device's driver name.
|
||||||
|
@ -241,6 +240,9 @@ private:
|
||||||
|
|
||||||
/// Format properties dictionary.
|
/// Format properties dictionary.
|
||||||
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
|
std::unordered_map<VkFormat, VkFormatProperties> format_properties;
|
||||||
|
|
||||||
|
/// Nsight Aftermath GPU crash tracker
|
||||||
|
NsightAftermathTracker nsight_aftermath_tracker;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
|
@ -148,6 +148,8 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device.SaveShader(stage->code);
|
||||||
|
|
||||||
ci.codeSize = stage->code.size() * sizeof(u32);
|
ci.codeSize = stage->code.size() * sizeof(u32);
|
||||||
ci.pCode = stage->code.data();
|
ci.pCode = stage->code.data();
|
||||||
modules.push_back(device.GetLogical().CreateShaderModule(ci));
|
modules.push_back(device.GetLogical().CreateShaderModule(ci));
|
||||||
|
|
|
@ -113,8 +113,19 @@ u64 HostCounter::BlockingQuery() const {
|
||||||
if (ticks >= cache.Scheduler().Ticks()) {
|
if (ticks >= cache.Scheduler().Ticks()) {
|
||||||
cache.Scheduler().Flush();
|
cache.Scheduler().Flush();
|
||||||
}
|
}
|
||||||
return cache.Device().GetLogical().GetQueryResult<u64>(
|
u64 data;
|
||||||
query.first, query.second, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
const VkResult result = cache.Device().GetLogical().GetQueryResults(
|
||||||
|
query.first, query.second, 1, sizeof(data), &data, sizeof(data),
|
||||||
|
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
||||||
|
switch (result) {
|
||||||
|
case VK_SUCCESS:
|
||||||
|
return data;
|
||||||
|
case VK_ERROR_DEVICE_LOST:
|
||||||
|
cache.Device().ReportLoss();
|
||||||
|
[[fallthrough]];
|
||||||
|
default:
|
||||||
|
throw vk::Exception(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
|
@ -347,11 +347,6 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
|
||||||
|
|
||||||
buffer_bindings.Bind(scheduler);
|
buffer_bindings.Bind(scheduler);
|
||||||
|
|
||||||
if (device.IsNvDeviceDiagnosticCheckpoints()) {
|
|
||||||
scheduler.Record(
|
|
||||||
[&pipeline](vk::CommandBuffer cmdbuf) { cmdbuf.SetCheckpointNV(&pipeline); });
|
|
||||||
}
|
|
||||||
|
|
||||||
BeginTransformFeedback();
|
BeginTransformFeedback();
|
||||||
|
|
||||||
const auto pipeline_layout = pipeline.GetLayout();
|
const auto pipeline_layout = pipeline.GetLayout();
|
||||||
|
@ -478,11 +473,6 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
|
||||||
TransitionImages(image_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
TransitionImages(image_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||||
|
|
||||||
if (device.IsNvDeviceDiagnosticCheckpoints()) {
|
|
||||||
scheduler.Record(
|
|
||||||
[&pipeline](vk::CommandBuffer cmdbuf) { cmdbuf.SetCheckpointNV(nullptr); });
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y,
|
scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y,
|
||||||
grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(),
|
grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(),
|
||||||
layout = pipeline.GetLayout(),
|
layout = pipeline.GetLayout(),
|
||||||
|
|
|
@ -166,7 +166,15 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
|
||||||
submit_info.pCommandBuffers = current_cmdbuf.address();
|
submit_info.pCommandBuffers = current_cmdbuf.address();
|
||||||
submit_info.signalSemaphoreCount = semaphore ? 1 : 0;
|
submit_info.signalSemaphoreCount = semaphore ? 1 : 0;
|
||||||
submit_info.pSignalSemaphores = &semaphore;
|
submit_info.pSignalSemaphores = &semaphore;
|
||||||
device.GetGraphicsQueue().Submit(submit_info, *current_fence);
|
switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info, *current_fence)) {
|
||||||
|
case VK_SUCCESS:
|
||||||
|
break;
|
||||||
|
case VK_ERROR_DEVICE_LOST:
|
||||||
|
device.ReportLoss();
|
||||||
|
[[fallthrough]];
|
||||||
|
default:
|
||||||
|
vk::Check(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKScheduler::AllocateNewContext() {
|
void VKScheduler::AllocateNewContext() {
|
||||||
|
|
|
@ -61,7 +61,6 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||||
X(vkCmdPipelineBarrier);
|
X(vkCmdPipelineBarrier);
|
||||||
X(vkCmdPushConstants);
|
X(vkCmdPushConstants);
|
||||||
X(vkCmdSetBlendConstants);
|
X(vkCmdSetBlendConstants);
|
||||||
X(vkCmdSetCheckpointNV);
|
|
||||||
X(vkCmdSetDepthBias);
|
X(vkCmdSetDepthBias);
|
||||||
X(vkCmdSetDepthBounds);
|
X(vkCmdSetDepthBounds);
|
||||||
X(vkCmdSetScissor);
|
X(vkCmdSetScissor);
|
||||||
|
@ -116,7 +115,6 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||||
X(vkGetFenceStatus);
|
X(vkGetFenceStatus);
|
||||||
X(vkGetImageMemoryRequirements);
|
X(vkGetImageMemoryRequirements);
|
||||||
X(vkGetQueryPoolResults);
|
X(vkGetQueryPoolResults);
|
||||||
X(vkGetQueueCheckpointDataNV);
|
|
||||||
X(vkMapMemory);
|
X(vkMapMemory);
|
||||||
X(vkQueueSubmit);
|
X(vkQueueSubmit);
|
||||||
X(vkResetFences);
|
X(vkResetFences);
|
||||||
|
@ -409,17 +407,6 @@ DebugCallback Instance::TryCreateDebugCallback(
|
||||||
return DebugCallback(messenger, handle, *dld);
|
return DebugCallback(messenger, handle, *dld);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<VkCheckpointDataNV> Queue::GetCheckpointDataNV(const DeviceDispatch& dld) const {
|
|
||||||
if (!dld.vkGetQueueCheckpointDataNV) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
u32 num;
|
|
||||||
dld.vkGetQueueCheckpointDataNV(queue, &num, nullptr);
|
|
||||||
std::vector<VkCheckpointDataNV> checkpoints(num);
|
|
||||||
dld.vkGetQueueCheckpointDataNV(queue, &num, checkpoints.data());
|
|
||||||
return checkpoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
|
void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
|
||||||
Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
|
Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
|
||||||
}
|
}
|
||||||
|
@ -469,12 +456,11 @@ std::vector<VkImage> SwapchainKHR::GetImages() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
|
Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
|
||||||
Span<const char*> enabled_extensions,
|
Span<const char*> enabled_extensions, const void* next,
|
||||||
const VkPhysicalDeviceFeatures2& enabled_features,
|
|
||||||
DeviceDispatch& dld) noexcept {
|
DeviceDispatch& dld) noexcept {
|
||||||
VkDeviceCreateInfo ci;
|
VkDeviceCreateInfo ci;
|
||||||
ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||||
ci.pNext = &enabled_features;
|
ci.pNext = next;
|
||||||
ci.flags = 0;
|
ci.flags = 0;
|
||||||
ci.queueCreateInfoCount = queues_ci.size();
|
ci.queueCreateInfoCount = queues_ci.size();
|
||||||
ci.pQueueCreateInfos = queues_ci.data();
|
ci.pQueueCreateInfos = queues_ci.data();
|
||||||
|
|
|
@ -197,7 +197,6 @@ struct DeviceDispatch : public InstanceDispatch {
|
||||||
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
|
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
|
||||||
PFN_vkCmdPushConstants vkCmdPushConstants;
|
PFN_vkCmdPushConstants vkCmdPushConstants;
|
||||||
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
|
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
|
||||||
PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV;
|
|
||||||
PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
|
PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
|
||||||
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
|
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
|
||||||
PFN_vkCmdSetScissor vkCmdSetScissor;
|
PFN_vkCmdSetScissor vkCmdSetScissor;
|
||||||
|
@ -252,7 +251,6 @@ struct DeviceDispatch : public InstanceDispatch {
|
||||||
PFN_vkGetFenceStatus vkGetFenceStatus;
|
PFN_vkGetFenceStatus vkGetFenceStatus;
|
||||||
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
|
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
|
||||||
PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
|
PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
|
||||||
PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV;
|
|
||||||
PFN_vkMapMemory vkMapMemory;
|
PFN_vkMapMemory vkMapMemory;
|
||||||
PFN_vkQueueSubmit vkQueueSubmit;
|
PFN_vkQueueSubmit vkQueueSubmit;
|
||||||
PFN_vkResetFences vkResetFences;
|
PFN_vkResetFences vkResetFences;
|
||||||
|
@ -567,12 +565,8 @@ public:
|
||||||
/// Construct a queue handle.
|
/// Construct a queue handle.
|
||||||
constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {}
|
constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {}
|
||||||
|
|
||||||
/// Returns the checkpoint data.
|
VkResult Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const noexcept {
|
||||||
/// @note Returns an empty vector when the function pointer is not present.
|
return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
|
||||||
std::vector<VkCheckpointDataNV> GetCheckpointDataNV(const DeviceDispatch& dld) const;
|
|
||||||
|
|
||||||
void Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const {
|
|
||||||
Check(dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
|
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
|
||||||
|
@ -659,8 +653,7 @@ class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
|
static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
|
||||||
Span<const char*> enabled_extensions,
|
Span<const char*> enabled_extensions, const void* next,
|
||||||
const VkPhysicalDeviceFeatures2& enabled_features,
|
|
||||||
DeviceDispatch& dld) noexcept;
|
DeviceDispatch& dld) noexcept;
|
||||||
|
|
||||||
Queue GetQueue(u32 family_index) const noexcept;
|
Queue GetQueue(u32 family_index) const noexcept;
|
||||||
|
@ -734,18 +727,11 @@ public:
|
||||||
dld->vkResetQueryPoolEXT(handle, query_pool, first, count);
|
dld->vkResetQueryPoolEXT(handle, query_pool, first, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
|
VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
|
||||||
void* data, VkDeviceSize stride, VkQueryResultFlags flags) const {
|
void* data, VkDeviceSize stride, VkQueryResultFlags flags) const
|
||||||
Check(dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
|
noexcept {
|
||||||
flags));
|
return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
|
||||||
}
|
flags);
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T GetQueryResult(VkQueryPool query_pool, u32 first, VkQueryResultFlags flags) const {
|
|
||||||
static_assert(std::is_trivially_copyable_v<T>);
|
|
||||||
T value;
|
|
||||||
GetQueryResults(query_pool, first, 1, sizeof(T), &value, sizeof(T), flags);
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -920,10 +906,6 @@ public:
|
||||||
dld->vkCmdPushConstants(handle, layout, flags, offset, size, values);
|
dld->vkCmdPushConstants(handle, layout, flags, offset, size, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCheckpointNV(const void* checkpoint_marker) const noexcept {
|
|
||||||
dld->vkCmdSetCheckpointNV(handle, checkpoint_marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept {
|
void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept {
|
||||||
dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data());
|
dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data());
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue