From 69a7108d75fe79b1e175900b2adbadddbfd6f96f Mon Sep 17 00:00:00 2001 From: Antonio Caggiano Date: Wed, 6 Nov 2024 18:02:08 -0700 Subject: [PATCH] tocpp: Support wayland (#1870) --- framework/decode/vulkan_cpp_consumer_base.cpp | 65 ++++ .../decode/vulkan_cpp_loader_generator.cpp | 9 +- .../decode/vulkan_cpp_template_strings.h | 281 ++++++++++++++++++ framework/decode/vulkan_cpp_utilities.h | 6 +- tools/tocpp/README.md | 23 +- 5 files changed, 367 insertions(+), 17 deletions(-) diff --git a/framework/decode/vulkan_cpp_consumer_base.cpp b/framework/decode/vulkan_cpp_consumer_base.cpp index d5df045a6..d2890ad84 100644 --- a/framework/decode/vulkan_cpp_consumer_base.cpp +++ b/framework/decode/vulkan_cpp_consumer_base.cpp @@ -94,6 +94,12 @@ void VulkanCppConsumerBase::WriteMainHeader() case GfxToCppPlatform::PLATFORM_XCB: fprintf(main_file_, "%s", sXcbOutputMainStart); break; + case GfxToCppPlatform::PLATFORM_WAYLAND: + fprintf(main_file_, "%s", sWaylandOutputMainStart); + break; + default: + GFXRECON_LOG_FATAL("Failed to write main header: Invalid platform (%d)", platform_); + break; } } @@ -110,6 +116,12 @@ void VulkanCppConsumerBase::WriteMainFooter() case GfxToCppPlatform::PLATFORM_XCB: fprintf(main_file_, "%s", sXcbOutputMainEnd); break; + case GfxToCppPlatform::PLATFORM_WAYLAND: + fprintf(main_file_, "%s", sWaylandOutputMainEnd); + break; + default: + GFXRECON_LOG_FATAL("Failed to write main footer: Invalid platform (%d)", platform_); + break; } } @@ -146,6 +158,17 @@ bool VulkanCppConsumerBase::WriteGlobalHeaderFile() sXcbOutputHeader, sCommonOutputHeaderFunctions); break; + case GfxToCppPlatform::PLATFORM_WAYLAND: + fprintf(header_file, + "%s%s%s%s", + sWaylandOutputHeadersPlatform, + sCommonHeaderOutputHeaders, + sWaylandOutputHeader, + sCommonOutputHeaderFunctions); + break; + default: + GFXRECON_LOG_FATAL("Failed to write global header file: Invalid platform (%d)", platform_); + break; } PrintToFile(header_file, "extern %s;\n", GfxToCppVariable::GenerateStringVec(variable_data_)); @@ -188,6 +211,12 @@ void VulkanCppConsumerBase::PrintOutCMakeFile() case GfxToCppPlatform::PLATFORM_XCB: fprintf(cmake_file, "%s", sXcbCMakeFile); break; + case GfxToCppPlatform::PLATFORM_WAYLAND: + fprintf(cmake_file, "%s", sWaylandCMakeFile); + break; + default: + GFXRECON_LOG_FATAL("Failed to print out CMake file: Unknown platform (%d)", platform_); + break; } util::platform::FileClose(cmake_file); } @@ -273,6 +302,22 @@ void VulkanCppConsumerBase::PrintOutGlobalVar() delete[] formatted_output_override_method; break; } + case GfxToCppPlatform::PLATFORM_WAYLAND: + { + int size = snprintf(NULL, 0, sWaylandOutputOverrideMethod, window_width_, window_height_); + char* formatted_output_override_method = new char[size + 2]; + snprintf(formatted_output_override_method, + size + 2, + sWaylandOutputOverrideMethod, + window_width_, + window_height_); + fputs(formatted_output_override_method, global_file); + delete[] formatted_output_override_method; + break; + } + default: + GFXRECON_LOG_FATAL("Failed to print out global var: Invalid platform (%d)", platform_); + break; } PrintToFile(global_file, "%s;\n", GfxToCppVariable::GenerateStringVec(variable_data_)); @@ -1703,6 +1748,26 @@ void VulkanCppConsumerBase::GenerateSurfaceCreation(GfxToCppPlatform plat surface_create_func_call = "vkCreateXcbSurfaceKHR"; break; } + case GfxToCppPlatform::PLATFORM_WAYLAND: + { + VkWaylandSurfaceCreateInfoKHR wayland_struct_info = {}; + Decoded_VkWaylandSurfaceCreateInfoKHR decoded_wayland_info = {}; + + if (platform_ == platform) + { + wayland_struct_info = + *reinterpret_cast*>(pSurfaceCreateInfo) + ->GetPointer(); + } + wayland_struct_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + create_info_struct_var_name = GenerateStruct_VkWaylandSurfaceCreateInfoKHR( + stream_create_info, &wayland_struct_info, &decoded_wayland_info, *this); + surface_create_func_call = "vkCreateWaylandSurfaceKHR"; + break; + } + default: + GFXRECON_LOG_FATAL("Failed to generate surface creation: Invalid platform (%d)", platform_); + break; } fprintf(file, "\n%s", stream_create_info.str().c_str()); AddKnownVariables("VkSurfaceKHR", surface_var_name, pSurface); diff --git a/framework/decode/vulkan_cpp_loader_generator.cpp b/framework/decode/vulkan_cpp_loader_generator.cpp index a6c2454a0..8168332e2 100644 --- a/framework/decode/vulkan_cpp_loader_generator.cpp +++ b/framework/decode/vulkan_cpp_loader_generator.cpp @@ -65,18 +65,21 @@ void VulkanCppLoaderGenerator::WriteOutLoaderGenerator(const std::string& outDir case GfxToCppPlatform::PLATFORM_XCB: fprintf(pfn_src_file, "#define VK_USE_PLATFORM_XCB_KHR\n"); break; + case GfxToCppPlatform::PLATFORM_WAYLAND: + fprintf(pfn_src_file, "#define VK_USE_PLATFORM_WAYLAND_KHR\n"); + break; #if 0 // TODO: implement these platforms case GfxToCppPlatform::PLATFORM_MACOS: fprintf(pfn_src_file, "#define VK_USE_PLATFORM_METAL_EXT\n"); break; - case GfxToCppPlatform::PLATFORM_WAYLAND: - fprintf(pfn_src_file, "#define VK_USE_PLATFORM_WAYLAND_KHR\n"); - break; case GfxToCppPlatform::PLATFORM_XLIB: fprintf(pfn_src_file, "#define VK_USE_PLATFORM_XLIB_KHR\n"); break; #endif + default: + GFXRECON_LOG_FATAL("Failed to write out loader generator: Invalid platform (%d)", platform); + break; } fprintf(pfn_src_file, "#include \"loader.h\"\n\n"); diff --git a/framework/decode/vulkan_cpp_template_strings.h b/framework/decode/vulkan_cpp_template_strings.h index d05e9f698..9d0521678 100644 --- a/framework/decode/vulkan_cpp_template_strings.h +++ b/framework/decode/vulkan_cpp_template_strings.h @@ -324,6 +324,287 @@ target_link_libraries(vulkan_app vulkan xcb) )"; // End of Xcb template strings +// Beginning of Wayland template strings +static const char* sWaylandOutputMainStart = R"( +#include "global_var.h" + +int main() { +)"; + +static const char* sWaylandOutputMainEnd = R"( + return 0; +} +)"; + +static const char* sWaylandOutputHeadersPlatform = R"( +// This file is a generated source, follow the instructions under tools/tocpp/README.md to build. +#define VK_USE_PLATFORM_WAYLAND_KHR +)"; + +static const char* sWaylandOutputHeader = R"( +#include +#include + +#define VK_CALL_CHECK(VK_CALL, VK_RESULT) \ + LogVkError(#VK_CALL, (VK_CALL), __FILE__, __LINE__, VK_RESULT) + +struct WaylandApp { + WaylandApp(uint32_t w, uint32_t h) { + width = w; + height = h; + } + + ~WaylandApp(); + + uint32_t width { 320 }; + uint32_t height { 240 }; + + struct { + wl_display *display { nullptr }; + wl_compositor *compositor { nullptr }; + wl_surface *surface { nullptr }; + int shm_fd {-1}; + wl_shm *shm { nullptr }; + wl_shm_pool *shm_pool { nullptr }; + wl_buffer *buffer { nullptr }; + } wl; + + struct { + xdg_wm_base *wm_base { nullptr }; + xdg_surface *surface { nullptr }; + xdg_toplevel *toplevel { nullptr }; + } xdg; +}; + +extern WaylandApp appdata; + +extern void OverrideVkWaylandSurfaceCreateInfoKHR(VkWaylandSurfaceCreateInfoKHR* createInfo, + struct WaylandApp& appdata); +extern void UpdateWindowSize(uint32_t width, + uint32_t height, + uint32_t pre_transform, + struct WaylandApp& appdata); +extern void LogVkError(const char* function, + VkResult returnValue, + const char* file, + int line, + VkResult capturedReturnValue); +extern size_t LoadBinaryData(const char* filename, + size_t file_offset, + void* buffer, + size_t offset, + size_t data_size, + struct WaylandApp& appdata); +)"; + +static const char* sWaylandOutputOverrideMethod = R"( +#include +#include +#include +#include +#include + +WaylandApp::~WaylandApp() +{ + wl_buffer_destroy(appdata.wl.buffer); + wl_shm_pool_destroy(appdata.wl.shm_pool); + wl_shm_destroy(appdata.wl.shm); + close(appdata.wl.shm_fd); + xdg_wm_base_destroy(appdata.xdg.wm_base); + xdg_toplevel_destroy(appdata.xdg.toplevel); + xdg_surface_destroy(appdata.xdg.surface); + wl_surface_destroy(appdata.wl.surface); + wl_compositor_destroy(appdata.wl.compositor); + wl_display_disconnect(appdata.wl.display); +} + +static void Randname(char *buf) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int CreateShmFile(void) +{ + int retries = 100; + do { + char name[] = "/wl_shm-XXXXXX"; + Randname(name + sizeof(name) - 7); + --retries; + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + return -1; +} + +int AllocateShmFile(size_t size) +{ + int fd = CreateShmFile(); + if (fd < 0) + return -1; + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + return fd; +} + +static void +RegistryHandleGlobal(void *data, wl_registry *registry, uint32_t name, const char* interface, uint32_t version) +{ + WaylandApp *app = (WaylandApp *)data; + if (strcmp(interface, wl_compositor_interface.name) == 0) + { + app->wl.compositor = (wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } + if (strcmp(interface, wl_shm_interface.name) == 0) + { + app->wl.shm = (wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, 1); + } + if (strcmp(interface, xdg_wm_base_interface.name) == 0) + { + app->xdg.wm_base = (xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } +} + +static void RegistryHandleGlobalRemove(void *data, wl_registry *registry, uint32_t name) +{ +} + +static const wl_registry_listener registry_listener = { + .global = RegistryHandleGlobal, + .global_remove = RegistryHandleGlobalRemove, +}; + +static void XdgSurfaceConfigure(void *data, xdg_surface *xdg_surface, uint32_t serial) {} + +static const xdg_surface_listener surface_listener = { + .configure = XdgSurfaceConfigure, +}; + +void OverrideVkWaylandSurfaceCreateInfoKHR(VkWaylandSurfaceCreateInfoKHR* createInfo, WaylandApp& appdata) +{ + if (appdata.wl.display == nullptr) { + // Open the connection to the wayland server + appdata.wl.display = wl_display_connect(nullptr); + + if (appdata.wl.display == nullptr) + { + printf("Cannot open display\n"); + exit(1); + } + + wl_registry* registry = wl_display_get_registry(appdata.wl.display); + wl_registry_add_listener(registry, ®istry_listener, &appdata); + wl_display_roundtrip(appdata.wl.display); + wl_registry_destroy(registry); + + appdata.wl.surface = wl_compositor_create_surface(appdata.wl.compositor); + + appdata.xdg.surface = xdg_wm_base_get_xdg_surface(appdata.xdg.wm_base, appdata.wl.surface); + xdg_surface_add_listener(appdata.xdg.surface, &surface_listener, &appdata); + appdata.xdg.toplevel = xdg_surface_get_toplevel(appdata.xdg.surface); + xdg_toplevel_set_title(appdata.xdg.toplevel, "vulkan-app"); + + const int width = appdata.width; + const int height = appdata.height; + const int stride = width * 4; + const int shm_pool_size = height * stride * 2; + + appdata.wl.shm_fd = AllocateShmFile(shm_pool_size); + + appdata.wl.shm_pool = wl_shm_create_pool(appdata.wl.shm, appdata.wl.shm_fd, shm_pool_size); + + int index = 0; + int offset = height * stride * index; + appdata.wl.buffer = wl_shm_pool_create_buffer( + appdata.wl.shm_pool, offset, width, height, stride, WL_SHM_FORMAT_XRGB8888); + + wl_surface_attach(appdata.wl.surface, appdata.wl.buffer, 0, 0); + wl_surface_damage(appdata.wl.surface, 0, 0, UINT32_MAX, UINT32_MAX); + wl_surface_commit(appdata.wl.surface); + } + + createInfo->sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo->display = appdata.wl.display; + createInfo->surface = appdata.wl.surface; +} + +void UpdateWindowSize(uint32_t width, uint32_t height, uint32_t pretransform, WaylandApp& appdata) +{ + appdata.width = width; + appdata.height = height; +} + +size_t LoadBinaryData(const char* filename, + size_t file_offset, + void* buffer, + size_t offset, + size_t data_size, + WaylandApp& appdata) +{ + (void)appdata; // Unused + + FILE* fp = fopen(filename, "rb"); + if (fp == nullptr) + { + throw std::runtime_error("Error while opening file: " + std::string(filename)); + } + + fseek(fp, file_offset, SEEK_SET); + size_t read_size = fread((uint8_t *)buffer + offset, sizeof(uint8_t), data_size, fp); + if (read_size != data_size) + { + fclose(fp); + throw std::runtime_error("Error while reading file: " + std::string(filename)); + } + + fclose(fp); + return read_size; +} + +WaylandApp appdata(%d, %d); +)"; + +static const char* sWaylandCMakeFile = R"( +cmake_minimum_required(VERSION 3.7) +project(vulkan_app) +set (CMAKE_CXX_STANDARD 11) + +find_program(WAYLAND_SCANNER NAMES wayland-scanner REQUIRED) +set(XDG_SHELL_PROTOCOL_C ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-protocol.c) +set(XDG_SHELL_CLIENT_PROTOCOL_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h) +set(XDG_SHELL_XML /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml) +add_custom_command( + OUTPUT ${XDG_SHELL_CLIENT_PROTOCOL_H} ${XDG_SHELL_PROTOCOL_C} + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_SHELL_XML} ${XDG_SHELL_CLIENT_PROTOCOL_H} + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_SHELL_XML} ${XDG_SHELL_PROTOCOL_C} + DEPENDS ${XDG_SHELL_XML} +) + +include_directories(${PROJECT_SOURCE_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}) +file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp) +file(GLOB MAIN_FILE ${PROJECT_SOURCE_DIR}/*.cpp) +add_executable(vulkan_app ${SRC_FILES} ${MAIN_FILE} ${XDG_SHELL_PROTOCOL_C}) +find_package(Vulkan REQUIRED) +find_package(PkgConfig) +pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) +target_link_libraries(vulkan_app Vulkan::Vulkan ${WAYLAND_CLIENT_LIBRARIES}) +)"; +// End of Wayland template strings + // Beginning of Android template strings static const char* sAndroidOutputHeadersPlatform = R"( // This file is a generated source, follow the instructions under tools/tocpp/README.md to build. diff --git a/framework/decode/vulkan_cpp_utilities.h b/framework/decode/vulkan_cpp_utilities.h index a0686128c..2a60e7b7f 100644 --- a/framework/decode/vulkan_cpp_utilities.h +++ b/framework/decode/vulkan_cpp_utilities.h @@ -35,7 +35,7 @@ enum class GfxToCppPlatform { PLATFORM_ANDROID, // PLATFORM_MACOS, - // PLATFORM_WAYLAND, + PLATFORM_WAYLAND, PLATFORM_WIN32, PLATFORM_XCB, // PLATFORM_XLIB, @@ -50,7 +50,7 @@ struct PlatformTargetInfo const std::map kTargetPlatforms = { { GfxToCppPlatform::PLATFORM_ANDROID, { "android", VK_KHR_ANDROID_SURFACE_EXTENSION_NAME } }, //{ GfxToCppPlatform::PLATFORM_MACOS, {"macos", VK_EXT_METAL_SURFACE_EXTENSION_NAME } }, - //{ GfxToCppPlatform::PLATFORM_WAYLAND, {"wayland", VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME } }, + { GfxToCppPlatform::PLATFORM_WAYLAND, { "wayland", VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME } }, { GfxToCppPlatform::PLATFORM_WIN32, { "win32", VK_KHR_WIN32_SURFACE_EXTENSION_NAME } }, { GfxToCppPlatform::PLATFORM_XCB, { "xcb", VK_KHR_XCB_SURFACE_EXTENSION_NAME } }, //{ GfxToCppPlatform::PLATFORM_XLIB, {"xlib", VK_KHR_XLIB_SURFACE_EXTENSION_NAME } }, @@ -59,7 +59,7 @@ const std::map kTargetPl const std::map kTargetPlatformByName = { { "android", GfxToCppPlatform::PLATFORM_ANDROID }, //{ "macos", GfxToCppPlatform::PLATFORM_MACOS}, - //{ "wayland", GfxToCppPlatform::PLATFORM_WAYLAND}, + { "wayland", GfxToCppPlatform::PLATFORM_WAYLAND }, { "win32", GfxToCppPlatform::PLATFORM_WIN32 }, { "xcb", GfxToCppPlatform::PLATFORM_XCB }, //{ "xlib", GfxToCppPlatform::PLATFORM_XLIB}, diff --git a/tools/tocpp/README.md b/tools/tocpp/README.md index 607fd701c..01654caa9 100644 --- a/tools/tocpp/README.md +++ b/tools/tocpp/README.md @@ -19,7 +19,7 @@ These limitations are discussed below. Because the ToCpp tool is still not in a complete state, it has the following known issues which will be worked on over time: -* The generated cpp code only executes on either Android or Linux (XCB) +* The generated cpp code only executes on either Android or Linux (XCB or Wayland) * The generated cpp code does not support multi-window capture on Android * It does not currently support Ray Tracing contents in a capture * The generated cpp code expects a system exactly like the capture system: @@ -62,7 +62,7 @@ gfxrecon-tocpp | -h
--help | Optional | Print Usage information and exit. | | -o
--output | Required | Directory path where the output will be generated into. | | -s --captured-swapchain | Optional | Use the swapchain as it was captured during toCpp replay instead of using the "Virtual Swapchain" path. | -| -t
--target | Optional | Type of target platform to generate the Vulkan source.
Available Platforms: android, xcb | +| -t
--target | Optional | Type of target platform to generate the Vulkan source.
Available Platforms: android, xcb, wayland | | -v
--version | Optional | Print version information and exit. | ## Generate Source From a Capture @@ -119,26 +119,27 @@ adb install ./app/build/outputs/apk/debug/app-debug.apk --- -### Linux (XCB) +### Linux ```sh # In the root of GFXReconstruct # Where 'build' in this case is the build folder used to generate the source -./build/tools/tocpp/gfxrecon-tocpp -t xcb -o out_xcb_capture ./capture.gfxr +./build/tools/tocpp/gfxrecon-tocpp -t xcb -o out_capture ./capture.gfxr ``` -* `-t xcb` indicates that we generate for XCB platform. -* `-o out_xcb_capture` specify the directory where the generated files will be +* `-t xcb` indicates that we generate for XCB platform. Alternatively, you could use `-t wayland` to generate sources for + Wayland. +* `-o out_capture` specify the directory where the generated files will be placed. -At the end the output directory (`out_xcb_capture`) contains the generated +At the end the output directory (`out_capture`) contains the generated `capture.cpp` and the saved image data (`*.bin`). -### Build the source for desktop (XCB) +#### Build the source for desktop ```sh #cd -cd out_xcb_capture +cd out_capture # Generate build contents cmake -H. -Bbuild @@ -147,10 +148,10 @@ cmake -H. -Bbuild cmake --build build ``` -### Run the source for desktop (XCB) +#### Run the source for desktop ```sh -# Inside the output directory (`out_xcb_capture`) +# Inside the output directory (`out_capture`) ./build/vulkan_app ```