diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bb3ee773..082a6f5a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ option(ENABLE_PYRENDERDOC "Enable renderdoc python modules" ON) option(ENABLE_XLIB "Enable xlib windowing support" ON) option(ENABLE_XCB "Enable xcb windowing support" ON) +option(ENABLE_WAYLAND "Enable experimental wayland windowing support" OFF) option(ENABLE_GGP "Enable GGP support" OFF) option(ENABLE_ASAN "Enable address sanitizer" OFF) @@ -182,6 +183,21 @@ if(ENABLE_TSAN) message(STATUS "Enabling thread sanitizer - may cause issues if capturing, only recommended for use with replay only") endif() +if(ENABLE_WAYLAND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(PKG_WAYLAND QUIET wayland-client) + + if(PKG_WAYLAND_FOUND) + if(NOT ENABLE_GLES) + message(WARNING "On Wayland GLES support is required for EGL, disabling GL support") + set(ENABLE_GL OFF CACHE BOOL "" FORCE) + endif() + else() + message(WARNING "Wayland not found - disabling support") + set(ENABLE_WAYLAND OFF CACHE BOOL "" FORCE) + endif() +endif() + if(WIN32) message(FATAL_ERROR "CMake is not needed on Windows, just open and build renderdoc.sln") endif() @@ -328,6 +344,10 @@ elseif(UNIX) if(ENABLE_XCB) add_definitions(-DRENDERDOC_WINDOWING_XCB) endif() + + if(ENABLE_WAYLAND) + add_definitions(-DRENDERDOC_WINDOWING_WAYLAND) + endif() endif() add_subdirectory(renderdoc) @@ -379,5 +399,9 @@ if(UNIX AND NOT ANDROID AND NOT APPLE AND NOT ENABLE_GGP) if(ENABLE_XCB) message(STATUS " - XCB") endif() + + if(ENABLE_WAYLAND) + message(STATUS " - Wayland") + endif() endif() diff --git a/docs/in_application_api.rst b/docs/in_application_api.rst index bb28bd620..de0c586c1 100644 --- a/docs/in_application_api.rst +++ b/docs/in_application_api.rst @@ -373,10 +373,11 @@ The path follows the template set in :cpp:func:`SetCaptureFilePathTemplate` so i * For D3D11 it must be the ``ID3D11Device`` device object. * For D3D12 it must be the ``ID3D12Device`` device object. - * For OpenGL it must be the ``HGLRC`` or ``GLXContext`` context object. + * For OpenGL it must be the ``HGLRC``, ``GLXContext``, or ``EGLContext`` context object. + * For OpenGLES it must be the ``EGLContext`` context object. * For Vulkan it must be the dispatch table pointer within the ``VkInstance``. This is a pointer-sized value at the location pointed to by the ``VkInstance``. NOTE - this is not the actual ``VkInstance`` pointer itself. You can use the RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE helper macro defined in the renderdoc header to obtain this pointer from any VkInstance. - ``RENDERDOC_WindowHandle`` is a typedef to ``void *``. It is the platform specific ``HWND``, ``xcb_window_t``, or Xlib ``Window``. + ``RENDERDOC_WindowHandle`` is a typedef to ``void *``. It is the platform specific Windows ``HWND``, Xcb ``xcb_window_t``, Xlib ``Window`` / ``Drawable``, Wayland ``wl_surface*``, or Android ``ANativeWindow*``. .. cpp:function:: void StartFrameCapture(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle) diff --git a/qrenderdoc/CMakeLists.txt b/qrenderdoc/CMakeLists.txt index 624300855..9b0003a4b 100644 --- a/qrenderdoc/CMakeLists.txt +++ b/qrenderdoc/CMakeLists.txt @@ -168,6 +168,13 @@ if(CMAKE_COMPILER_IS_GNUCXX) "QMAKE_CXXFLAGS+=-Wno-unknown-warning -Wno-implicit-fallthrough -Wno-cast-function-type -Wno-stringop-truncation\n") endif() +if(ENABLE_WAYLAND) + message(WARNING "!!!! Using the Wayland Qt platform in qrenderdoc is NOT SUPPORTED !!!!") + file(APPEND + ${CMAKE_BINARY_DIR}/qrenderdoc/qrenderdoc_cmake.pri + "DEFINES+=RENDERDOC_WINDOWING_WAYLAND\n") +endif() + # propagate build version info. Lots of escaping needed here to pass ""s into the define value if(BUILD_VERSION_STABLE) file(APPEND diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index d79571275..1af01d345 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -849,22 +849,48 @@ void CaptureContext::LoadCaptureThreaded(const QString &captureFile, const Repla #if defined(RENDERDOC_PLATFORM_WIN32) m_CurWinSystem = WindowingSystem::Win32; #elif defined(RENDERDOC_PLATFORM_LINUX) - m_CurWinSystem = WindowingSystem::Xlib; + m_CurWinSystem = WindowingSystem::Unknown; - // prefer XCB, if supported - for(WindowingSystem sys : m_WinSystems) + if(QGuiApplication::platformName() == lit("wayland")) { - if(sys == WindowingSystem::XCB) + // if we're using Wayland we must use wayland since we can't get any other surface type + for(WindowingSystem sys : m_WinSystems) { - m_CurWinSystem = WindowingSystem::XCB; - break; + if(sys == WindowingSystem::Wayland) + { + m_CurWinSystem = WindowingSystem::Wayland; + m_WaylandDisplay = (wl_display *)AccessWaylandPlatformInterface("display", NULL); + break; + } + } + + if(m_CurWinSystem == WindowingSystem::Unknown) + { + RDDialog::critical(NULL, tr("No wayland support"), + tr("Replay doesn't support Wayland surfaces - check you compiled this " + "build of RenderDoc with Wayland support enabled.")); } } - - if(m_CurWinSystem == WindowingSystem::XCB) - m_XCBConnection = QX11Info::connection(); else - m_X11Display = QX11Info::display(); + { + m_CurWinSystem = WindowingSystem::Xlib; + + // prefer XCB, if supported + for(WindowingSystem sys : m_WinSystems) + { + if(sys == WindowingSystem::XCB) + { + m_CurWinSystem = WindowingSystem::XCB; + break; + } + } + + if(m_CurWinSystem == WindowingSystem::XCB) + m_XCBConnection = QX11Info::connection(); + else + m_X11Display = QX11Info::display(); + } + #elif defined(RENDERDOC_PLATFORM_APPLE) m_CurWinSystem = WindowingSystem::MacOS; #endif @@ -1777,10 +1803,26 @@ WindowingData CaptureContext::CreateWindowingData(QWidget *window) #elif defined(RENDERDOC_PLATFORM_LINUX) - if(m_CurWinSystem == WindowingSystem::XCB) + if(m_CurWinSystem == WindowingSystem::Wayland) + { + // we don't need this, we just need to force creation of a window handle + window->winId(); + wl_surface *surface = + (wl_surface *)AccessWaylandPlatformInterface("surface", window->windowHandle()); + return CreateWaylandWindowingData(m_WaylandDisplay, surface); + } + else if(m_CurWinSystem == WindowingSystem::XCB) + { return CreateXCBWindowingData(m_XCBConnection, (xcb_window_t)window->winId()); - else + } + else if(m_CurWinSystem == WindowingSystem::Xlib) + { return CreateXlibWindowingData(m_X11Display, (Drawable)window->winId()); + } + else + { + return CreateHeadlessWindowingData(1, 1); + } #elif defined(RENDERDOC_PLATFORM_APPLE) diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 680814fc7..f94a7d7a7 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -367,6 +367,7 @@ private: WindowingSystem m_CurWinSystem = WindowingSystem::Unknown; #if defined(RENDERDOC_PLATFORM_LINUX) + wl_display *m_WaylandDisplay = NULL; xcb_connection_t *m_XCBConnection = NULL; Display *m_X11Display = NULL; #endif diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index 27d1e517f..ec2b807ae 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -2156,3 +2156,28 @@ QColor contrastingColor(const QColor &col, const QColor &defaultCol) else return QColor(Qt::black); } + +// we declare this partial class to get the accessors. THIS IS DANGEROUS as the ABI is unstable and +// this is a private class. The first few functions have been stable for a while so we hope that it +// will remain so. If a stable interface is added in future like QX11Info we should definitely use +// it instead. +// +// Unfortunately we need this for Wayland, so we only ever use it when we are absolutely forced to +// because we're running under the Wayland Qt platform. +class QOpenGLContext; + +class Q_GUI_EXPORT QPlatformNativeInterface : public QObject +{ + Q_OBJECT +public: + virtual void *nativeResourceForIntegration(const QByteArray &resource); + virtual void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context); + virtual void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen); + virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window); +}; + +void *AccessWaylandPlatformInterface(const QByteArray &resource, QWindow *window) +{ + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + return native->nativeResourceForWindow(resource, window); +} \ No newline at end of file diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index f71cd4539..f49b52a7c 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -609,3 +609,5 @@ bool IsDarkTheme(); float getLuminance(const QColor &col); QColor contrastingColor(const QColor &col, const QColor &defaultCol); + +void *AccessWaylandPlatformInterface(const QByteArray &resource, QWindow *window); \ No newline at end of file diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index f4ebd161c..be41c10c8 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -75,6 +75,7 @@ %typemap(in) Display* = HWND; %typemap(in) xcb_connection_t* = HWND; +%typemap(in) wl_surface* = HWND; // completely ignore rdcdatetime, we custom convert to/from a native python datetime %ignore rdcdatetime; diff --git a/qrenderdoc/Code/qrenderdoc.cpp b/qrenderdoc/Code/qrenderdoc.cpp index f632e0711..e4749303f 100644 --- a/qrenderdoc/Code/qrenderdoc.cpp +++ b/qrenderdoc/Code/qrenderdoc.cpp @@ -358,6 +358,20 @@ int main(int argc, char *argv[]) GlobalEnvironment env; #if defined(RENDERDOC_PLATFORM_LINUX) env.xlibDisplay = QX11Info::display(); + if(QGuiApplication::platformName() == lit("wayland")) + { + env.waylandDisplay = (wl_display *)AccessWaylandPlatformInterface("display", NULL); + + QString warning = + tr("Running directly on Wayland is NOT SUPPORTED and is likely to crash, hang, or " + "fail to render."); + + qInfo() << "------ !!!! WARNING !!!! ------"; + qInfo() << warning; + qInfo() << "------ !!!! WARNING !!!! ------"; + + RDDialog::critical(NULL, tr("Wayland Qt platform not supported"), warning); + } #endif rdcarray coreargs; if(!crashReportPath.isEmpty()) diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index c976cfd94..60c653b7e 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -70,6 +70,16 @@ elseif(UNIX) list(APPEND RDOC_LIBRARIES PRIVATE -l${XCB_LIBRARIES}) endif() + + if(ENABLE_WAYLAND) + pkg_check_modules(PKG_WAYLAND QUIET wayland-client) + + list(APPEND RDOC_INCLUDES + PRIVATE ${PKG_WAYLAND_INCLUDE_DIRS}) + + list(APPEND RDOC_LIBRARIES + PRIVATE -l${PKG_WAYLAND_LIBRARIES}) + endif() endif() if(NOT "x${CMAKE_THREAD_LIBS_INIT}" STREQUAL "x") diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index bcdd5fb1e..d62668c5a 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -330,6 +330,10 @@ DOCUMENT(R"(Specifies a windowing system to use for creating an output window. The windowing data refers to an XCB window. See :func:`CreateXCBWindowingData`. +.. data:: Wayland + + The windowing data refers to an Wayland window. See :func:`CreateWaylandWindowingData`. + .. data:: Android The windowing data refers to an Android window. See :func:`CreateAndroidWindowingData`. @@ -349,6 +353,7 @@ enum class WindowingSystem : uint32_t Android, MacOS, GGP, + Wayland, }; DECLARE_REFLECTION_ENUM(WindowingSystem); @@ -368,6 +373,10 @@ typedef unsigned long Drawable; struct xcb_connection_t; typedef uint32_t xcb_window_t; +// wayland +struct wl_display; +struct wl_surface; + // android struct ANativeWindow; @@ -409,6 +418,12 @@ struct WindowingData xcb_window_t window; } xcb; + struct + { + wl_display *display; + wl_surface *window; + } wayland; + struct { ANativeWindow *window; @@ -498,6 +513,24 @@ inline const WindowingData CreateXCBWindowingData(xcb_connection_t *connection, return ret; } +DOCUMENT(R"(Create a :class:`WindowingData` for an Wayland ``wl_surface`` handle. + +:param wl_display display: The ``wl_display`` connection used for this window. +:param wl_surface window: The native ``wl_surface`` handle for this window. +:return: A :class:`WindowingData` corresponding to the given window. +:rtype: WindowingData +)"); +inline const WindowingData CreateWaylandWindowingData(wl_display *display, wl_surface *window) +{ + WindowingData ret = {}; + + ret.system = WindowingSystem::Wayland; + ret.wayland.display = display; + ret.wayland.window = window; + + return ret; +} + DOCUMENT(R"(Create a :class:`WindowingData` for a GGP application. :return: A :class:`WindowingData` corresponding to the given system. @@ -571,6 +604,10 @@ struct GlobalEnvironment DOCUMENT("The handle to the X display to use internally. If left ``NULL``, one will be opened."); Display *xlibDisplay = NULL; + + DOCUMENT( + "The handle to the X display to use internally. If left ``NULL``, wayland cannot be used."); + wl_display *waylandDisplay = NULL; }; DOCUMENT("The result of executing or injecting into a program.") diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index d192b6e01..3c6c7c18d 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -60,11 +60,14 @@ rdcstr DoStringise(const WindowingSystem &el) BEGIN_ENUM_STRINGISE(WindowingSystem) { STRINGISE_ENUM_CLASS(Unknown); + STRINGISE_ENUM_CLASS(Headless); STRINGISE_ENUM_CLASS(Win32); STRINGISE_ENUM_CLASS(Xlib); STRINGISE_ENUM_CLASS(XCB); STRINGISE_ENUM_CLASS(Android); STRINGISE_ENUM_CLASS(MacOS); + STRINGISE_ENUM_CLASS(GGP); + STRINGISE_ENUM_CLASS(Wayland); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/common/globalconfig.h b/renderdoc/common/globalconfig.h index e1fa7431b..a5e954370 100644 --- a/renderdoc/common/globalconfig.h +++ b/renderdoc/common/globalconfig.h @@ -129,6 +129,12 @@ #define RDOC_XCB OPTION_OFF #endif +#if defined(RENDERDOC_WINDOWING_WAYLAND) +#define RDOC_WAYLAND OPTION_ON +#else +#define RDOC_WAYLAND OPTION_OFF +#endif + ///////////////////////////////////////////////// // Global constants enum diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index cd80e1133..60d0ab860 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -1323,7 +1323,7 @@ void WrappedID3D11Device::ReleaseSwapchainResources(IDXGISwapper *swapper, UINT if(wnd) { - Keyboard::RemoveInputWindow(wnd); + Keyboard::RemoveInputWindow(WindowingSystem::Win32, wnd); RenderDoc::Inst().RemoveFrameCapturer((ID3D11Device *)this, wnd); } @@ -1460,7 +1460,7 @@ IUnknown *WrappedID3D11Device::WrapSwapchainBuffer(IDXGISwapper *swapper, DXGI_F if(wnd) { - Keyboard::AddInputWindow(wnd); + Keyboard::AddInputWindow(WindowingSystem::Win32, wnd); RenderDoc::Inst().AddFrameCapturer((ID3D11Device *)this, wnd, this); } diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index 91ccfbe06..f89bdf4cd 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -870,7 +870,7 @@ void WrappedID3D12Device::ReleaseSwapchainResources(IDXGISwapper *swapper, UINT if(wnd) { - Keyboard::RemoveInputWindow(wnd); + Keyboard::RemoveInputWindow(WindowingSystem::Win32, wnd); RenderDoc::Inst().RemoveFrameCapturer((ID3D12Device *)this, wnd); } @@ -1045,7 +1045,7 @@ IUnknown *WrappedID3D12Device::WrapSwapchainBuffer(IDXGISwapper *swapper, DXGI_F if(wnd) { - Keyboard::AddInputWindow(wnd); + Keyboard::AddInputWindow(WindowingSystem::Win32, wnd); RenderDoc::Inst().AddFrameCapturer((ID3D12Device *)this, wnd, this); } diff --git a/renderdoc/driver/gl/cgl_hooks.cpp b/renderdoc/driver/gl/cgl_hooks.cpp index a906f03dd..e2f2d821b 100644 --- a/renderdoc/driver/gl/cgl_hooks.cpp +++ b/renderdoc/driver/gl/cgl_hooks.cpp @@ -178,7 +178,7 @@ CGLError GL_EXPORT_NAME(CGLFlushDrawable)(CGLContextObj ctx) CGL.CGLGetSurface(ctx, &conn, &window, &surface); - cglhook.driver.SwapBuffers((void *)(uintptr_t)window); + cglhook.driver.SwapBuffers(WindowingSystem::macOS, (void *)(uintptr_t)window); } CGLError ret; diff --git a/renderdoc/driver/gl/egl_dispatch_table.h b/renderdoc/driver/gl/egl_dispatch_table.h index d0fe3906c..303d992d9 100644 --- a/renderdoc/driver/gl/egl_dispatch_table.h +++ b/renderdoc/driver/gl/egl_dispatch_table.h @@ -28,6 +28,8 @@ typedef EGLBoolean(EGLAPIENTRY *PFN_eglBindAPI)(EGLenum api); typedef EGLDisplay(EGLAPIENTRY *PFN_eglGetDisplay)(EGLNativeDisplayType display_id); +typedef EGLDisplay(EGLAPIENTRY *PFN_eglGetPlatformDisplay)(EGLenum platform, void *native_display, + const EGLAttrib *attrib_list); typedef EGLContext(EGLAPIENTRY *PFN_eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); @@ -43,6 +45,9 @@ typedef EGLSurface(EGLAPIENTRY *PFN_eglCreatePbufferSurface)(EGLDisplay dpy, EGL typedef EGLSurface(EGLAPIENTRY *PFN_eglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); +typedef EGLSurface(EGLAPIENTRY *PFN_eglCreatePlatformWindowSurface)(EGLDisplay dpy, EGLConfig config, + void *native_window, + const EGLAttrib *attrib_list); typedef EGLBoolean(EGLAPIENTRY *PFN_eglChooseConfig)(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); @@ -60,17 +65,19 @@ typedef PFNEGLPOSTSUBBUFFERNVPROC PFN_eglPostSubBufferNV; typedef PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC PFN_eglSwapBuffersWithDamageEXT; typedef PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC PFN_eglSwapBuffersWithDamageKHR; -#define EGL_HOOKED_SYMBOLS(FUNC) \ - FUNC(BindAPI, false); \ - FUNC(GetProcAddress, false); \ - FUNC(GetDisplay, false); \ - FUNC(CreateContext, false); \ - FUNC(DestroyContext, false); \ - FUNC(CreateWindowSurface, false); \ - FUNC(MakeCurrent, false); \ - FUNC(SwapBuffers, false); \ - FUNC(PostSubBufferNV, true); \ - FUNC(SwapBuffersWithDamageEXT, true); \ +#define EGL_HOOKED_SYMBOLS(FUNC) \ + FUNC(BindAPI, false); \ + FUNC(GetProcAddress, false); \ + FUNC(GetDisplay, false); \ + FUNC(GetPlatformDisplay, false); \ + FUNC(CreateContext, false); \ + FUNC(DestroyContext, false); \ + FUNC(CreateWindowSurface, false); \ + FUNC(CreatePlatformWindowSurface, false); \ + FUNC(MakeCurrent, false); \ + FUNC(SwapBuffers, false); \ + FUNC(PostSubBufferNV, true); \ + FUNC(SwapBuffersWithDamageEXT, true); \ FUNC(SwapBuffersWithDamageKHR, true); #define EGL_NONHOOKED_SYMBOLS(FUNC) \ diff --git a/renderdoc/driver/gl/egl_hooks.cpp b/renderdoc/driver/gl/egl_hooks.cpp index 5cb22e455..d9931c5ca 100644 --- a/renderdoc/driver/gl/egl_hooks.cpp +++ b/renderdoc/driver/gl/egl_hooks.cpp @@ -38,10 +38,23 @@ #if ENABLED(RDOC_LINUX) namespace Keyboard { -void CloneDisplay(Display *dpy); +void UseXlibDisplay(Display *dpy); +WindowingSystem UseUnknownDisplay(void *disp); +void UseWaylandDisplay(wl_display *disp); } #endif +struct SurfaceConfig +{ + WindowingSystem system; + void *wnd; +}; + +struct DisplayConfig +{ + WindowingSystem system; +}; + class EGLHook : LibraryHook { public: @@ -54,7 +67,8 @@ public: WrappedOpenGL driver; std::set contexts; std::map configs; - std::map windows; + std::map windows; + std::map displays; // indicates we're in a swap function, so don't process the swap any further if we recurse - could // happen due to driver implementation of one function calling another @@ -121,12 +135,15 @@ static void EnsureRealLibraryLoaded() #if ENABLED(RDOC_LINUX) if(eglhook.handle == DEFAULT_HANDLE) { - RDCLOG("Loading libEGL at the last second"); + if(!RenderDoc::Inst().IsReplayApp()) + RDCLOG("Loading libEGL at the last second"); void *handle = Process::LoadModule("libEGL.so"); if(!handle) handle = Process::LoadModule("libEGL.so.1"); + + eglhook.handle = handle; } #endif } @@ -144,12 +161,38 @@ HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetDisplay_renderdoc_hooked(EGLNativeDispl EnsureRealLibraryLoaded(); #if ENABLED(RDOC_LINUX) - Keyboard::CloneDisplay(display); + Keyboard::UseUnknownDisplay((void *)display); #endif return EGL.GetDisplay(display); } +HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetPlatformDisplay_renderdoc_hooked(EGLenum platform, + void *native_display, + const EGLAttrib *attrib_list) +{ + if(RenderDoc::Inst().IsReplayApp()) + { + if(!EGL.GetDisplay) + EGL.PopulateForReplay(); + + return EGL.GetPlatformDisplay(platform, native_display, attrib_list); + } + + EnsureRealLibraryLoaded(); + +#if ENABLED(RDOC_LINUX) + if(platform == EGL_PLATFORM_X11_KHR) + Keyboard::UseXlibDisplay((Display *)native_display); + else if(platform == EGL_PLATFORM_WAYLAND_KHR) + Keyboard::UseWaylandDisplay((wl_display *)native_display); + else + RDCWARN("Unknown platform %x in eglGetPlatformDisplay", platform); +#endif + + return EGL.GetPlatformDisplay(platform, native_display, attrib_list); +} + HOOK_EXPORT EGLBoolean EGLAPIENTRY eglBindAPI_renderdoc_hooked(EGLenum api) { if(RenderDoc::Inst().IsReplayApp()) @@ -336,7 +379,35 @@ HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreateWindowSurface_renderdoc_hooked(EGLDi { SCOPED_LOCK(glLock); - eglhook.windows[ret] = win; + // spec says it's implementation dependent what happens, so we assume that we're using the same + // window system as the display + eglhook.windows[ret] = {eglhook.displays[dpy].system, (void *)win}; + } + + return ret; +} + +HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface_renderdoc_hooked( + EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list) +{ + if(RenderDoc::Inst().IsReplayApp()) + { + if(!EGL.CreatePlatformWindowSurface) + EGL.PopulateForReplay(); + + return EGL.CreatePlatformWindowSurface(dpy, config, native_window, attrib_list); + } + + EnsureRealLibraryLoaded(); + + EGLSurface ret = EGL.CreatePlatformWindowSurface(dpy, config, native_window, attrib_list); + + if(ret) + { + SCOPED_LOCK(glLock); + + // spec guarantees that we're using the same window system as the display + eglhook.windows[ret] = {eglhook.displays[dpy].system, native_window}; } return ret; @@ -376,17 +447,20 @@ HOOK_EXPORT EGLBoolean EGLAPIENTRY eglMakeCurrent_renderdoc_hooked(EGLDisplay di GL.DriverForEmulation(&eglhook.driver); } + SurfaceConfig cfg = eglhook.windows[draw]; + GLWindowingData data; data.egl_dpy = display; data.egl_wnd = draw; data.egl_ctx = ctx; - data.wnd = (decltype(data.wnd))eglhook.windows[draw]; + data.wnd = (decltype(data.wnd))cfg.wnd; if(!data.wnd) { // could be a pbuffer surface or other offscreen rendering. We want a valid wnd, so set it to // a dummy value data.wnd = (decltype(data.wnd))(void *)(uintptr_t(0xdeadbeef) + uintptr_t(draw)); + cfg.system = WindowingSystem::Headless; } // we could query this out technically but it's easier to keep a map @@ -426,7 +500,9 @@ HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffers_renderdoc_hooked(EGLDisplay dp eglhook.RefreshWindowParameters(data); - eglhook.driver.SwapBuffers(surface); + SurfaceConfig cfg = eglhook.windows[surface]; + + eglhook.driver.SwapBuffers(cfg.system, cfg.wnd); } { @@ -456,7 +532,11 @@ HOOK_EXPORT EGLBoolean EGLAPIENTRY eglPostSubBufferNV_renderdoc_hooked(EGLDispla eglhook.driver.SetDriverType(eglhook.activeAPI); if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping) - eglhook.driver.SwapBuffers((void *)eglhook.windows[surface]); + { + SurfaceConfig cfg = eglhook.windows[surface]; + + eglhook.driver.SwapBuffers(cfg.system, cfg.wnd); + } { eglhook.swapping = true; @@ -485,7 +565,11 @@ HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT_renderdoc_hooked( eglhook.driver.SetDriverType(eglhook.activeAPI); if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping) - eglhook.driver.SwapBuffers((void *)eglhook.windows[surface]); + { + SurfaceConfig cfg = eglhook.windows[surface]; + + eglhook.driver.SwapBuffers(cfg.system, cfg.wnd); + } { eglhook.swapping = true; @@ -514,7 +598,11 @@ HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR_renderdoc_hooked( eglhook.driver.SetDriverType(eglhook.activeAPI); if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping) - eglhook.driver.SwapBuffers((void *)eglhook.windows[surface]); + { + SurfaceConfig cfg = eglhook.windows[surface]; + + eglhook.driver.SwapBuffers(cfg.system, cfg.wnd); + } { eglhook.swapping = true; @@ -549,9 +637,9 @@ eglGetProcAddress_renderdoc_hooked(const char *func) return realFunc; // return our egl hooks -#define GPA_FUNCTION(name) \ - if(!strcmp(func, "egl" STRINGIZE(name))) \ - return (__eglMustCastToProperFunctionPointerType)&CONCAT(egl, CONCAT(name, _renderdoc_hooked)); \ +#define GPA_FUNCTION(name, isext) \ + if(!strcmp(func, "egl" STRINGIZE(name))) \ + return (__eglMustCastToProperFunctionPointerType)&CONCAT(egl, CONCAT(name, _renderdoc_hooked)); EGL_HOOKED_SYMBOLS(GPA_FUNCTION) #undef GPA_FUNCTION @@ -578,6 +666,12 @@ HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display) return eglGetDisplay_renderdoc_hooked(display); } +HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetPlatformDisplay(EGLenum platform, void *native_display, + const EGLAttrib *attrib_list) +{ + return eglGetPlatformDisplay_renderdoc_hooked(platform, native_display, attrib_list); +} + HOOK_EXPORT EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext shareContext, EGLint const *attribList) { @@ -596,6 +690,13 @@ HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLCon return eglCreateWindowSurface_renderdoc_hooked(dpy, config, win, attrib_list); } +HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, + void *native_window, + const EGLAttrib *attrib_list) +{ + return eglCreatePlatformWindowSurface_renderdoc_hooked(dpy, config, native_window, attrib_list); +} + HOOK_EXPORT EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext ctx) { @@ -757,10 +858,6 @@ EGL_PASSTHRU_4(EGLBoolean, eglGetSyncAttrib, EGLDisplay, dpy, EGLSync, sync, EGL EGL_PASSTHRU_5(EGLImage, eglCreateImage, EGLDisplay, dpy, EGLContext, ctx, EGLenum, target, EGLClientBuffer, buffer, const EGLAttrib *, attrib_list) EGL_PASSTHRU_2(EGLBoolean, eglDestroyImage, EGLDisplay, dpy, EGLImage, image) -EGL_PASSTHRU_3(EGLDisplay, eglGetPlatformDisplay, EGLenum, platform, void *, native_display, - const EGLAttrib *, attrib_list) -EGL_PASSTHRU_4(EGLSurface, eglCreatePlatformWindowSurface, EGLDisplay, dpy, EGLConfig, config, - void *, native_window, const EGLAttrib *, attrib_list) EGL_PASSTHRU_4(EGLSurface, eglCreatePlatformPixmapSurface, EGLDisplay, dpy, EGLConfig, config, void *, native_pixmap, const EGLAttrib *, attrib_list) EGL_PASSTHRU_3(EGLBoolean, eglWaitSync, EGLDisplay, dpy, EGLSync, sync, EGLint, flags) diff --git a/renderdoc/driver/gl/egl_platform.cpp b/renderdoc/driver/gl/egl_platform.cpp index 71753c1a8..81eca4779 100644 --- a/renderdoc/driver/gl/egl_platform.cpp +++ b/renderdoc/driver/gl/egl_platform.cpp @@ -42,6 +42,8 @@ static void *GetEGLHandle() class EGLPlatform : public GLPlatform { + RDCDriver m_API = RDCDriver::OpenGLES; + bool MakeContextCurrent(GLWindowingData data) { if(EGL.MakeCurrent) @@ -113,6 +115,7 @@ class EGLPlatform : public GLPlatform bool IsOutputWindowVisible(GLWindowingData context) { return true; } GLWindowingData MakeOutputWindow(WindowingData window, bool depth, GLWindowingData share_context) { + EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; EGLNativeWindowType win = 0; switch(window.system) @@ -122,7 +125,32 @@ class EGLPlatform : public GLPlatform #elif ENABLED(RDOC_ANDROID) case WindowingSystem::Android: win = window.android.window; break; #elif ENABLED(RDOC_LINUX) - case WindowingSystem::Xlib: win = window.xlib.window; break; + case WindowingSystem::Xlib: + { + Display *xlibDisplay = RenderDoc::Inst().GetGlobalEnvironment().xlibDisplay; + + display = (EGLNativeDisplayType)window.xlib.display; + win = (EGLNativeWindowType)window.xlib.window; + + // ensure we're using the same display as the share context, and the same as our global + // display that we used at init time to create the share context's display + RDCASSERT((void *)display == (void *)xlibDisplay && display != NULL, (void *)display, + (void *)xlibDisplay); + break; + } + case WindowingSystem::Wayland: + { + wl_display *waylandDisplay = RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay; + + display = (EGLNativeDisplayType)window.wayland.display; + win = (EGLNativeWindowType)window.wayland.window; + + // ensure we're using the same display as the share context, and the same as our global + // display + RDCASSERT((void *)display == (void *)waylandDisplay && display != NULL, (void *)display, + (void *)waylandDisplay); + break; + } #endif case WindowingSystem::Unknown: case WindowingSystem::Headless: @@ -131,9 +159,18 @@ class EGLPlatform : public GLPlatform default: RDCERR("Unexpected window system %u", window.system); break; } - EGLDisplay eglDisplay = EGL.GetDisplay(EGL_DEFAULT_DISPLAY); + EGLDisplay eglDisplay = NULL; + + if(share_context.egl_dpy) + eglDisplay = share_context.egl_dpy; + else + eglDisplay = EGL.GetDisplay(display); + RDCASSERT(eglDisplay); + int major, minor; + EGL.Initialize(eglDisplay, &major, &minor); + return CreateWindowingData(eglDisplay, share_context.ctx, win, false); } @@ -150,24 +187,27 @@ class EGLPlatform : public GLPlatform ret.egl_wnd = NULL; EGLint surfaceType = (window == 0) ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT; - const EGLint configAttribs[] = {EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES3_BIT, - EGL_CONFORMANT, - EGL_OPENGL_ES3_BIT, - EGL_SURFACE_TYPE, - surfaceType, - EGL_COLOR_BUFFER_TYPE, - EGL_RGB_BUFFER, - EGL_NONE}; + const EGLint configAttribs[] = { + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_RENDERABLE_TYPE, + m_API == RDCDriver::OpenGLES ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT, + EGL_CONFORMANT, + m_API == RDCDriver::OpenGLES ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT, + EGL_SURFACE_TYPE, + surfaceType, + EGL_COLOR_BUFFER_TYPE, + EGL_RGB_BUFFER, + EGL_NONE, + }; - EGLint numConfigs; - if(!EGL.ChooseConfig(eglDisplay, configAttribs, &ret.egl_cfg, 1, &numConfigs)) + EGLint numConfigs = 0; + EGLBoolean success = EGL.ChooseConfig(eglDisplay, configAttribs, &ret.egl_cfg, 1, &numConfigs); + if(!success || numConfigs == 0) { RDCERR("Couldn't find a suitable EGL config"); return ret; @@ -280,12 +320,26 @@ class EGLPlatform : public GLPlatform bool PopulateForReplay() { return EGL.PopulateForReplay(); } ReplayStatus InitialiseAPI(GLWindowingData &replayContext, RDCDriver api, bool debug) { - // we only support replaying GLES through EGL - RDCASSERT(api == RDCDriver::OpenGLES); + Display *xlibDisplay = RenderDoc::Inst().GetGlobalEnvironment().xlibDisplay; + wl_display *waylandDisplay = RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay; - EGL.BindAPI(EGL_OPENGL_ES_API); + // we only support replaying GLES through EGL, except if Wayland is enabled + RDCASSERT(api == RDCDriver::OpenGLES || waylandDisplay); - EGLDisplay eglDisplay = EGL.GetDisplay(EGL_DEFAULT_DISPLAY); + m_API = api; + + if(api == RDCDriver::OpenGLES) + EGL.BindAPI(EGL_OPENGL_ES_API); + else + EGL.BindAPI(EGL_OPENGL_API); + + EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; + if(waylandDisplay) + display = (EGLNativeDisplayType)waylandDisplay; + else if(xlibDisplay) + display = (EGLNativeDisplayType)xlibDisplay; + + EGLDisplay eglDisplay = EGL.GetDisplay(display); if(!eglDisplay) { RDCERR("Couldn't open default EGL display"); diff --git a/renderdoc/driver/gl/gl_debug.cpp b/renderdoc/driver/gl/gl_debug.cpp index d7aaae195..165ce03da 100644 --- a/renderdoc/driver/gl/gl_debug.cpp +++ b/renderdoc/driver/gl/gl_debug.cpp @@ -340,9 +340,14 @@ void GLReplay::InitDebugData() RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.0f); { - WindowingData window = {WindowingSystem::Unknown}; + WindowingData window = {WindowingSystem::Headless}; uint64_t id = MakeOutputWindow(window, true); + m_DebugCtx = NULL; + + if(id == 0) + return; + m_DebugID = id; m_DebugCtx = &m_OutputWindows[id]; diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index e7908879a..8dd5d8ed7 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -1061,29 +1061,38 @@ void WrappedOpenGL::DeleteContext(void *contextHandle) void *wndHandle = it->first; it++; - ctxdata.UnassociateWindow(wndHandle); + ctxdata.UnassociateWindow(this, wndHandle); } m_ContextData.erase(contextHandle); } -void WrappedOpenGL::ContextData::UnassociateWindow(void *wndHandle) +void WrappedOpenGL::ContextData::UnassociateWindow(WrappedOpenGL *driver, void *wndHandle) { auto it = windows.find(wndHandle); if(it != windows.end()) { + if(it->second.first != WindowingSystem::Headless && IsCaptureMode(driver->GetState())) + Keyboard::RemoveInputWindow(it->second.first, wndHandle); + windows.erase(wndHandle); RenderDoc::Inst().RemoveFrameCapturer(ctx, wndHandle); } } -void WrappedOpenGL::ContextData::AssociateWindow(WrappedOpenGL *driver, void *wndHandle) +void WrappedOpenGL::ContextData::AssociateWindow(WrappedOpenGL *driver, WindowingSystem winSystem, + void *wndHandle) { auto it = windows.find(wndHandle); if(it == windows.end()) + { RenderDoc::Inst().AddFrameCapturer(ctx, wndHandle, driver); - windows[wndHandle] = Timing::GetUnixTimestamp(); + if(winSystem != WindowingSystem::Headless && IsCaptureMode(driver->GetState())) + Keyboard::AddInputWindow(winSystem, wndHandle); + } + + windows[wndHandle] = {winSystem, Timing::GetUnixTimestamp()}; } void WrappedOpenGL::ContextData::CreateResourceRecord(WrappedOpenGL *driver, void *suppliedCtx) @@ -1245,9 +1254,6 @@ void WrappedOpenGL::ActivateContext(GLWindowingData winData) m_LastContexts.erase(m_LastContexts.begin()); } - // TODO: support multiple GL contexts more explicitly - Keyboard::AddInputWindow((void *)winData.wnd); - if(winData.ctx) { if(IsActiveCapturing(m_State)) @@ -1802,7 +1808,7 @@ void WrappedOpenGL::FreeTargetResource(ResourceId id) } } -void WrappedOpenGL::SwapBuffers(void *windowHandle) +void WrappedOpenGL::SwapBuffers(WindowingSystem winSystem, void *windowHandle) { if(IsBackgroundCapturing(m_State)) RenderDoc::Inst().Tick(); @@ -1840,9 +1846,9 @@ void WrappedOpenGL::SwapBuffers(void *windowHandle) { for(auto it = m_ContextData.begin(); it != m_ContextData.end(); ++it) if(it->first != ctxdata.ctx) - it->second.UnassociateWindow(windowHandle); + it->second.UnassociateWindow(this, windowHandle); - ctxdata.AssociateWindow(this, windowHandle); + ctxdata.AssociateWindow(this, winSystem, windowHandle); } // we used to do this here so it was as late as possible to avoid creating objects on contexts @@ -1863,14 +1869,12 @@ void WrappedOpenGL::SwapBuffers(void *windowHandle) { for(auto wit = cit->second.windows.begin(); wit != cit->second.windows.end();) { - if(wit->second < ref) + if(wit->second.second < ref) { auto remove = wit; ++wit; - RenderDoc::Inst().RemoveFrameCapturer(cit->first, remove->first); - - cit->second.windows.erase(remove); + cit->second.UnassociateWindow(this, remove->first); } else { diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index 0cf78c04e..015afc329 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -359,17 +359,17 @@ private: GLInitParams initParams; - // map from window handle void* to uint64_t unix timestamp with - // the last time a window was seen/associated with this context. - // Decays after a few seconds since there's no good explicit - // 'remove' type call for GL, only wglCreateContext/wglMakeCurrent - std::map windows; + // map from window handle void* to the windowing system used and the uint64_t unix timestamp of + // the last time a window was seen/associated with this context. Decays after a few seconds + // since there's no good explicit 'remove' type call for GL, only + // wglCreateContext/wglMakeCurrent + std::map> windows; // a window is only associated with one context at once, so any // time we associate a window, it broadcasts to all other // contexts to let them know to remove it - void UnassociateWindow(void *wndHandle); - void AssociateWindow(WrappedOpenGL *driver, void *wndHandle); + void UnassociateWindow(WrappedOpenGL *driver, void *wndHandle); + void AssociateWindow(WrappedOpenGL *driver, WindowingSystem winSystem, void *wndHandle); void CreateDebugData(); @@ -595,7 +595,7 @@ public: } void ActivateContext(GLWindowingData winData); bool ForceSharedObjects(void *oldContext, void *newContext); - void SwapBuffers(void *windowHandle); + void SwapBuffers(WindowingSystem winSystem, void *windowHandle); void HandleVRFrameMarkers(const GLchar *buf, GLsizei length); bool UsesVRFrameMarkers() { return m_UsesVRMarkers; } void FirstFrame(void *ctx, void *wndHandle); diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 3cebad570..dbeefa5ce 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -140,12 +140,24 @@ std::vector GLReplay::GetSupportedWindowSystems() std::vector ret; #if ENABLED(RDOC_LINUX) - // only Xlib supported for GLX. We can't report XCB here since we need - // the Display, and that can't be obtained from XCB. The application is - // free to use XCB internally but it would have to create a hybrid and - // initialise XCB out of Xlib, to be able to provide the display and - // drawable to us. - ret.push_back(WindowingSystem::Xlib); + +#if ENABLED(RDOC_WAYLAND) + // if wayland is supported and a display is configured, we *must* get wayland surfaces to render + // on + if(RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay) + { + ret.push_back(WindowingSystem::Wayland); + } + else +#endif + { + // only Xlib supported for GLX. We can't report XCB here since we need the Display, and that + // can't be obtained from XCB. The application is free to use XCB internally but it would have + // to create a hybrid and initialise XCB out of Xlib, to be able to provide the display and + // drawable to us. + ret.push_back(WindowingSystem::Xlib); + } + #elif ENABLED(RDOC_ANDROID) ret.push_back(WindowingSystem::Android); #elif ENABLED(RDOC_APPLE) @@ -250,6 +262,9 @@ void GLReplay::SetReplayData(GLWindowingData data) InitDebugData(); + if(!HasDebugContext()) + return; + AMDCounters *countersAMD = NULL; IntelGlCounters *countersIntel = NULL; @@ -3465,6 +3480,12 @@ ReplayStatus CreateReplayDevice(RDCDriver rdcdriver, RDCFile *rdc, const ReplayO replay->SetProxy(rdc == NULL); replay->SetReplayData(data); + if(!replay->HasDebugContext()) + { + platform.DeleteReplayContext(data); + return ReplayStatus::APIHardwareUnsupported; + } + gldriver->Initialise(initParams, ver, opts); *driver = (IReplayDriver *)replay; @@ -3510,28 +3531,6 @@ std::vector GetReplayVersions(RDCDriver api) } } -#if defined(RENDERDOC_SUPPORT_GL) - -ReplayStatus GL_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver) -{ - RDCDEBUG("Creating an OpenGL replay device"); - - bool load_ok = GetGLPlatform().PopulateForReplay(); - - if(!load_ok) - { - RDCERR("Couldn't find required platform GL function addresses"); - return ReplayStatus::APIInitFailed; - } - - return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGL, rdc, opts, GetGLPlatform(), - driver); -} - -static DriverRegistration GLDriverRegistration(RDCDriver::OpenGL, &GL_CreateReplayDevice); - -#endif - #if defined(RENDERDOC_SUPPORT_GLES) ReplayStatus GLES_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver) @@ -3587,3 +3586,36 @@ ReplayStatus GLES_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IR static DriverRegistration GLESDriverRegistration(RDCDriver::OpenGLES, &GLES_CreateReplayDevice); #endif + +#if defined(RENDERDOC_SUPPORT_GL) + +ReplayStatus GL_CreateReplayDevice(RDCFile *rdc, const ReplayOptions &opts, IReplayDriver **driver) +{ + if(RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay) + { +#if defined(RENDERDOC_SUPPORT_GLES) + RDCLOG("Forcing EGL device creation for wayland"); + return GLES_CreateReplayDevice(rdc, opts, driver); +#else + RDCERR("GLES support must be enabled at build time when using Wayland"); + return ReplayStatus::InternalError; +#endif + } + + RDCDEBUG("Creating an OpenGL replay device"); + + bool load_ok = GetGLPlatform().PopulateForReplay(); + + if(!load_ok) + { + RDCERR("Couldn't find required platform GL function addresses"); + return ReplayStatus::APIInitFailed; + } + + return CreateReplayDevice(rdc ? rdc->GetDriver() : RDCDriver::OpenGL, rdc, opts, GetGLPlatform(), + driver); +} + +static DriverRegistration GLDriverRegistration(RDCDriver::OpenGL, &GL_CreateReplayDevice); + +#endif diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index bfbdfe651..12b42f80c 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -234,6 +234,7 @@ public: void SetReplayData(GLWindowingData data); bool IsReplayContext(void *ctx) { return m_ReplayCtx.ctx == NULL || ctx == m_ReplayCtx.ctx; } + bool HasDebugContext() { return m_DebugCtx != NULL; } private: void OpenGLFillCBufferVariables(GLuint prog, bool bufferBacked, std::string prefix, const rdcarray &variables, diff --git a/renderdoc/driver/gl/glx_hooks.cpp b/renderdoc/driver/gl/glx_hooks.cpp index 4857f530f..73b0c0146 100644 --- a/renderdoc/driver/gl/glx_hooks.cpp +++ b/renderdoc/driver/gl/glx_hooks.cpp @@ -29,7 +29,7 @@ namespace Keyboard { -void CloneDisplay(Display *dpy); +void UseXlibDisplay(Display *dpy); } class GLXHook : LibraryHook @@ -65,18 +65,18 @@ public: // callback and we'll get a specific library handle. static void EnsureRealLibraryLoaded() { - if(RenderDoc::Inst().IsReplayApp()) - return; - if(glxhook.handle == RTLD_NEXT) { - RDCLOG("Loading libGL at the last second"); + if(!RenderDoc::Inst().IsReplayApp()) + RDCLOG("Loading libGL at the last second"); void *handle = Process::LoadModule("libGL.so.1"); if(!handle) handle = Process::LoadModule("libGL.so"); if(!handle) handle = Process::LoadModule("libGLX.so.0"); + + glxhook.handle = handle; } } @@ -106,7 +106,7 @@ HOOK_EXPORT GLXContext glXCreateContext_renderdoc_hooked(Display *dpy, XVisualIn int value = 0; - Keyboard::CloneDisplay(dpy); + Keyboard::UseXlibDisplay(dpy); GLX.glXGetConfig(dpy, vis, GLX_BUFFER_SIZE, &value); init.colorBits = value; @@ -251,7 +251,7 @@ HOOK_EXPORT GLXContext glXCreateContextAttribsARB_renderdoc_hooked(Display *dpy, int value = 0; - Keyboard::CloneDisplay(dpy); + Keyboard::UseXlibDisplay(dpy); GLX.glXGetConfig(dpy, vis, GLX_BUFFER_SIZE, &value); init.colorBits = value; @@ -444,7 +444,7 @@ HOOK_EXPORT void glXSwapBuffers_renderdoc_hooked(Display *dpy, GLXDrawable drawa glxhook.UpdateWindowSize(data, dpy, drawable); } - glxhook.driver.SwapBuffers((void *)drawable); + glxhook.driver.SwapBuffers(WindowingSystem::Xlib, (void *)drawable); GLX.glXSwapBuffers(dpy, drawable); } diff --git a/renderdoc/driver/gl/wgl_hooks.cpp b/renderdoc/driver/gl/wgl_hooks.cpp index 405da1cf4..08df221bb 100644 --- a/renderdoc/driver/gl/wgl_hooks.cpp +++ b/renderdoc/driver/gl/wgl_hooks.cpp @@ -181,7 +181,7 @@ void WGLHook::ProcessSwapBuffers(HDC dc) { SCOPED_LOCK(glLock); - driver.SwapBuffers(w); + driver.SwapBuffers(WindowingSystem::Win32, w); } SetLastError(0); diff --git a/renderdoc/driver/gl/wrappers/gl_debug_funcs.cpp b/renderdoc/driver/gl/wrappers/gl_debug_funcs.cpp index af0e44cf7..4833f794b 100644 --- a/renderdoc/driver/gl/wrappers/gl_debug_funcs.cpp +++ b/renderdoc/driver/gl/wrappers/gl_debug_funcs.cpp @@ -293,7 +293,7 @@ void WrappedOpenGL::HandleVRFrameMarkers(const GLchar *buf, GLsizei length) { if(strstr(buf, "vr-marker,frame_end,type,application") != NULL) { - SwapBuffers((void *)m_ActiveContexts[Threading::GetCurrentID()].wnd); + SwapBuffers(WindowingSystem::Headless, (void *)m_ActiveContexts[Threading::GetCurrentID()].wnd); m_UsesVRMarkers = true; if(IsActiveCapturing(m_State)) @@ -401,7 +401,7 @@ void WrappedOpenGL::glInsertEventMarkerEXT(GLsizei length, const GLchar *marker) void WrappedOpenGL::glFrameTerminatorGREMEDY() { - SwapBuffers((void *)m_ActiveContexts[Threading::GetCurrentID()].wnd); + SwapBuffers(WindowingSystem::Headless, (void *)m_ActiveContexts[Threading::GetCurrentID()].wnd); } void WrappedOpenGL::glStringMarkerGREMEDY(GLsizei len, const void *string) diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index 38ae52102..a52095592 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -111,6 +111,10 @@ elseif(UNIX) add_definitions(-DVK_USE_PLATFORM_XCB_KHR) endif() + if(ENABLE_WAYLAND) + add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR) + endif() + set(VULKAN_LAYER_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/${LIB_SUBFOLDER_TRAIL_SLASH}librenderdoc.so") set(json_in ${CMAKE_CURRENT_SOURCE_DIR}/renderdoc.json) diff --git a/renderdoc/driver/vulkan/vk_android.cpp b/renderdoc/driver/vulkan/vk_android.cpp index 9361528f3..5eab230c7 100644 --- a/renderdoc/driver/vulkan/vk_android.cpp +++ b/renderdoc/driver/vulkan/vk_android.cpp @@ -43,9 +43,8 @@ VkResult WrappedVulkan::vkCreateAndroidSurfaceKHR(VkInstance instance, 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; + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::Android, (void *)(uintptr_t)pCreateInfo->window); } return ret; diff --git a/renderdoc/driver/vulkan/vk_apple.cpp b/renderdoc/driver/vulkan/vk_apple.cpp index 7baa5c065..d1cf28c38 100644 --- a/renderdoc/driver/vulkan/vk_apple.cpp +++ b/renderdoc/driver/vulkan/vk_apple.cpp @@ -51,9 +51,8 @@ VkResult WrappedVulkan::vkCreateMacOSSurfaceMVK(VkInstance instance, 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; + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::MacOS, (void *)(uintptr_t)pCreateInfo->pView); } return ret; @@ -80,9 +79,8 @@ VkResult WrappedVulkan::vkCreateMetalSurfaceEXT(VkInstance instance, 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->pLayer; + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::MacOS, (void *)(uintptr_t)pCreateInfo->pLayer); } return ret; diff --git a/renderdoc/driver/vulkan/vk_common.h b/renderdoc/driver/vulkan/vk_common.h index 6c37b04bc..c1ffe664b 100644 --- a/renderdoc/driver/vulkan/vk_common.h +++ b/renderdoc/driver/vulkan/vk_common.h @@ -101,6 +101,20 @@ int SampleCount(VkSampleCountFlagBits countFlag); int SampleIndex(VkSampleCountFlagBits countFlag); int StageIndex(VkShaderStageFlagBits stageFlag); +struct PackedWindowHandle +{ + PackedWindowHandle(WindowingSystem s, void *h) : system(s), handle(h) {} + WindowingSystem system; + void *handle; +}; + +struct VkResourceRecord; + +inline VkResourceRecord *PackWindowHandleInRecord(WindowingSystem system, void *handle) +{ + return (VkResourceRecord *)new PackedWindowHandle(system, handle); +} + class WrappedVulkan; struct VkPackedVersion diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 7ee631fb1..e7d543a04 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -1010,6 +1010,11 @@ static const VkExtensionProperties supportedExtensions[] = { { VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME, VK_KHR_VULKAN_MEMORY_MODEL_SPEC_VERSION, }, +#ifdef VK_KHR_wayland_surface + { + VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_SPEC_VERSION, + }, +#endif #ifdef VK_KHR_win32_keyed_mutex { VK_KHR_WIN32_KEYED_MUTEX_EXTENSION_NAME, VK_KHR_WIN32_KEYED_MUTEX_SPEC_VERSION, diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index d5dc73a1c..a05def484 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -1697,6 +1697,17 @@ public: VkSurfaceKHR *pSurface); #endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + // VK_KHR_wayland_surface + VkResult vkCreateWaylandSurfaceKHR(VkInstance instance, + const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface); + + VkBool32 vkGetPhysicalDeviceWaylandPresentationSupportKHR(VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + struct wl_display *display); +#endif + // VK_KHR_display and VK_KHR_display_swapchain. These have no library or include dependencies so // wecan just compile them in on all platforms to reduce platform-specific code. They are mostly // only actually used though on *nix. diff --git a/renderdoc/driver/vulkan/vk_ggp.cpp b/renderdoc/driver/vulkan/vk_ggp.cpp index 0d5d9bf00..b720c5fac 100644 --- a/renderdoc/driver/vulkan/vk_ggp.cpp +++ b/renderdoc/driver/vulkan/vk_ggp.cpp @@ -43,7 +43,8 @@ VkResult WrappedVulkan::vkCreateStreamDescriptorSurfaceGGP( WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); - wrapped->record = (VkResourceRecord *)(uintptr_t)pCreateInfo->streamDescriptor; + wrapped->record = PackWindowHandleInRecord(WindowingSystem::GGP, + (void *)(uintptr_t)pCreateInfo->streamDescriptor); } return ret; } diff --git a/renderdoc/driver/vulkan/vk_hookset_defs.h b/renderdoc/driver/vulkan/vk_hookset_defs.h index 381408003..e99a4ca48 100644 --- a/renderdoc/driver/vulkan/vk_hookset_defs.h +++ b/renderdoc/driver/vulkan/vk_hookset_defs.h @@ -174,6 +174,26 @@ #endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + +#define HookInitInstance_PlatformSpecific_Wayland() \ + HookInitExtension(VK_KHR_wayland_surface, CreateWaylandSurfaceKHR); \ + HookInitExtension(VK_KHR_wayland_surface, GetPhysicalDeviceWaylandPresentationSupportKHR); + +#define HookDefine_PlatformSpecific_Wayland() \ + HookDefine4(VkResult, vkCreateWaylandSurfaceKHR, VkInstance, instance, \ + const VkWaylandSurfaceCreateInfoKHR *, pCreateInfo, const VkAllocationCallbacks *, \ + pAllocator, VkSurfaceKHR *, pSurface); \ + HookDefine3(VkBool32, vkGetPhysicalDeviceWaylandPresentationSupportKHR, VkPhysicalDevice, \ + physicalDevice, uint32_t, queueFamilyIndex, struct wl_display *, display); + +#else + +#define HookInitInstance_PlatformSpecific_Wayland() +#define HookDefine_PlatformSpecific_Wayland() + +#endif + #if defined(VK_USE_PLATFORM_XLIB_KHR) #define HookInitInstance_PlatformSpecific_Xlib() \ @@ -200,11 +220,14 @@ #endif -#define HookInitInstance_PlatformSpecific() \ - HookInitInstance_PlatformSpecific_Xcb() HookInitInstance_PlatformSpecific_Xlib() +#define HookInitInstance_PlatformSpecific() \ + HookInitInstance_PlatformSpecific_Xcb() HookInitInstance_PlatformSpecific_Xlib() \ + HookInitInstance_PlatformSpecific_Wayland() #define HookInitDevice_PlatformSpecific() -#define HookDefine_PlatformSpecific() \ - HookDefine_PlatformSpecific_Xcb() HookDefine_PlatformSpecific_Xlib() + +#define HookDefine_PlatformSpecific() \ + HookDefine_PlatformSpecific_Xcb() HookDefine_PlatformSpecific_Xlib() \ + HookDefine_PlatformSpecific_Wayland() #endif @@ -375,6 +398,7 @@ DeclExt(KHR_get_display_properties2); \ DeclExt(EXT_headless_surface); \ DeclExt(EXT_metal_surface); \ + DeclExt(KHR_wayland_surface); \ /* device extensions */ \ DeclExt(EXT_debug_marker); \ DeclExt(GGP_frame_token); \ @@ -457,7 +481,8 @@ CheckExt(EXT_calibrated_timestamps, VKXX); \ CheckExt(EXT_full_screen_exclusive, VKXX); \ CheckExt(EXT_headless_surface, VKXX); \ - CheckExt(EXT_metal_surface, VKXX); + CheckExt(EXT_metal_surface, VKXX); \ + CheckExt(KHR_wayland_surface, VKXX); #define CheckDeviceExts() \ CheckExt(EXT_debug_marker, VKXX); \ diff --git a/renderdoc/driver/vulkan/vk_linux.cpp b/renderdoc/driver/vulkan/vk_linux.cpp index b944423f6..df6403b5f 100644 --- a/renderdoc/driver/vulkan/vk_linux.cpp +++ b/renderdoc/driver/vulkan/vk_linux.cpp @@ -41,7 +41,7 @@ VkBool32 WrappedVulkan::vkGetPhysicalDeviceXcbPresentationSupportKHR(VkPhysicalD namespace Keyboard { -void UseConnection(xcb_connection_t *conn); +void UseXcbConnection(xcb_connection_t *conn); } VkResult WrappedVulkan::vkCreateXcbSurfaceKHR(VkInstance instance, @@ -61,11 +61,57 @@ VkResult WrappedVulkan::vkCreateXcbSurfaceKHR(VkInstance instance, 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; + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::XCB, (void *)(uintptr_t)pCreateInfo->window); - Keyboard::UseConnection(pCreateInfo->connection); + Keyboard::UseXcbConnection(pCreateInfo->connection); + + Keyboard::AddInputWindow(WindowingSystem::Xlib, (void *)(uintptr_t)pCreateInfo->window); + } + + return ret; +} + +#endif + +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + +namespace Keyboard +{ +void UseWaylandDisplay(wl_display *disp); +} + +VkBool32 WrappedVulkan::vkGetPhysicalDeviceWaylandPresentationSupportKHR( + VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct wl_display *display) +{ + return ObjDisp(physicalDevice) + ->GetPhysicalDeviceWaylandPresentationSupportKHR(Unwrap(physicalDevice), queueFamilyIndex, + display); +} + +VkResult WrappedVulkan::vkCreateWaylandSurfaceKHR(VkInstance instance, + const VkWaylandSurfaceCreateInfoKHR *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) +{ + // should not come in here at all on replay + RDCASSERT(IsCaptureMode(m_State)); + + VkResult ret = ObjDisp(instance)->CreateWaylandSurfaceKHR(Unwrap(instance), pCreateInfo, + pAllocator, pSurface); + + if(ret == VK_SUCCESS) + { + GetResourceManager()->WrapResource(Unwrap(instance), *pSurface); + + WrappedVkSurfaceKHR *wrapped = GetWrapped(*pSurface); + + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::Wayland, (void *)(uintptr_t)pCreateInfo->surface); + + Keyboard::UseWaylandDisplay(pCreateInfo->display); + + Keyboard::AddInputWindow(WindowingSystem::Wayland, pCreateInfo->surface); } return ret; @@ -85,7 +131,7 @@ VkBool32 WrappedVulkan::vkGetPhysicalDeviceXlibPresentationSupportKHR( namespace Keyboard { -void CloneDisplay(Display *dpy); +void UseXlibDisplay(Display *dpy); } VkResult WrappedVulkan::vkCreateXlibSurfaceKHR(VkInstance instance, @@ -105,11 +151,12 @@ VkResult WrappedVulkan::vkCreateXlibSurfaceKHR(VkInstance instance, 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; + wrapped->record = + PackWindowHandleInRecord(WindowingSystem::Xlib, (void *)(uintptr_t)pCreateInfo->window); - Keyboard::CloneDisplay(pCreateInfo->dpy); + Keyboard::UseXlibDisplay(pCreateInfo->dpy); + + Keyboard::AddInputWindow(WindowingSystem::Xlib, (void *)(uintptr_t)pCreateInfo->window); } return ret; @@ -152,6 +199,15 @@ void VulkanReplay::OutputWindow::SetWindowHandle(WindowingData window) } #endif +#if ENABLED(RDOC_WAYLAND) + if(window.system == WindowingSystem::Wayland) + { + wayland.display = window.wayland.display; + wayland.window = window.wayland.window; + return; + } +#endif + RDCERR("Unrecognised/unsupported window system %d", window.system); } @@ -193,6 +249,24 @@ void VulkanReplay::OutputWindow::CreateSurface(WrappedVulkan *driver, VkInstance } #endif +#if ENABLED(RDOC_WAYLAND) + if(m_WindowSystem == WindowingSystem::Wayland) + { + VkWaylandSurfaceCreateInfoKHR createInfo; + + createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.display = wayland.display; + createInfo.surface = wayland.window; + + VkResult vkr = ObjDisp(inst)->CreateWaylandSurfaceKHR(Unwrap(inst), &createInfo, NULL, &surface); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + return; + } +#endif + RDCERR("Unrecognised/unsupported window system %d", m_WindowSystem); } @@ -239,6 +313,17 @@ void VulkanReplay::GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h } #endif +#if ENABLED(RDOC_WAYLAND) + if(outw.m_WindowSystem == WindowingSystem::Wayland) + { + RDCWARN("Need Wayland query for current surface dimensions"); + w = RDCMAX(1U, outw.width); + h = RDCMAX(1U, outw.height); + + return; + } +#endif + RDCERR("Unrecognised/unsupported window system %d", outw.m_WindowSystem); } diff --git a/renderdoc/driver/vulkan/vk_posix.cpp b/renderdoc/driver/vulkan/vk_posix.cpp index 3cd8f39a2..e4e6bd068 100644 --- a/renderdoc/driver/vulkan/vk_posix.cpp +++ b/renderdoc/driver/vulkan/vk_posix.cpp @@ -52,9 +52,10 @@ void WrappedVulkan::AddRequiredExtensions(bool instance, std::vectorrecord = (VkResourceRecord *)pCreateInfo->hwnd; + wrapped->record = PackWindowHandleInRecord(WindowingSystem::Win32, (void *)pCreateInfo->hwnd); - Keyboard::AddInputWindow((void *)pCreateInfo->hwnd); + Keyboard::AddInputWindow(WindowingSystem::Win32, (void *)pCreateInfo->hwnd); } return ret; diff --git a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp index c2919a961..e335e5656 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp @@ -491,7 +491,7 @@ void WrappedVulkan::WrapAndProcessCreatedSwapchain(VkDevice device, SwapchainInfo &swapInfo = *record->swapInfo; // sneaky casting of window handle into record - swapInfo.wndHandle = (RENDERDOC_WindowHandle)GetRecord(pCreateInfo->surface); + swapInfo.wndHandle = ((PackedWindowHandle *)GetRecord(pCreateInfo->surface))->handle; { SCOPED_LOCK(m_SwapLookupLock); @@ -846,7 +846,11 @@ void WrappedVulkan::vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surfac // record pointer has window handle packed in if(wrapper->record) - Keyboard::RemoveInputWindow((void *)wrapper->record); + { + PackedWindowHandle *wnd = (PackedWindowHandle *)wrapper->record; + Keyboard::RemoveInputWindow(wnd->system, wnd->handle); + delete wnd; + } // now set record pointer back to NULL so no-one tries to delete it wrapper->record = NULL; diff --git a/renderdoc/os/os_specific.h b/renderdoc/os/os_specific.h index 319f94df8..54655152f 100644 --- a/renderdoc/os/os_specific.h +++ b/renderdoc/os/os_specific.h @@ -356,8 +356,8 @@ inline bool slurp(const char *filename, std::vector &buffer) namespace Keyboard { void Init(); -void AddInputWindow(void *wnd); -void RemoveInputWindow(void *wnd); +void AddInputWindow(WindowingSystem windowSystem, void *wnd); +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd); bool GetKeyState(int key); bool PlatformHasKeyInput(); }; diff --git a/renderdoc/os/posix/android/android_stringio.cpp b/renderdoc/os/posix/android/android_stringio.cpp index df46401bd..17c92e2a5 100644 --- a/renderdoc/os/posix/android/android_stringio.cpp +++ b/renderdoc/os/posix/android/android_stringio.cpp @@ -40,11 +40,11 @@ bool PlatformHasKeyInput() return false; } -void AddInputWindow(void *wnd) +void AddInputWindow(WindowingSystem windowSystem, void *wnd) { } -void RemoveInputWindow(void *wnd) +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) { } diff --git a/renderdoc/os/posix/apple/apple_stringio.cpp b/renderdoc/os/posix/apple/apple_stringio.cpp index fd3e45c57..a063d2856 100644 --- a/renderdoc/os/posix/apple/apple_stringio.cpp +++ b/renderdoc/os/posix/apple/apple_stringio.cpp @@ -45,11 +45,11 @@ bool PlatformHasKeyInput() return false; } -void AddInputWindow(void *wnd) +void AddInputWindow(WindowingSystem windowSystem, void *wnd) { } -void RemoveInputWindow(void *wnd) +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) { } diff --git a/renderdoc/os/posix/ggp/ggp_stringio.cpp b/renderdoc/os/posix/ggp/ggp_stringio.cpp index 0997889a2..7a10c3a7d 100644 --- a/renderdoc/os/posix/ggp/ggp_stringio.cpp +++ b/renderdoc/os/posix/ggp/ggp_stringio.cpp @@ -50,11 +50,11 @@ bool PlatformHasKeyInput() return false; } -void AddInputWindow(void *wnd) +void AddInputWindow(WindowingSystem windowSystem, void *wnd) { } -void RemoveInputWindow(void *wnd) +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) { } diff --git a/renderdoc/os/posix/linux/linux_stringio.cpp b/renderdoc/os/posix/linux/linux_stringio.cpp index 0c575b96b..412e91730 100644 --- a/renderdoc/os/posix/linux/linux_stringio.cpp +++ b/renderdoc/os/posix/linux/linux_stringio.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "api/app/renderdoc_app.h" #include "common/threading.h" #include "os/os_specific.h" @@ -47,6 +48,11 @@ #include #endif +#if ENABLED(RDOC_WAYLAND) +#include +#include +#endif + namespace Keyboard { void Init() @@ -55,7 +61,7 @@ void Init() bool PlatformHasKeyInput() { -#if ENABLED(RDOC_XCB) || ENABLED(RDOC_XLIB) +#if ENABLED(RDOC_XCB) || ENABLED(RDOC_XLIB) || ENABLED(RDOC_WAYLAND) return true; #else return false; @@ -66,7 +72,7 @@ bool PlatformHasKeyInput() Display *CurrentXDisplay = NULL; -void CloneDisplay(Display *dpy) +void UseXlibDisplay(Display *dpy) { if(CurrentXDisplay || dpy == NULL) return; @@ -137,7 +143,7 @@ bool GetXlibKeyState(int key) // if RENDERDOC_WINDOWING_XLIB is not enabled -void CloneDisplay(Display *dpy) +void UseXlibDisplay(Display *dpy) { } @@ -153,7 +159,7 @@ bool GetXlibKeyState(int key) xcb_connection_t *connection; xcb_key_symbols_t *symbols; -void UseConnection(xcb_connection_t *conn) +void UseXcbConnection(xcb_connection_t *conn) { connection = conn; symbols = xcb_key_symbols_alloc(conn); @@ -233,6 +239,10 @@ bool GetXCBKeyState(int key) // if RENDERDOC_WINDOWING_XCB is not enabled +void UseXcbConnection(xcb_connection_t *conn) +{ +} + bool GetXCBKeyState(int key) { return false; @@ -240,18 +250,298 @@ bool GetXCBKeyState(int key) #endif -void AddInputWindow(void *wnd) +#if ENABLED(RDOC_WAYLAND) + +#include + +std::set displays; +std::set surfaces; +std::map, wl_seat *> seatNames; +std::map seatKeyboard; +bool inFocus = false; +Threading::CriticalSection waylandLock; + +bool keyState[eRENDERDOC_Key_Max] = {}; + +void WaylandKeymapDummy(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { - // TODO check against this drawable & parent window being focused in GetKeyState } -void RemoveInputWindow(void *wnd) +void WaylandModifiersDummy(void *data, wl_keyboard *keyboard, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { } +void WaylandRepeatInfoDummy(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) +{ +} + +void WaylandEnter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf, wl_array *keys) +{ + SCOPED_LOCK(waylandLock); + inFocus = surfaces.find(surf) != surfaces.end(); + RDCEraseEl(keyState); +} + +void WaylandLeave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf) +{ + SCOPED_LOCK(waylandLock); + inFocus = false; + RDCEraseEl(keyState); +} + +void WaylandKeypress(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, + uint32_t key, uint32_t state) +{ + int keyIdx = -1; + switch(key) + { + case KEY_0: keyIdx = eRENDERDOC_Key_0; break; + case KEY_1: keyIdx = eRENDERDOC_Key_1; break; + case KEY_2: keyIdx = eRENDERDOC_Key_2; break; + case KEY_3: keyIdx = eRENDERDOC_Key_3; break; + case KEY_4: keyIdx = eRENDERDOC_Key_4; break; + case KEY_5: keyIdx = eRENDERDOC_Key_5; break; + case KEY_6: keyIdx = eRENDERDOC_Key_6; break; + case KEY_7: keyIdx = eRENDERDOC_Key_7; break; + case KEY_8: keyIdx = eRENDERDOC_Key_8; break; + case KEY_9: keyIdx = eRENDERDOC_Key_9; break; + case KEY_A: keyIdx = eRENDERDOC_Key_A; break; + case KEY_B: keyIdx = eRENDERDOC_Key_B; break; + case KEY_C: keyIdx = eRENDERDOC_Key_C; break; + case KEY_D: keyIdx = eRENDERDOC_Key_D; break; + case KEY_E: keyIdx = eRENDERDOC_Key_E; break; + case KEY_F: keyIdx = eRENDERDOC_Key_F; break; + case KEY_G: keyIdx = eRENDERDOC_Key_G; break; + case KEY_H: keyIdx = eRENDERDOC_Key_H; break; + case KEY_I: keyIdx = eRENDERDOC_Key_I; break; + case KEY_J: keyIdx = eRENDERDOC_Key_J; break; + case KEY_K: keyIdx = eRENDERDOC_Key_K; break; + case KEY_L: keyIdx = eRENDERDOC_Key_L; break; + case KEY_M: keyIdx = eRENDERDOC_Key_M; break; + case KEY_N: keyIdx = eRENDERDOC_Key_N; break; + case KEY_O: keyIdx = eRENDERDOC_Key_O; break; + case KEY_P: keyIdx = eRENDERDOC_Key_P; break; + case KEY_Q: keyIdx = eRENDERDOC_Key_Q; break; + case KEY_R: keyIdx = eRENDERDOC_Key_R; break; + case KEY_S: keyIdx = eRENDERDOC_Key_S; break; + case KEY_T: keyIdx = eRENDERDOC_Key_T; break; + case KEY_U: keyIdx = eRENDERDOC_Key_U; break; + case KEY_V: keyIdx = eRENDERDOC_Key_V; break; + case KEY_W: keyIdx = eRENDERDOC_Key_W; break; + case KEY_X: keyIdx = eRENDERDOC_Key_X; break; + case KEY_Y: keyIdx = eRENDERDOC_Key_Y; break; + case KEY_Z: keyIdx = eRENDERDOC_Key_Z; break; + + case KEY_KPSLASH: keyIdx = eRENDERDOC_Key_Divide; break; + case KEY_KPASTERISK: keyIdx = eRENDERDOC_Key_Multiply; break; + case KEY_KPMINUS: keyIdx = eRENDERDOC_Key_Subtract; break; + case KEY_KPPLUS: keyIdx = eRENDERDOC_Key_Plus; break; + + case KEY_F1: keyIdx = eRENDERDOC_Key_F1; break; + case KEY_F2: keyIdx = eRENDERDOC_Key_F2; break; + case KEY_F3: keyIdx = eRENDERDOC_Key_F3; break; + case KEY_F4: keyIdx = eRENDERDOC_Key_F4; break; + case KEY_F5: keyIdx = eRENDERDOC_Key_F5; break; + case KEY_F6: keyIdx = eRENDERDOC_Key_F6; break; + case KEY_F7: keyIdx = eRENDERDOC_Key_F7; break; + case KEY_F8: keyIdx = eRENDERDOC_Key_F8; break; + case KEY_F9: keyIdx = eRENDERDOC_Key_F9; break; + case KEY_F10: keyIdx = eRENDERDOC_Key_F10; break; + case KEY_F11: keyIdx = eRENDERDOC_Key_F11; break; + case KEY_F12: keyIdx = eRENDERDOC_Key_F12; break; + + case KEY_HOME: keyIdx = eRENDERDOC_Key_Home; break; + case KEY_END: keyIdx = eRENDERDOC_Key_End; break; + case KEY_INSERT: keyIdx = eRENDERDOC_Key_Insert; break; + case KEY_DELETE: keyIdx = eRENDERDOC_Key_Delete; break; + case KEY_PAGEUP: keyIdx = eRENDERDOC_Key_PageUp; break; + case KEY_PAGEDOWN: keyIdx = eRENDERDOC_Key_PageDn; break; + + case KEY_BACKSPACE: keyIdx = eRENDERDOC_Key_Backspace; break; + case KEY_TAB: keyIdx = eRENDERDOC_Key_Tab; break; + case KEY_SYSRQ: keyIdx = eRENDERDOC_Key_PrtScrn; break; + case KEY_PAUSE: keyIdx = eRENDERDOC_Key_Pause; break; + } + + if(keyIdx < 0) + return; + + { + SCOPED_LOCK(waylandLock); + keyState[keyIdx] = (state == WL_KEYBOARD_KEY_STATE_PRESSED); + } +} + +void WaylandSeatCaps(void *data, wl_seat *seat, uint32_t capabilities) +{ + if(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) + { + { + SCOPED_LOCK(waylandLock); + if(seatKeyboard[seat]) + return; + } + + wl_keyboard *keyboard = wl_seat_get_keyboard(seat); + static const wl_keyboard_listener listener = { + WaylandKeymapDummy, WaylandEnter, WaylandLeave, + WaylandKeypress, WaylandModifiersDummy, WaylandRepeatInfoDummy, + }; + wl_keyboard_add_listener(keyboard, &listener, NULL); + + { + SCOPED_LOCK(waylandLock); + seatKeyboard[seat] = keyboard; + } + } + else + { + wl_keyboard *keyboard = NULL; + + { + SCOPED_LOCK(waylandLock); + keyboard = seatKeyboard[seat]; + + if(!keyboard) + return; + + seatKeyboard[seat] = NULL; + } + + wl_keyboard_destroy(keyboard); + } +} + +void WaylandRegistryAdd(void *data, wl_registry *reg, uint32_t name, const char *iface, + uint32_t version) +{ + if(!strcmp(iface, "wl_seat")) + { + wl_seat *seat = (wl_seat *)wl_registry_bind(reg, name, &wl_seat_interface, 1); + static const wl_seat_listener listener = {&WaylandSeatCaps}; + wl_seat_add_listener(seat, &listener, NULL); + + { + SCOPED_LOCK(waylandLock); + seatNames[{reg, name}] = seat; + } + } +} + +void WaylandRegistryRemove(void *data, wl_registry *reg, uint32_t name) +{ + SCOPED_LOCK(waylandLock); + auto it = seatNames.find({reg, name}); + if(it != seatNames.end()) + { + wl_seat_destroy(it->second); + seatNames.erase(it); + } +} + +void UseWaylandDisplay(wl_display *disp) +{ + // only listen to each display once at most + { + SCOPED_LOCK(waylandLock); + if(displays.find(disp) != displays.end()) + return; + displays.insert(disp); + } + + static const wl_registry_listener listener = {&WaylandRegistryAdd, &WaylandRegistryRemove}; + + // get the registry and listen to it. This will then let us find seats + wl_registry_add_listener(wl_display_get_registry(disp), &listener, NULL); +} + +void AddWaylandInputWindow(wl_surface *wnd) +{ + SCOPED_LOCK(waylandLock); + surfaces.insert(wnd); +} + +void RemoveWaylandInputWindow(wl_surface *wnd) +{ + SCOPED_LOCK(waylandLock); + surfaces.erase(wnd); +} + +bool GetWaylandKeyState(int key) +{ + SCOPED_LOCK(waylandLock); + return keyState[key]; +} + +#else + +void UseWaylandDisplay(wl_display *disp) +{ +} + +void AddWaylandInputWindow(wl_surface *wnd) +{ +} + +void RemoveWaylandInputWindow(wl_surface *wnd) +{ +} + +bool GetWaylandKeyState(int key) +{ + return false; +} + +#endif + +WindowingSystem UseUnknownDisplay(void *disp) +{ + // could be wayland or xlib, try to detect. + // both Display* and wl_display* are valid pointers, so dereference and read the first pointer + // sized bytes + void *firstPointer = NULL; + memcpy(&firstPointer, disp, sizeof(void *)); + + // in a Display* we don't know what this contains, but in a wl_display it should point to the + // wl_display_interface exported symbol. Check with dladdr + Dl_info info; + if(dladdr(firstPointer, &info) && !strcmp(info.dli_sname, "wl_display_interface")) + { + UseWaylandDisplay((wl_display *)disp); + return WindowingSystem::Wayland; + } + else + { + UseXlibDisplay((Display *)disp); + return WindowingSystem::Xlib; + } +} + +void AddInputWindow(WindowingSystem windowSystem, void *wnd) +{ + if(windowSystem == WindowingSystem::Wayland) + { + AddWaylandInputWindow((wl_surface *)wnd); + } + else + { + // TODO check against this drawable & parent window being focused in GetKeyState + } +} + +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) +{ + if(windowSystem == WindowingSystem::Wayland) + { + RemoveWaylandInputWindow((wl_surface *)wnd); + } +} + bool GetKeyState(int key) { - return GetXCBKeyState(key) || GetXlibKeyState(key); + return GetXCBKeyState(key) || GetXlibKeyState(key) || GetWaylandKeyState(key); } } diff --git a/renderdoc/os/win32/win32_stringio.cpp b/renderdoc/os/win32/win32_stringio.cpp index 26b9ef09a..aee339129 100644 --- a/renderdoc/os/win32/win32_stringio.cpp +++ b/renderdoc/os/win32/win32_stringio.cpp @@ -79,13 +79,15 @@ bool PlatformHasKeyInput() std::set inputWindows; -void AddInputWindow(void *wnd) +void AddInputWindow(WindowingSystem windowSystem, void *wnd) { + RDCASSERTEQUAL(windowSystem, WindowingSystem::Win32); inputWindows.insert((HWND)wnd); } -void RemoveInputWindow(void *wnd) +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) { + RDCASSERTEQUAL(windowSystem, WindowingSystem::Win32); inputWindows.erase((HWND)wnd); } diff --git a/renderdoc/replay/replay_output.cpp b/renderdoc/replay/replay_output.cpp index c1a92b4b6..ed141919e 100644 --- a/renderdoc/replay/replay_output.cpp +++ b/renderdoc/replay/replay_output.cpp @@ -51,7 +51,16 @@ static uint64_t GetHandle(WindowingData window) #endif } - RDCERR("Unrecognised window system %d", system); + if(window.system == WindowingSystem::Wayland) + { +#if ENABLED(RDOC_WAYLAND) + return (uint64_t)window.wayland.window; +#else + RDCERR("Wayland windowing system data passed in, but support is not compiled in"); +#endif + } + + RDCERR("Unrecognised window system %s", ToStr(window.system).c_str()); return 0; diff --git a/renderdoccmd/renderdoccmd_linux.cpp b/renderdoccmd/renderdoccmd_linux.cpp index 22fb72c19..30167632e 100644 --- a/renderdoccmd/renderdoccmd_linux.cpp +++ b/renderdoccmd/renderdoccmd_linux.cpp @@ -537,6 +537,11 @@ int main(int argc, char *argv[]) count++; #endif +#if defined(RENDERDOC_WINDOWING_WAYLAND) + support += "Wayland (CAPTURE ONLY), "; + count++; +#endif + #if defined(RENDERDOC_SUPPORT_VULKAN) support += "Vulkan KHR_display, "; count++;