From 2b8062bc430b2690280d0d907f6fd4e59e62c3fb Mon Sep 17 00:00:00 2001 From: baldurk Date: Sun, 2 Sep 2018 13:13:13 +0100 Subject: [PATCH] Add support for MoltenVK in vulkan back-end --- renderdoc/CMakeLists.txt | 8 + renderdoc/driver/vulkan/CMakeLists.txt | 4 +- .../driver/vulkan/renderdoc_vulkan.vcxproj | 1 + .../vulkan/renderdoc_vulkan.vcxproj.filters | 3 + renderdoc/driver/vulkan/vk_android.cpp | 25 ++ renderdoc/driver/vulkan/vk_apple.cpp | 71 +++- renderdoc/driver/vulkan/vk_apple.mm | 17 + renderdoc/driver/vulkan/vk_core.cpp | 5 + renderdoc/driver/vulkan/vk_core.h | 7 + renderdoc/driver/vulkan/vk_hookset_defs.h | 13 + renderdoc/driver/vulkan/vk_linux.cpp | 390 +++++------------- renderdoc/driver/vulkan/vk_posix.cpp | 382 ++++++++++++----- .../vulkan/wrappers/vk_device_funcs.cpp | 7 +- 13 files changed, 533 insertions(+), 400 deletions(-) create mode 100644 renderdoc/driver/vulkan/vk_apple.mm diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index 47a0e3099..3c536797a 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -17,6 +17,14 @@ elseif(APPLE) list(APPEND RDOC_LIBRARIES PRIVATE -lm PRIVATE -ldl) + + if(ENABLE_VULKAN) + find_library(COCOA_LIBRARY Cocoa) + list(APPEND RDOC_LIBRARIES PRIVATE ${COCOA_LIBRARY}) + + find_library(QUARTZCORE_LIBRARY QuartzCore) + list(APPEND RDOC_LIBRARIES PRIVATE ${QUARTZCORE_LIBRARY}) + endif() elseif(UNIX) find_package(PkgConfig REQUIRED) find_package(Threads REQUIRED) diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index 1ab5fdd1e..4f2d63b4f 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -81,7 +81,9 @@ if(ANDROID) list(APPEND sources vk_posix.cpp vk_android.cpp vk_layer_android.cpp) list(APPEND definitions PRIVATE -DVK_USE_PLATFORM_ANDROID_KHR) elseif(APPLE) - list(APPEND sources vk_posix.cpp vk_apple.cpp) + list(APPEND sources vk_posix.cpp vk_apple.cpp vk_apple.mm) + + add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) elseif(UNIX) list(APPEND sources vk_posix.cpp vk_linux.cpp) diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj index 533e75d5c..1be40eb90 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj @@ -186,6 +186,7 @@ + diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters index e4dbce43b..0f5f89823 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters @@ -253,5 +253,8 @@ Layer + + OS\Posix + \ No newline at end of file diff --git a/renderdoc/driver/vulkan/vk_android.cpp b/renderdoc/driver/vulkan/vk_android.cpp index 8395cf9ad..e0ac8fc38 100644 --- a/renderdoc/driver/vulkan/vk_android.cpp +++ b/renderdoc/driver/vulkan/vk_android.cpp @@ -26,6 +26,31 @@ #include "vk_core.h" #include "vk_replay.h" +VkResult WrappedVulkan::vkCreateAndroidSurfaceKHR(VkInstance instance, + const VkAndroidSurfaceCreateInfoKHR *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) +{ + // should not come in here at all on replay + RDCASSERT(IsCaptureMode(m_State)); + + VkResult ret = ObjDisp(instance)->CreateAndroidSurfaceKHR(Unwrap(instance), pCreateInfo, + pAllocator, pSurface); + + if(ret == VK_SUCCESS) + { + GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + + WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); + + // since there's no point in allocating a full resource record and storing the window + // handle under there somewhere, we just cast. We won't use the resource record for anything + wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->window; + } + + return ret; +} + void VulkanReplay::OutputWindow::SetWindowHandle(WindowingData window) { RDCASSERT(window.system == WindowingSystem::Android, window.system); diff --git a/renderdoc/driver/vulkan/vk_apple.cpp b/renderdoc/driver/vulkan/vk_apple.cpp index a86bf418a..8387d7c2d 100644 --- a/renderdoc/driver/vulkan/vk_apple.cpp +++ b/renderdoc/driver/vulkan/vk_apple.cpp @@ -25,29 +25,76 @@ #include "vk_core.h" #include "vk_replay.h" +#include + +// helpers defined in vk_apple.mm +extern "C" int getCALayerWidth(void *handle); +extern "C" int getCALayerHeight(void *handle); + +VkResult WrappedVulkan::vkCreateMacOSSurfaceMVK(VkInstance instance, + const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) +{ + // should not come in here at all on replay + RDCASSERT(IsCaptureMode(m_State)); + + VkResult ret = + ObjDisp(instance)->CreateMacOSSurfaceMVK(Unwrap(instance), pCreateInfo, pAllocator, pSurface); + + if(ret == VK_SUCCESS) + { + GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + + WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); + + // since there's no point in allocating a full resource record and storing the window + // handle under there somewhere, we just cast. We won't use the resource record for anything + wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->pView; + } + + return ret; +} + void VulkanReplay::OutputWindow::SetWindowHandle(WindowingData window) { - RDCUNIMPLEMENTED("SetWindowHandle"); + RDCASSERT(window.system == WindowingSystem::MacOS, window.system); + wnd = window.macOS.layer; } void VulkanReplay::OutputWindow::CreateSurface(VkInstance inst) { - RDCUNIMPLEMENTED("CreateSurface"); + VkMacOSSurfaceCreateInfoMVK createInfo; + + createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.pView = wnd; + + VkResult vkr = ObjDisp(inst)->CreateMacOSSurfaceMVK(Unwrap(inst), &createInfo, NULL, &surface); + RDCASSERTEQUAL(vkr, VK_SUCCESS); } void VulkanReplay::GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h) { - RDCUNIMPLEMENTED("GetOutputWindowDimensions"); + if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end()) + return; + + OutputWindow &outw = m_OutputWindows[id]; + + w = getCALayerWidth(outw.wnd); + h = getCALayerHeight(outw.wnd); } -const char *VulkanLibraryName = "libvulkan.so"; +const char *VulkanLibraryName = "libvulkan.1.dylib"; -bool VulkanReplay::CheckVulkanLayer(VulkanLayerFlags &flags, std::vector &myJSONs, - std::vector &otherJSONs) +string GetThisLibPath() { - return false; -} - -void VulkanReplay::InstallVulkanLayer(bool systemLevel) -{ -} + Dl_info info; + if(dladdr(&VulkanLibraryName, &info)) + { + RDCDEBUG("GetThisLibPath = '%s'", info.dli_fname); + return info.dli_fname; + } + return ""; +} \ No newline at end of file diff --git a/renderdoc/driver/vulkan/vk_apple.mm b/renderdoc/driver/vulkan/vk_apple.mm new file mode 100644 index 000000000..a72624ca0 --- /dev/null +++ b/renderdoc/driver/vulkan/vk_apple.mm @@ -0,0 +1,17 @@ +#import + +extern "C" int getCALayerWidth(void *handle) +{ + CALayer *layer = (CALayer *)handle; + assert([layer isKindOfClass:[CALayer class]]); + + return layer.bounds.size.width; +} + +extern "C" int getCALayerHeight(void *handle) +{ + CALayer *layer = (CALayer *)handle; + assert([layer isKindOfClass:[CALayer class]]); + + return layer.bounds.size.height; +} \ No newline at end of file diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 76a5c20a9..5692d2096 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -800,6 +800,11 @@ static const VkExtensionProperties supportedExtensions[] = { { VK_KHR_XLIB_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_SPEC_VERSION, }, +#endif +#ifdef VK_MVK_macos_surface + { + VK_MVK_MACOS_SURFACE_EXTENSION_NAME, VK_MVK_MACOS_SURFACE_SPEC_VERSION, + }, #endif { VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_NV_DEDICATED_ALLOCATION_SPEC_VERSION, diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 45f53147f..2c5929b51 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -1543,6 +1543,13 @@ public: const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface); #endif +#if defined(VK_USE_PLATFORM_MACOS_MVK) + // VK_MVK_macos_surface + VkResult vkCreateMacOSSurfaceMVK(VkInstance instance, + const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface); +#endif + #if defined(VK_USE_PLATFORM_XCB_KHR) // VK_KHR_xcb_surface VkResult vkCreateXcbSurfaceKHR(VkInstance instance, const VkXcbSurfaceCreateInfoKHR *pCreateInfo, diff --git a/renderdoc/driver/vulkan/vk_hookset_defs.h b/renderdoc/driver/vulkan/vk_hookset_defs.h index 4d60ebb36..09b070858 100644 --- a/renderdoc/driver/vulkan/vk_hookset_defs.h +++ b/renderdoc/driver/vulkan/vk_hookset_defs.h @@ -69,6 +69,18 @@ HookDefine3(VkResult, vkGetFenceWin32HandleKHR, VkDevice, device, \ const VkFenceGetWin32HandleInfoKHR *, pGetWin32HandleInfo, HANDLE *, pHandle); +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + +#define HookInitInstance_PlatformSpecific() \ + HookInitExtension(VK_MVK_macos_surface, CreateMacOSSurfaceMVK); + +#define HookInitDevice_PlatformSpecific() + +#define HookDefine_PlatformSpecific() \ + HookDefine4(VkResult, vkCreateMacOSSurfaceMVK, VkInstance, instance, \ + const VkMacOSSurfaceCreateInfoMVK *, pCreateInfo, const VkAllocationCallbacks *, \ + pAllocator, VkSurfaceKHR *, pSurface); + #elif defined(VK_USE_PLATFORM_ANDROID_KHR) #define HookInitInstance_PlatformSpecific() \ @@ -285,6 +297,7 @@ CheckExt(KHR_xcb_surface, VKXX); \ CheckExt(KHR_win32_surface, VKXX); \ CheckExt(KHR_android_surface, VKXX); \ + CheckExt(MVK_macos_surface, VKXX); \ CheckExt(KHR_surface, VKXX); \ CheckExt(EXT_debug_report, VKXX); \ CheckExt(KHR_display, VKXX); \ diff --git a/renderdoc/driver/vulkan/vk_linux.cpp b/renderdoc/driver/vulkan/vk_linux.cpp index 052ec2707..370cc3380 100644 --- a/renderdoc/driver/vulkan/vk_linux.cpp +++ b/renderdoc/driver/vulkan/vk_linux.cpp @@ -23,16 +23,115 @@ ******************************************************************************/ #include "api/replay/renderdoc_replay.h" -#include "api/replay/version.h" -#include "strings/string_utils.h" - -#include -#include -#include #include "vk_core.h" #include "vk_replay.h" +#if defined(VK_USE_PLATFORM_XCB_KHR) + +VkBool32 WrappedVulkan::vkGetPhysicalDeviceXcbPresentationSupportKHR(VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + xcb_connection_t *connection, + xcb_visualid_t visual_id) +{ + return ObjDisp(physicalDevice) + ->GetPhysicalDeviceXcbPresentationSupportKHR(Unwrap(physicalDevice), queueFamilyIndex, + connection, visual_id); +} + +namespace Keyboard +{ +void UseConnection(xcb_connection_t *conn); +} + +VkResult WrappedVulkan::vkCreateXcbSurfaceKHR(VkInstance instance, + const VkXcbSurfaceCreateInfoKHR *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) +{ + // should not come in here at all on replay + RDCASSERT(IsCaptureMode(m_State)); + + VkResult ret = + ObjDisp(instance)->CreateXcbSurfaceKHR(Unwrap(instance), pCreateInfo, pAllocator, pSurface); + + if(ret == VK_SUCCESS) + { + GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + + WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); + + // since there's no point in allocating a full resource record and storing the window + // handle under there somewhere, we just cast. We won't use the resource record for anything + wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->window; + + Keyboard::UseConnection(pCreateInfo->connection); + } + + return ret; +} + +#endif + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + +VkBool32 WrappedVulkan::vkGetPhysicalDeviceXlibPresentationSupportKHR( + VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, Display *dpy, VisualID visualID) +{ + return ObjDisp(physicalDevice) + ->GetPhysicalDeviceXlibPresentationSupportKHR(Unwrap(physicalDevice), queueFamilyIndex, dpy, + visualID); +} + +namespace Keyboard +{ +void CloneDisplay(Display *dpy); +} + +VkResult WrappedVulkan::vkCreateXlibSurfaceKHR(VkInstance instance, + const VkXlibSurfaceCreateInfoKHR *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) +{ + // should not come in here at all on replay + RDCASSERT(IsCaptureMode(m_State)); + + VkResult ret = + ObjDisp(instance)->CreateXlibSurfaceKHR(Unwrap(instance), pCreateInfo, pAllocator, pSurface); + + if(ret == VK_SUCCESS) + { + GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + + WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); + + // since there's no point in allocating a full resource record and storing the window + // handle under there somewhere, we just cast. We won't use the resource record for anything + wrapped->record = (VkResourceRecord *)pCreateInfo->window; + + Keyboard::CloneDisplay(pCreateInfo->dpy); + } + + return ret; +} + +VkResult WrappedVulkan::vkAcquireXlibDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, + VkDisplayKHR display) +{ + // display is not wrapped so we can pass straight through + return ObjDisp(physicalDevice)->AcquireXlibDisplayEXT(Unwrap(physicalDevice), dpy, display); +} + +VkResult WrappedVulkan::vkGetRandROutputDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, + RROutput rrOutput, VkDisplayKHR *pDisplay) +{ + // display is not wrapped so we can pass straight through + return ObjDisp(physicalDevice) + ->GetRandROutputDisplayEXT(Unwrap(physicalDevice), dpy, rrOutput, pDisplay); +} + +#endif + void VulkanReplay::OutputWindow::SetWindowHandle(WindowingData window) { #if ENABLED(RDOC_XLIB) @@ -138,126 +237,11 @@ void VulkanReplay::GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h const char *VulkanLibraryName = "libvulkan.so.1"; -// embedded data file - -extern unsigned char driver_vulkan_renderdoc_json[]; -extern int driver_vulkan_renderdoc_json_len; - -static std::string GenerateJSON(const std::string &sopath) -{ - char *txt = (char *)driver_vulkan_renderdoc_json; - int len = driver_vulkan_renderdoc_json_len; - - string json = string(txt, txt + len); - - const char dllPathString[] = ".\\\\renderdoc.dll"; - - size_t idx = json.find(dllPathString); - - json = json.substr(0, idx) + sopath + json.substr(idx + sizeof(dllPathString) - 1); - - const char majorString[] = "[MAJOR]"; - - idx = json.find(majorString); - while(idx != string::npos) - { - json = json.substr(0, idx) + STRINGIZE(RENDERDOC_VERSION_MAJOR) + - json.substr(idx + sizeof(majorString) - 1); - - idx = json.find(majorString); - } - - const char minorString[] = "[MINOR]"; - - idx = json.find(minorString); - while(idx != string::npos) - { - json = json.substr(0, idx) + STRINGIZE(RENDERDOC_VERSION_MINOR) + - json.substr(idx + sizeof(minorString) - 1); - - idx = json.find(minorString); - } - - return json; -} - -static bool FileExists(const std::string &path) -{ - return access(path.c_str(), F_OK) == 0; -} - -static std::string GetSOFromJSON(const std::string &json) -{ - char *json_string = new char[1024]; - memset(json_string, 0, 1024); - - FILE *f = fopen(json.c_str(), "r"); - - if(f) - { - fread(json_string, 1, 1024, f); - - fclose(f); - } - - string ret = ""; - - // The line is: - // "library_path": "/foo/bar/librenderdoc.so", - char *c = strstr(json_string, "library_path"); - - if(c) - { - c += sizeof("library_path\": \"") - 1; - - char *quote = strchr(c, '"'); - - if(quote) - { - *quote = 0; - ret = c; - } - } - - delete[] json_string; - - return ret; -} - -enum class LayerPath : int -{ - usr, - First = usr, - etc, - home, - Count, -}; - -ITERABLE_OPERATORS(LayerPath); - -string LayerRegistrationPath(LayerPath path) -{ - switch(path) - { - case LayerPath::usr: return "/usr/share/vulkan/implicit_layer.d/renderdoc_capture.json"; - case LayerPath::etc: return "/etc/vulkan/implicit_layer.d/renderdoc_capture.json"; - case LayerPath::home: - { - const char *xdg = getenv("XDG_DATA_HOME"); - if(xdg && FileIO::exists(xdg)) - return string(xdg) + "/vulkan/implicit_layer.d/renderdoc_capture.json"; - - return string(getenv("HOME")) + - "/.local/share/vulkan/implicit_layer.d/renderdoc_capture.json"; - } - default: break; - } - - return ""; -} - string GetThisLibPath() { + // this is a hack, but the only reliable way to find the absolute path to the library. + // dladdr would be fine but it returns the wrong result for symbols in the library + string librenderdoc_path; FILE *f = fopen("/proc/self/maps", "r"); @@ -341,161 +325,3 @@ string GetThisLibPath() return librenderdoc_path; } - -void MakeParentDirs(std::string file) -{ - std::string dir = dirname(file); - - if(dir == "/" || dir.empty()) - return; - - MakeParentDirs(dir); - - if(FileExists(dir)) - return; - - mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); -} - -bool VulkanReplay::CheckVulkanLayer(VulkanLayerFlags &flags, std::vector &myJSONs, - std::vector &otherJSONs) -{ - // see if the user has suppressed all this checking as a "I know what I'm doing" measure - - if(FileExists(string(getenv("HOME")) + "/.renderdoc/ignore_vulkan_layer_issues")) - { - flags = VulkanLayerFlags::ThisInstallRegistered; - return false; - } - - //////////////////////////////////////////////////////////////////////////////////////// - // check that there's only one layer registered, and it points to the same .so file that - // we are running with in this instance of renderdoccmd - - // this is a hack, but the only reliable way to find the absolute path to the library. - // dladdr would be fine but it returns the wrong result for symbols in the library - string librenderdoc_path = GetThisLibPath(); - - // it's impractical to determine whether the currently running RenderDoc build is just a loose - // extract of a tarball or a distribution that decided to put all the files in the same folder, - // and whether or not the library is in ld's searchpath. - // - // Instead we just make the requirement that renderdoc.json will always contain an absolute path - // to the matching librenderdoc.so, so that we can check if it points to this build or another - // build etc. - // - // Note there are three places to register layers - /usr, /etc and /home. The first is reserved - // for distribution packages, so if it conflicts or needs to be deleted for this install to run, - // we can't do that and have to just prompt the user. /etc we can mess with since that's for - // non-distribution packages, but it will need root permissions. - - bool exist[arraydim()]; - bool match[arraydim()]; - - int numExist = 0; - int numMatch = 0; - - for(LayerPath i : values()) - { - exist[(int)i] = FileExists(LayerRegistrationPath(i)); - match[(int)i] = (GetSOFromJSON(LayerRegistrationPath(i)) == librenderdoc_path); - - if(exist[(int)i]) - numExist++; - - if(match[(int)i]) - numMatch++; - } - - flags = VulkanLayerFlags::CouldElevate | VulkanLayerFlags::UpdateAllowed; - - if(numMatch >= 1) - flags |= VulkanLayerFlags::ThisInstallRegistered; - - // if we only have one registration, check that it points to us. If so, we're good - if(numExist == 1 && numMatch == 1) - return false; - - if(exist[(int)LayerPath::usr] && !match[(int)LayerPath::usr]) - otherJSONs.push_back(LayerRegistrationPath(LayerPath::usr)); - - if(exist[(int)LayerPath::etc] && !match[(int)LayerPath::etc]) - otherJSONs.push_back(LayerRegistrationPath(LayerPath::etc)); - - if(exist[(int)LayerPath::home] && !match[(int)LayerPath::home]) - otherJSONs.push_back(LayerRegistrationPath(LayerPath::home)); - - if(!otherJSONs.empty()) - flags |= VulkanLayerFlags::OtherInstallsRegistered; - - if(exist[(int)LayerPath::usr] && match[(int)LayerPath::usr]) - { - // just need to unregister others - } - else - { - myJSONs.push_back(LayerRegistrationPath(LayerPath::etc)); - myJSONs.push_back(LayerRegistrationPath(LayerPath::home)); - } - - if(exist[(int)LayerPath::usr] && !match[(int)LayerPath::usr]) - { - flags = VulkanLayerFlags::Unfixable | VulkanLayerFlags::OtherInstallsRegistered; - otherJSONs.clear(); - otherJSONs.push_back(LayerRegistrationPath(LayerPath::usr)); - } - - return true; -} - -void VulkanReplay::InstallVulkanLayer(bool systemLevel) -{ - std::string homePath = LayerRegistrationPath(LayerPath::home); - - // if we want to install to the system and there's a registration in $HOME, delete it - if(systemLevel && FileExists(homePath)) - { - if(unlink(homePath.c_str()) < 0) - { - const char *const errtext = strerror(errno); - RDCERR("Error removing %s: %s", homePath.c_str(), errtext); - } - } - - std::string etcPath = LayerRegistrationPath(LayerPath::etc); - - // and vice-versa - if(!systemLevel && FileExists(etcPath)) - { - if(unlink(etcPath.c_str()) < 0) - { - const char *const errtext = strerror(errno); - RDCERR("Error removing %s: %s", etcPath.c_str(), errtext); - } - } - - LayerPath idx = systemLevel ? LayerPath::etc : LayerPath::home; - - string jsonPath = LayerRegistrationPath(idx); - string path = GetSOFromJSON(jsonPath); - string libPath = GetThisLibPath(); - - if(path != libPath) - { - MakeParentDirs(jsonPath); - - FILE *f = fopen(jsonPath.c_str(), "w"); - - if(f) - { - fputs(GenerateJSON(libPath).c_str(), f); - - fclose(f); - } - else - { - const char *const errtext = strerror(errno); - RDCERR("Error writing %s: %s", jsonPath.c_str(), errtext); - } - } -} \ No newline at end of file diff --git a/renderdoc/driver/vulkan/vk_posix.cpp b/renderdoc/driver/vulkan/vk_posix.cpp index 3e3b3d501..4bff678fa 100644 --- a/renderdoc/driver/vulkan/vk_posix.cpp +++ b/renderdoc/driver/vulkan/vk_posix.cpp @@ -22,9 +22,15 @@ * THE SOFTWARE. ******************************************************************************/ +#include "api/replay/version.h" +#include "strings/string_utils.h" #include "vk_core.h" #include "vk_replay.h" +#include +#include +#include + bool VulkanReplay::IsOutputWindowVisible(uint64_t id) { if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end()) @@ -44,7 +50,7 @@ void WrappedVulkan::AddRequiredExtensions(bool instance, vector &extensi #define EXPECT_WSI 0 #if(defined(VK_USE_PLATFORM_ANDROID_KHR) || defined(VK_USE_PLATFORM_XCB_KHR) || \ - defined(VK_USE_PLATFORM_XLIB_KHR)) + defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_MACOS_MVK)) #undef EXPECT_WSI #define EXPECT_WSI 1 @@ -88,12 +94,27 @@ void WrappedVulkan::AddRequiredExtensions(bool instance, vector &extensi } #endif +#if defined(VK_USE_PLATFORM_MACOS_MVK) + // must be supported + RDCASSERT(supportedExtensions.find(VK_MVK_MACOS_SURFACE_EXTENSION_NAME) != + supportedExtensions.end()); + + m_SupportedWindowSystems.push_back(WindowingSystem::MacOS); + + // don't add duplicates, application will have added this but just be sure + if(std::find(extensionList.begin(), extensionList.end(), VK_MVK_MACOS_SURFACE_EXTENSION_NAME) == + extensionList.end()) + { + extensionList.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); + } +#endif + #if defined(VK_USE_PLATFORM_ANDROID_KHR) // must be supported RDCASSERT(supportedExtensions.find(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME) != supportedExtensions.end()); - m_SupportedWindowSystems.push_back(WindowingSystem::Android); + m_SupportedWindowSystems.push_back(WindowingSystem::macOS); // don't add duplicates, application will have added this but just be sure if(std::find(extensionList.begin(), extensionList.end(), @@ -121,6 +142,11 @@ void WrappedVulkan::AddRequiredExtensions(bool instance, vector &extensi { RDCWARN("No WSI support - only headless replay allowed."); +#if defined(VK_USE_PLATFORM_MACOS_MVK) + RDCWARN("macOS Output requires the '%s' extension to be present", + VK_MVK_MACOS_SURFACE_EXTENSION_NAME); +#endif + #if defined(VK_USE_PLATFORM_ANDROID_KHR) RDCWARN("Android Output requires the '%s' extension to be present", VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); @@ -155,134 +181,286 @@ void WrappedVulkan::AddRequiredExtensions(bool instance, vector &extensi } } -#if defined(VK_USE_PLATFORM_XCB_KHR) +// defined in vk_linux.cpp or vk_apple.cpp +string GetThisLibPath(); -VkBool32 WrappedVulkan::vkGetPhysicalDeviceXcbPresentationSupportKHR(VkPhysicalDevice physicalDevice, - uint32_t queueFamilyIndex, - xcb_connection_t *connection, - xcb_visualid_t visual_id) +// embedded data file + +extern unsigned char driver_vulkan_renderdoc_json[]; +extern int driver_vulkan_renderdoc_json_len; + +static std::string GenerateJSON(const std::string &sopath) { - return ObjDisp(physicalDevice) - ->GetPhysicalDeviceXcbPresentationSupportKHR(Unwrap(physicalDevice), queueFamilyIndex, - connection, visual_id); -} + char *txt = (char *)driver_vulkan_renderdoc_json; + int len = driver_vulkan_renderdoc_json_len; -namespace Keyboard -{ -void UseConnection(xcb_connection_t *conn); -} + string json = string(txt, txt + len); -VkResult WrappedVulkan::vkCreateXcbSurfaceKHR(VkInstance instance, - const VkXcbSurfaceCreateInfoKHR *pCreateInfo, - const VkAllocationCallbacks *pAllocator, - VkSurfaceKHR *pSurface) -{ - // should not come in here at all on replay - RDCASSERT(IsCaptureMode(m_State)); + const char dllPathString[] = ".\\\\renderdoc.dll"; - VkResult ret = - ObjDisp(instance)->CreateXcbSurfaceKHR(Unwrap(instance), pCreateInfo, pAllocator, pSurface); + size_t idx = json.find(dllPathString); - if(ret == VK_SUCCESS) + json = json.substr(0, idx) + sopath + json.substr(idx + sizeof(dllPathString) - 1); + + const char majorString[] = "[MAJOR]"; + + idx = json.find(majorString); + while(idx != string::npos) { - GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + json = json.substr(0, idx) + STRINGIZE(RENDERDOC_VERSION_MAJOR) + + json.substr(idx + sizeof(majorString) - 1); - WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); - - // since there's no point in allocating a full resource record and storing the window - // handle under there somewhere, we just cast. We won't use the resource record for anything - wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->window; - - Keyboard::UseConnection(pCreateInfo->connection); + idx = json.find(majorString); } + const char minorString[] = "[MINOR]"; + + idx = json.find(minorString); + while(idx != string::npos) + { + json = json.substr(0, idx) + STRINGIZE(RENDERDOC_VERSION_MINOR) + + json.substr(idx + sizeof(minorString) - 1); + + idx = json.find(minorString); + } + + return json; +} + +static bool FileExists(const std::string &path) +{ + return access(path.c_str(), F_OK) == 0; +} + +static std::string GetSOFromJSON(const std::string &json) +{ + char *json_string = new char[1024]; + memset(json_string, 0, 1024); + + FILE *f = fopen(json.c_str(), "r"); + + if(f) + { + fread(json_string, 1, 1024, f); + + fclose(f); + } + + string ret = ""; + + // The line is: + // "library_path": "/foo/bar/librenderdoc.so", + char *c = strstr(json_string, "library_path"); + + if(c) + { + c += sizeof("library_path\": \"") - 1; + + char *quote = strchr(c, '"'); + + if(quote) + { + *quote = 0; + ret = c; + } + } + + delete[] json_string; + return ret; } -#endif - -#if defined(VK_USE_PLATFORM_XLIB_KHR) - -VkBool32 WrappedVulkan::vkGetPhysicalDeviceXlibPresentationSupportKHR( - VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, Display *dpy, VisualID visualID) +enum class LayerPath : int { - return ObjDisp(physicalDevice) - ->GetPhysicalDeviceXlibPresentationSupportKHR(Unwrap(physicalDevice), queueFamilyIndex, dpy, - visualID); -} + usr, + First = usr, + etc, + home, + Count, +}; -namespace Keyboard +ITERABLE_OPERATORS(LayerPath); + +string LayerRegistrationPath(LayerPath path) { -void CloneDisplay(Display *dpy); -} - -VkResult WrappedVulkan::vkCreateXlibSurfaceKHR(VkInstance instance, - const VkXlibSurfaceCreateInfoKHR *pCreateInfo, - const VkAllocationCallbacks *pAllocator, - VkSurfaceKHR *pSurface) -{ - // should not come in here at all on replay - RDCASSERT(IsCaptureMode(m_State)); - - VkResult ret = - ObjDisp(instance)->CreateXlibSurfaceKHR(Unwrap(instance), pCreateInfo, pAllocator, pSurface); - - if(ret == VK_SUCCESS) + switch(path) { - GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + case LayerPath::usr: return "/usr/share/vulkan/implicit_layer.d/renderdoc_capture.json"; + case LayerPath::etc: return "/etc/vulkan/implicit_layer.d/renderdoc_capture.json"; + case LayerPath::home: + { + const char *xdg = getenv("XDG_DATA_HOME"); + if(xdg && FileIO::exists(xdg)) + return string(xdg) + "/vulkan/implicit_layer.d/renderdoc_capture.json"; - WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); - - // since there's no point in allocating a full resource record and storing the window - // handle under there somewhere, we just cast. We won't use the resource record for anything - wrapped->record = (VkResourceRecord *)pCreateInfo->window; - - Keyboard::CloneDisplay(pCreateInfo->dpy); + return string(getenv("HOME")) + + "/.local/share/vulkan/implicit_layer.d/renderdoc_capture.json"; + } + default: break; } - return ret; + return ""; } -VkResult WrappedVulkan::vkAcquireXlibDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, - VkDisplayKHR display) +void MakeParentDirs(std::string file) { - // display is not wrapped so we can pass straight through - return ObjDisp(physicalDevice)->AcquireXlibDisplayEXT(Unwrap(physicalDevice), dpy, display); + std::string dir = dirname(file); + + if(dir == "/" || dir.empty()) + return; + + MakeParentDirs(dir); + + if(FileExists(dir)) + return; + + mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); } -VkResult WrappedVulkan::vkGetRandROutputDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, - RROutput rrOutput, VkDisplayKHR *pDisplay) +bool VulkanReplay::CheckVulkanLayer(VulkanLayerFlags &flags, std::vector &myJSONs, + std::vector &otherJSONs) { - // display is not wrapped so we can pass straight through - return ObjDisp(physicalDevice) - ->GetRandROutputDisplayEXT(Unwrap(physicalDevice), dpy, rrOutput, pDisplay); -} + // see if the user has suppressed all this checking as a "I know what I'm doing" measure -#endif - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) -VkResult WrappedVulkan::vkCreateAndroidSurfaceKHR(VkInstance instance, - const VkAndroidSurfaceCreateInfoKHR *pCreateInfo, - const VkAllocationCallbacks *pAllocator, - VkSurfaceKHR *pSurface) -{ - // should not come in here at all on replay - RDCASSERT(IsCaptureMode(m_State)); - - VkResult ret = ObjDisp(instance)->CreateAndroidSurfaceKHR(Unwrap(instance), pCreateInfo, - pAllocator, pSurface); - - if(ret == VK_SUCCESS) + if(FileExists(string(getenv("HOME")) + "/.renderdoc/ignore_vulkan_layer_issues")) { - GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); - - WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); - - // since there's no point in allocating a full resource record and storing the window - // handle under there somewhere, we just cast. We won't use the resource record for anything - wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->window; + flags = VulkanLayerFlags::ThisInstallRegistered; + return false; } - return ret; + //////////////////////////////////////////////////////////////////////////////////////// + // check that there's only one layer registered, and it points to the same .so file that + // we are running with in this instance of renderdoccmd + + string librenderdoc_path = GetThisLibPath(); + + if(librenderdoc_path.empty() || !FileExists(librenderdoc_path)) + { + RDCERR("Couldn't determine current library path!"); + flags = VulkanLayerFlags::ThisInstallRegistered; + return false; + } + + // it's impractical to determine whether the currently running RenderDoc build is just a loose + // extract of a tarball or a distribution that decided to put all the files in the same folder, + // and whether or not the library is in ld's searchpath. + // + // Instead we just make the requirement that renderdoc.json will always contain an absolute path + // to the matching librenderdoc.so, so that we can check if it points to this build or another + // build etc. + // + // Note there are three places to register layers - /usr, /etc and /home. The first is reserved + // for distribution packages, so if it conflicts or needs to be deleted for this install to run, + // we can't do that and have to just prompt the user. /etc we can mess with since that's for + // non-distribution packages, but it will need root permissions. + + bool exist[arraydim()]; + bool match[arraydim()]; + + int numExist = 0; + int numMatch = 0; + + for(LayerPath i : values()) + { + exist[(int)i] = FileExists(LayerRegistrationPath(i)); + match[(int)i] = (GetSOFromJSON(LayerRegistrationPath(i)) == librenderdoc_path); + + if(exist[(int)i]) + numExist++; + + if(match[(int)i]) + numMatch++; + } + + flags = VulkanLayerFlags::CouldElevate | VulkanLayerFlags::UpdateAllowed; + + if(numMatch >= 1) + flags |= VulkanLayerFlags::ThisInstallRegistered; + + // if we only have one registration, check that it points to us. If so, we're good + if(numExist == 1 && numMatch == 1) + return false; + + if(exist[(int)LayerPath::usr] && !match[(int)LayerPath::usr]) + otherJSONs.push_back(LayerRegistrationPath(LayerPath::usr)); + + if(exist[(int)LayerPath::etc] && !match[(int)LayerPath::etc]) + otherJSONs.push_back(LayerRegistrationPath(LayerPath::etc)); + + if(exist[(int)LayerPath::home] && !match[(int)LayerPath::home]) + otherJSONs.push_back(LayerRegistrationPath(LayerPath::home)); + + if(!otherJSONs.empty()) + flags |= VulkanLayerFlags::OtherInstallsRegistered; + + if(exist[(int)LayerPath::usr] && match[(int)LayerPath::usr]) + { + // just need to unregister others + } + else + { + myJSONs.push_back(LayerRegistrationPath(LayerPath::etc)); + myJSONs.push_back(LayerRegistrationPath(LayerPath::home)); + } + + if(exist[(int)LayerPath::usr] && !match[(int)LayerPath::usr]) + { + flags = VulkanLayerFlags::Unfixable | VulkanLayerFlags::OtherInstallsRegistered; + otherJSONs.clear(); + otherJSONs.push_back(LayerRegistrationPath(LayerPath::usr)); + } + + return true; } -#endif + +void VulkanReplay::InstallVulkanLayer(bool systemLevel) +{ + std::string homePath = LayerRegistrationPath(LayerPath::home); + + // if we want to install to the system and there's a registration in $HOME, delete it + if(systemLevel && FileExists(homePath)) + { + if(unlink(homePath.c_str()) < 0) + { + const char *const errtext = strerror(errno); + RDCERR("Error removing %s: %s", homePath.c_str(), errtext); + } + } + + std::string etcPath = LayerRegistrationPath(LayerPath::etc); + + // and vice-versa + if(!systemLevel && FileExists(etcPath)) + { + if(unlink(etcPath.c_str()) < 0) + { + const char *const errtext = strerror(errno); + RDCERR("Error removing %s: %s", etcPath.c_str(), errtext); + } + } + + LayerPath idx = systemLevel ? LayerPath::etc : LayerPath::home; + + string jsonPath = LayerRegistrationPath(idx); + string path = GetSOFromJSON(jsonPath); + string libPath = GetThisLibPath(); + + if(path != libPath) + { + MakeParentDirs(jsonPath); + + FILE *f = fopen(jsonPath.c_str(), "w"); + + if(f) + { + fputs(GenerateJSON(libPath).c_str(), f); + + fclose(f); + } + else + { + const char *const errtext = strerror(errno); + RDCERR("Error writing %s: %s", jsonPath.c_str(), errtext); + } + } +} \ No newline at end of file diff --git a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp index f89e9f38d..42d817825 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_device_funcs.cpp @@ -152,9 +152,10 @@ ReplayStatus WrappedVulkan::Initialise(VkInitParams ¶ms, uint64_t sectionVer { if(*it == "VK_KHR_xlib_surface" || *it == "VK_KHR_xcb_surface" || *it == "VK_KHR_wayland_surface" || *it == "VK_KHR_mir_surface" || - *it == "VK_KHR_android_surface" || *it == "VK_KHR_win32_surface" || - *it == "VK_KHR_display" || *it == "VK_EXT_direct_mode_display" || - *it == "VK_EXT_acquire_xlib_display" || *it == "VK_EXT_display_surface_counter") + *it == "VK_MVK_macos_surface " || *it == "VK_KHR_android_surface" || + *it == "VK_KHR_win32_surface" || *it == "VK_KHR_display" || + *it == "VK_EXT_direct_mode_display" || *it == "VK_EXT_acquire_xlib_display" || + *it == "VK_EXT_display_surface_counter") { it = params.Extensions.erase(it); }