diff --git a/extensions/EGL_ANGLE_x11_visual.txt b/extensions/EGL_ANGLE_x11_visual.txt new file mode 100644 index 000000000..d77561e43 --- /dev/null +++ b/extensions/EGL_ANGLE_x11_visual.txt @@ -0,0 +1,106 @@ +Name + + ANGLE_x11_visual + +Name Strings + + EGL_ANGLE_x11_visual + +Contributors + + Corentin Wallez, Google + Shannon Woods, Google + Jamie Madill, Google + Geoff Lang, Google + +Contacts + + Corentin Wallez, Google (cwallez 'at' chromium 'dot' org) + +Status + + Draft + +Version + + Version 1, 2015-11-13 + +Number + + EGL Extension XXX + +Extension Type + + EGL client extension + +Dependencies + + Requires EGL_EXT_client_extensions to query its existence without + a display. + + Requires EGL_EXT_platform_base. + + This extension is written against the wording of version 9 of the + EGL_EXT_platform_base specification. + + Written based on the wording of the EGL 1.5 Specification + (August 7 2014). + +Overview + + This extension allows passing the X11 visual ID used by the native + EGL surface types at display creation time. This will restrict + EGLSurfaces to be created from native types with this visual ID, + which may allow the created display to be more compatible and + performant. + +New Types + + None + +New Procedures and Functions + + None + +New Tokens + + Accepted as an attribute name in the argument of + eglGetPlatformDisplayEXT: + + EGL_X11_VISUAL_ID_ANGLE 0x33A3 + +Additions to the EGL Specification + + Modify section 3.5.1 (Creating On-Screen Rendering Surfaces), p. 34 + + Append the following to the errors of CreateWindowSurface: + + "If an X11 visual was specified at display creation time using + EGL_ANGLE_X11_VISUAL_ID that is not equal to the ID of the + native_window's visual, an EGL_BAD_MATCH error is generated and + EGL_NO_SURFACE is returned." + +New Behavior + + To request a display created with a X11 visual ID, the value of + EGL_ANGLE_X11_VISUAL_ID should be set to a valid X11 visual ID. If + present, this ID will be used during display creation to make a + display that is more compatible and potentially more performant when + used with EGLsurfaces created from native types with this ID. If the + visual ID passed isn't a valid visual ID, eglGetPlatformDisplay will + return EGL_NO_DISPLAY and generate an EGL_NOT_INITIALIZED error. + +Issues + + 1) When the hint is present, should EGLsurface creation functions + only accept native types with the hint's visual ID? + + RESOLVED: Yes, generate an error when the visual of the native + surface doesn't match. This will avoid having hidden performance + or compatibility losses when using this extension. + +Revision History + + Version 1, 2015-11-13 (Corentin Wallez) + - Initial draft + diff --git a/include/EGL/eglext.h b/include/EGL/eglext.h index fed0a55b1..3f985de37 100644 --- a/include/EGL/eglext.h +++ b/include/EGL/eglext.h @@ -499,6 +499,11 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurfacePointerANGLE (EGLDisplay dpy, EGLSu #define EGL_FIXED_SIZE_ANGLE 0x3201 #endif /* EGL_ANGLE_window_fixed_size */ +#ifndef EGL_ANGLE_x11_visual +#define EGL_ANGLE_x11_visual +#define EGL_X11_VISUAL_ID_ANGLE 0x33A3 +#endif /* EGL_ANGLE_x11_visual */ + #ifndef EGL_ARM_pixmap_multisample_discard #define EGL_ARM_pixmap_multisample_discard 1 #define EGL_DISCARD_SAMPLES_ARM 0x3286 diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp index cd5e8b3dd..7bd7745f6 100644 --- a/src/libANGLE/Caps.cpp +++ b/src/libANGLE/Caps.cpp @@ -653,6 +653,7 @@ ClientExtensions::ClientExtensions() platformANGLEOpenGL(false), deviceCreation(false), deviceCreationD3D11(false), + x11Visual(false), clientGetAllProcAddresses(false) { } @@ -670,6 +671,7 @@ std::vector ClientExtensions::getStrings() const InsertExtensionString("EGL_ANGLE_platform_angle_opengl", platformANGLEOpenGL, &extensionStrings); InsertExtensionString("EGL_ANGLE_device_creation", deviceCreation, &extensionStrings); InsertExtensionString("EGL_ANGLE_device_creation_d3d11", deviceCreationD3D11, &extensionStrings); + InsertExtensionString("EGL_ANGLE_x11_visual", x11Visual, &extensionStrings); InsertExtensionString("EGL_KHR_client_get_all_proc_addresses", clientGetAllProcAddresses, &extensionStrings); // clang-format on diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h index c02ba7ef8..5dfc99a7a 100644 --- a/src/libANGLE/Caps.h +++ b/src/libANGLE/Caps.h @@ -486,6 +486,9 @@ struct ClientExtensions // EGL_ANGLE_device_creation_d3d11 bool deviceCreationD3D11; + // EGL_ANGLE_x11_visual + bool x11Visual; + // EGL_KHR_client_get_all_proc_addresses bool clientGetAllProcAddresses; }; diff --git a/src/libANGLE/Display.cpp b/src/libANGLE/Display.cpp index d1e9b1a15..f4e09ffcd 100644 --- a/src/libANGLE/Display.cpp +++ b/src/libANGLE/Display.cpp @@ -723,6 +723,10 @@ static ClientExtensions GenerateClientExtensions() extensions.deviceCreationD3D11 = true; #endif +#if defined(ANGLE_USE_X11) + extensions.x11Visual = true; +#endif + extensions.clientGetAllProcAddresses = true; return extensions; diff --git a/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp b/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp index 7ba9fd660..40c5e9b17 100644 --- a/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp +++ b/src/libANGLE/renderer/gl/glx/DisplayGLX.cpp @@ -57,6 +57,7 @@ class FunctionsGLGLX : public FunctionsGL DisplayGLX::DisplayGLX() : DisplayGL(), mFunctionsGL(nullptr), + mRequestedVisual(-1), mContextConfig(nullptr), mContext(nullptr), mDummyPbuffer(0), @@ -80,6 +81,7 @@ egl::Error DisplayGLX::initialize(egl::Display *display) { mEGLDisplay = display; Display *xDisplay = display->getNativeDisplayId(); + const auto &attribMap = display->getAttributeMap(); // ANGLE_platform_angle allows the creation of a default display // using EGL_DEFAULT_DISPLAY (= nullptr). In this case just open @@ -137,14 +139,42 @@ egl::Error DisplayGLX::initialize(egl::Display *display) mMinSwapInterval = 1; } - // When glXMakeCurrent is called, the context and the surface must be - // compatible which in glX-speak means that their config have the same - // color buffer type, are both RGBA or ColorIndex, and their buffers have - // the same depth, if they exist. - // Since our whole EGL implementation is backed by only one GL context, this - // context must be compatible with all the GLXFBConfig corresponding to the - // EGLconfigs that we will be exposing. + if (attribMap.contains(EGL_X11_VISUAL_ID_ANGLE)) { + mRequestedVisual = attribMap.get(EGL_X11_VISUAL_ID_ANGLE, -1); + + // There is no direct way to get the GLXFBConfig matching an X11 visual ID + // so we have to iterate over all the GLXFBConfigs to find the right one. + int nConfigs; + int attribList[] = { + None, + }; + glx::FBConfig *allConfigs = mGLX.chooseFBConfig(attribList, &nConfigs); + + for (int i = 0; i < nConfigs; ++i) + { + if (getGLXFBConfigAttrib(allConfigs[i], GLX_VISUAL_ID) == mRequestedVisual) + { + mContextConfig = allConfigs[i]; + break; + } + } + XFree(allConfigs); + + if (mContextConfig == nullptr) + { + return egl::Error(EGL_NOT_INITIALIZED, "Invalid visual ID requested."); + } + } + else + { + // When glXMakeCurrent is called, the context and the surface must be + // compatible which in glX-speak means that their config have the same + // color buffer type, are both RGBA or ColorIndex, and their buffers have + // the same depth, if they exist. + // Since our whole EGL implementation is backed by only one GL context, this + // context must be compatible with all the GLXFBConfig corresponding to the + // EGLconfigs that we will be exposing. int nConfigs; int attribList[] = { @@ -168,7 +198,7 @@ egl::Error DisplayGLX::initialize(egl::Display *display) GLX_CONFIG_CAVEAT, GLX_NONE, None }; - glx::FBConfig* candidates = mGLX.chooseFBConfig(attribList, &nConfigs); + glx::FBConfig *candidates = mGLX.chooseFBConfig(attribList, &nConfigs); if (nConfigs == 0) { XFree(candidates); @@ -435,6 +465,14 @@ egl::ConfigSet DisplayGLX::generateConfigs() const config.nativeVisualType = getGLXFBConfigAttrib(glxConfig, GLX_X_VISUAL_TYPE); config.nativeRenderable = EGL_TRUE; + // When a visual ID has been specified with EGL_ANGLE_x11_visual we should + // only return configs with this visual: it will maximize performance by avoid + // blits in the driver when showing the window on the screen. + if (mRequestedVisual != -1 && config.nativeVisualID != mRequestedVisual) + { + continue; + } + // Buffer sizes config.redSize = getGLXFBConfigAttrib(glxConfig, GLX_RED_SIZE); config.greenSize = getGLXFBConfigAttrib(glxConfig, GLX_GREEN_SIZE); @@ -652,6 +690,11 @@ void DisplayGLX::setSwapInterval(glx::Drawable drawable, SwapControlData *data) } } +bool DisplayGLX::isValidWindowVisualId(int visualId) const +{ + return mRequestedVisual == -1 || mRequestedVisual == visualId; +} + const FunctionsGL *DisplayGLX::getFunctionsGL() const { return mFunctionsGL; diff --git a/src/libANGLE/renderer/gl/glx/DisplayGLX.h b/src/libANGLE/renderer/gl/glx/DisplayGLX.h index 5fd6abb76..7bce44de0 100644 --- a/src/libANGLE/renderer/gl/glx/DisplayGLX.h +++ b/src/libANGLE/renderer/gl/glx/DisplayGLX.h @@ -79,6 +79,8 @@ class DisplayGLX : public DisplayGL // acts as expected. void setSwapInterval(glx::Drawable drawable, SwapControlData *data); + bool isValidWindowVisualId(int visualId) const; + private: const FunctionsGL *getFunctionsGL() const override; @@ -95,6 +97,7 @@ class DisplayGLX : public DisplayGL //TODO(cwallez) yuck, change generateConfigs to be non-const or add a userdata member to egl::Config? mutable std::map configIdToGLXConfig; + EGLint mRequestedVisual; glx::FBConfig mContextConfig; glx::Context mContext; // A pbuffer the context is current on during ANGLE initialization diff --git a/src/libANGLE/renderer/gl/glx/WindowSurfaceGLX.cpp b/src/libANGLE/renderer/gl/glx/WindowSurfaceGLX.cpp index c41982901..f739aa952 100644 --- a/src/libANGLE/renderer/gl/glx/WindowSurfaceGLX.cpp +++ b/src/libANGLE/renderer/gl/glx/WindowSurfaceGLX.cpp @@ -52,12 +52,26 @@ WindowSurfaceGLX::~WindowSurfaceGLX() egl::Error WindowSurfaceGLX::initialize() { + // Check that the window's visual ID is valid, as part of the AMGLE_x11_visual + // extension. + { + XWindowAttributes windowAttributes; + XGetWindowAttributes(mDisplay, mParent, &windowAttributes); + int visualId = windowAttributes.visual->visualid; + + if (!mGLXDisplay->isValidWindowVisualId(visualId)) + { + return egl::Error(EGL_BAD_MATCH, + "The visual of native_window doesn't match the visual given with " + "ANGLE_X11_VISUAL_ID"); + } + } + // The visual of the X window, GLX window and GLX context must match, // however we received a user-created window that can have any visual // and wouldn't work with our GLX context. To work in all cases, we // create a child window with the right visual that covers all of its // parent. - XVisualInfo *visualInfo = mGLX.getVisualFromFBConfig(mFBConfig); if (!visualInfo) { diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi index 5477ec814..e67cda3ae 100644 --- a/src/tests/angle_end2end_tests.gypi +++ b/src/tests/angle_end2end_tests.gypi @@ -81,6 +81,10 @@ # TODO(cwallez) for Linux, requires a portable implementation of threads '<(angle_path)/src/tests/egl_tests/EGLThreadTest.cpp', ], + 'angle_end2end_tests_x11_sources': + [ + '<(angle_path)/src/tests/egl_tests/EGLX11VisualTest.cpp', + ], }, 'dependencies': [ @@ -108,5 +112,12 @@ '<@(angle_end2end_tests_win_sources)', ], }], + ['use_x11==1', + { + 'sources': + [ + '<@(angle_end2end_tests_x11_sources)', + ], + }], ] } diff --git a/src/tests/egl_tests/EGLX11VisualTest.cpp b/src/tests/egl_tests/EGLX11VisualTest.cpp new file mode 100644 index 000000000..01dc7ffbb --- /dev/null +++ b/src/tests/egl_tests/EGLX11VisualTest.cpp @@ -0,0 +1,213 @@ +// +// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +// EGLX11VisualTest.cpp: tests for EGL_ANGLE_x11_visual extension + +#include + +#include +#include +#include + +#include "OSWindow.h" +#include "test_utils/ANGLETest.h" +#include "x11/X11Window.h" + +using namespace angle; + +namespace +{ + +const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; +} + +class EGLX11VisualHintTest : public ::testing::TestWithParam +{ + public: + void SetUp() override + { + mEglGetPlatformDisplayEXT = reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + + mDisplay = XOpenDisplay(NULL); + } + + std::vector getDisplayAttributes(int visualId) const + { + std::vector attribs; + + attribs.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE); + attribs.push_back(GetParam().getRenderer()); + attribs.push_back(EGL_X11_VISUAL_ID_ANGLE); + attribs.push_back(visualId); + attribs.push_back(EGL_NONE); + + return attribs; + } + + unsigned int chooseDifferentVisual(unsigned int visualId) + { + int numVisuals; + XVisualInfo visualTemplate; + visualTemplate.screen = DefaultScreen(mDisplay); + + XVisualInfo *visuals = + XGetVisualInfo(mDisplay, VisualScreenMask, &visualTemplate, &numVisuals); + EXPECT_TRUE(numVisuals >= 2); + + for (int i = 0; i < numVisuals; ++i) + { + if (visuals[i].visualid != visualId) + { + int result = visuals[i].visualid; + XFree(visuals); + return result; + } + } + + UNREACHABLE(); + return -1; + } + + protected: + PFNEGLGETPLATFORMDISPLAYEXTPROC mEglGetPlatformDisplayEXT; + Display *mDisplay; +}; + +// Test that display creation fails if the visual ID passed in invalid. +TEST_P(EGLX11VisualHintTest, InvalidVisualID) +{ + static const int gInvalidVisualId = -1; + auto attributes = getDisplayAttributes(gInvalidVisualId); + + EGLDisplay display = + mEglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, attributes.data()); + ASSERT_TRUE(display != EGL_NO_DISPLAY); + + ASSERT_TRUE(EGL_FALSE == eglInitialize(display, nullptr, nullptr)); + ASSERT_EGL_ERROR(EGL_NOT_INITIALIZED); +} + +// Test that context creation with a visual ID succeeds, that the context exposes +// only one config, and that a clear on a surface with this config works. +TEST_P(EGLX11VisualHintTest, ValidVisualIDAndClear) +{ + // We'll test the extension with one visual ID but we don't care which one. This means we + // can use OSWindow to create a window and just grab its visual. + OSWindow *osWindow = CreateOSWindow(); + osWindow->initialize("EGLX11VisualHintTest", 500, 500); + osWindow->setVisible(true); + + Window xWindow = osWindow->getNativeWindow(); + + XWindowAttributes windowAttributes; + ASSERT_NE(0, XGetWindowAttributes(mDisplay, xWindow, &windowAttributes)); + int visualId = windowAttributes.visual->visualid; + + auto attributes = getDisplayAttributes(visualId); + EGLDisplay display = + mEglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, attributes.data()); + ASSERT_NE(EGL_NO_DISPLAY, display); + + ASSERT_TRUE(EGL_TRUE == eglInitialize(display, nullptr, nullptr)); + + // While this is not required by the extension, test that our implementation returns only one + // config, with the same native visual Id that we provided. + int nConfigs = 0; + ASSERT_TRUE(EGL_TRUE == eglGetConfigs(display, nullptr, 0, &nConfigs)); + ASSERT_EQ(1, nConfigs); + + int nReturnedConfigs = 0; + EGLConfig config; + ASSERT_TRUE(EGL_TRUE == eglGetConfigs(display, &config, 1, &nReturnedConfigs)); + ASSERT_EQ(nConfigs, nReturnedConfigs); + + EGLint eglNativeId; + ASSERT_TRUE(EGL_TRUE == eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &eglNativeId)); + ASSERT_EQ(visualId, eglNativeId); + + // Finally, try to do a clear on the window. + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + ASSERT_NE(EGL_NO_CONTEXT, context); + + EGLSurface window = eglCreateWindowSurface(display, config, xWindow, nullptr); + ASSERT_EGL_SUCCESS(); + + eglMakeCurrent(display, window, window, context); + ASSERT_EGL_SUCCESS(); + + glViewport(0, 0, 500, 500); + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_EQ(250, 250, 0, 0, 255, 255); + + // Teardown + eglDestroySurface(display, window); + ASSERT_EGL_SUCCESS(); + + eglDestroyContext(display, context); + ASSERT_EGL_SUCCESS(); + + SafeDelete(osWindow); + + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglTerminate(display); +} + +// Test that EGL_BAD_MATCH is generated when trying to create an EGL window from +// an X11 window whose visual ID doesn't match the visual ID passed at display creation. +TEST_P(EGLX11VisualHintTest, InvalidWindowVisualID) +{ + // Get the default visual ID, as a good guess of a visual id for which display + // creation will succeed. + int visualId; + { + OSWindow *osWindow = CreateOSWindow(); + osWindow->initialize("EGLX11VisualHintTest", 500, 500); + osWindow->setVisible(true); + + Window xWindow = osWindow->getNativeWindow(); + + XWindowAttributes windowAttributes; + ASSERT_NE(0, XGetWindowAttributes(mDisplay, xWindow, &windowAttributes)); + visualId = windowAttributes.visual->visualid; + + SafeDelete(osWindow); + } + + auto attributes = getDisplayAttributes(visualId); + EGLDisplay display = + mEglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, attributes.data()); + ASSERT_NE(EGL_NO_DISPLAY, display); + + ASSERT_TRUE(EGL_TRUE == eglInitialize(display, nullptr, nullptr)); + + + // Initialize the window with a visual id different from the display's visual id + int otherVisualId = chooseDifferentVisual(visualId); + ASSERT_NE(visualId, otherVisualId); + + OSWindow *osWindow = new X11Window(otherVisualId); + osWindow->initialize("EGLX11VisualHintTest", 500, 500); + osWindow->setVisible(true); + + Window xWindow = osWindow->getNativeWindow(); + + // Creating the EGL window should fail with EGL_BAD_MATCH + int nReturnedConfigs = 0; + EGLConfig config; + ASSERT_TRUE(EGL_TRUE == eglGetConfigs(display, &config, 1, &nReturnedConfigs)); + ASSERT_EQ(1, nReturnedConfigs); + + EGLSurface window = eglCreateWindowSurface(display, config, xWindow, nullptr); + ASSERT_EQ(EGL_NO_SURFACE, window); + ASSERT_EGL_ERROR(EGL_BAD_MATCH); + + SafeDelete(osWindow); +} + +ANGLE_INSTANTIATE_TEST(EGLX11VisualHintTest, ES2_OPENGL()); diff --git a/util/x11/X11Window.cpp b/util/x11/X11Window.cpp index 68324b71e..97d173a6a 100644 --- a/util/x11/X11Window.cpp +++ b/util/x11/X11Window.cpp @@ -8,6 +8,7 @@ #include "x11/X11Window.h" +#include "common/debug.h" #include "system_utils.h" #include "Timer.h" @@ -152,8 +153,21 @@ static void AddX11KeyStateToEvent(Event *event, unsigned int state) X11Window::X11Window() : WM_DELETE_WINDOW(None), + WM_PROTOCOLS(None), + TEST_EVENT(None), mDisplay(nullptr), - mWindow(0) + mWindow(0), + mRequestedVisualId(-1) +{ +} + +X11Window::X11Window(int visualId) + : WM_DELETE_WINDOW(None), + WM_PROTOCOLS(None), + TEST_EVENT(None), + mDisplay(nullptr), + mWindow(0), + mRequestedVisualId(visualId) { } @@ -176,7 +190,28 @@ bool X11Window::initialize(const std::string &name, size_t width, size_t height) int screen = DefaultScreen(mDisplay); Window root = RootWindow(mDisplay, screen); - Visual *visual = DefaultVisual(mDisplay, screen); + Visual *visual; + if (mRequestedVisualId == -1) + { + visual = DefaultVisual(mDisplay, screen); + } + else + { + XVisualInfo visualTemplate; + visualTemplate.visualid = mRequestedVisualId; + + int numVisuals = 0; + XVisualInfo *visuals = XGetVisualInfo(mDisplay, VisualIDMask, &visualTemplate, &numVisuals); + if (numVisuals <= 0) + { + return false; + } + ASSERT(numVisuals == 1); + + visual = visuals[0].visual; + XFree(visuals); + } + int depth = DefaultDepth(mDisplay, screen); Colormap colormap = XCreateColormap(mDisplay, root, visual, AllocNone); diff --git a/util/x11/X11Window.h b/util/x11/X11Window.h index 297c710f4..9293bb3fc 100644 --- a/util/x11/X11Window.h +++ b/util/x11/X11Window.h @@ -20,6 +20,7 @@ class X11Window : public OSWindow { public: X11Window(); + X11Window(int visualId); ~X11Window(); bool initialize(const std::string &name, size_t width, size_t height) override; @@ -46,6 +47,7 @@ class X11Window : public OSWindow Display *mDisplay; Window mWindow; + int mRequestedVisualId; }; #endif // UTIL_X11_WINDOW_H