/*------------------------------------------------------------------------- * drawElements Quality Program Tester Core * ---------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Platform that uses X11 via GLX. *//*--------------------------------------------------------------------*/ #include "tcuX11GlxPlatform.hpp" #include "tcuX11Platform.hpp" #include "tcuRenderTarget.hpp" #include "glwInitFunctions.hpp" #include "deUniquePtr.hpp" #include <sstream> #include <iterator> #include <set> #define GLX_GLXEXT_PROTOTYPES #include <GL/glx.h> namespace tcu { namespace x11 { namespace glx { using de::UniquePtr; using de::MovePtr; using glu::ApiType; using glu::ContextFactory; using glu::ContextType; using glu::RenderConfig; using glu::RenderContext; using tcu::CommandLine; using tcu::RenderTarget; using std::string; using std::set; using std::istringstream; using std::ostringstream; using std::istream_iterator; typedef RenderConfig::Visibility Visibility; template<typename T> static inline T checkGLX(T value, const char* expr, const char* file, int line) { if (!value) throw tcu::TestError("GLX call failed", expr, file, line); return value; } #define TCU_CHECK_GLX(EXPR) checkGLX(EXPR, #EXPR, __FILE__, __LINE__) #define TCU_CHECK_GLX_CONFIG(EXPR) checkGLX((EXPR) == Success, #EXPR, __FILE__, __LINE__) class GlxContextFactory : public glu::ContextFactory { public: GlxContextFactory (EventState& eventState); ~GlxContextFactory (void); RenderContext* createContext (const RenderConfig& config, const CommandLine& cmdLine) const; EventState& getEventState (void) const { return m_eventState;} const PFNGLXCREATECONTEXTATTRIBSARBPROC m_glXCreateContextAttribsARB; private: EventState& m_eventState; }; class GlxDisplay : public XlibDisplay { public: GlxDisplay (EventState& eventState, const char* name); int getGlxMajorVersion (void) const { return m_majorVersion; } int getGlxMinorVersion (void) const { return m_minorVersion; } bool isGlxExtensionSupported (const char* extName) const; private: int m_errorBase; int m_eventBase; int m_majorVersion; int m_minorVersion; set<string> m_extensions; }; class GlxVisual { public: GlxVisual (GlxDisplay& display, GLXFBConfig fbConfig); int getAttrib (int attribute); Visual* getXVisual (void) { return m_visual; } GLXContext createContext (const GlxContextFactory& factory, const ContextType& contextType); GLXWindow createWindow (::Window xWindow); GlxDisplay& getGlxDisplay (void) { return m_display; } ::Display* getXDisplay (void) { return m_display.getXDisplay(); } private: GlxDisplay& m_display; ::Visual* m_visual; const GLXFBConfig m_fbConfig; }; class GlxDrawable { public: virtual ~GlxDrawable (void) {} virtual void processEvents (void) {} virtual void getDimensions (int* width, int* height) = 0; int getWidth (void); int getHeight (void); void swapBuffers (void) { glXSwapBuffers(getXDisplay(), getGLXDrawable()); } virtual ::Display* getXDisplay (void) = 0; virtual GLXDrawable getGLXDrawable (void) = 0; protected: GlxDrawable () {} unsigned int getAttrib (int attribute); }; class GlxWindow : public GlxDrawable { public: GlxWindow (GlxVisual& visual, const RenderConfig& cfg); ~GlxWindow (void); void processEvents (void) { m_x11Window.processEvents(); } ::Display* getXDisplay (void) { return m_x11Display.getXDisplay(); } void getDimensions (int* width, int* height); protected: GLXDrawable getGLXDrawable () { return m_GLXDrawable; } private: XlibDisplay& m_x11Display; XlibWindow m_x11Window; const GLXDrawable m_GLXDrawable; }; class GlxRenderContext : public RenderContext { public: GlxRenderContext (const GlxContextFactory& factory, const RenderConfig& config); ~GlxRenderContext (void); virtual ContextType getType (void) const; virtual void postIterate (void); void makeCurrent (void); void clearCurrent (void); virtual const glw::Functions& getFunctions (void) const; virtual const tcu::RenderTarget& getRenderTarget (void) const; private: GlxDisplay m_glxDisplay; GlxVisual m_glxVisual; ContextType m_type; GLXContext m_GLXContext; UniquePtr<GlxDrawable> m_glxDrawable; RenderTarget m_renderTarget; glw::Functions m_functions; }; extern "C" { static int tcuX11GlxErrorHandler (::Display* display, XErrorEvent* event) { char buf[80]; XGetErrorText(display, event->error_code, buf, sizeof(buf)); tcu::print("X operation %u:%u failed: %s\n", event->request_code, event->minor_code, buf); return 0; } } GlxContextFactory::GlxContextFactory (EventState& eventState) : glu::ContextFactory ("glx", "X11 GLX OpenGL Context") , m_glXCreateContextAttribsARB ( reinterpret_cast<PFNGLXCREATECONTEXTATTRIBSARBPROC>( TCU_CHECK_GLX( glXGetProcAddress( reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB"))))) , m_eventState (eventState) { XSetErrorHandler(tcuX11GlxErrorHandler); } RenderContext* GlxContextFactory::createContext (const RenderConfig& config, const CommandLine& cmdLine) const { DE_UNREF(cmdLine); GlxRenderContext* const renderContext = new GlxRenderContext(*this, config); return renderContext; } GlxContextFactory::~GlxContextFactory (void) { } GlxDisplay::GlxDisplay (EventState& eventState, const char* name) : XlibDisplay (eventState, name) { const Bool supported = glXQueryExtension(m_display, &m_errorBase, &m_eventBase); if (!supported) TCU_THROW(NotSupportedError, "GLX protocol not supported by X server"); TCU_CHECK_GLX(glXQueryVersion(m_display, &m_majorVersion, &m_minorVersion)); { const int screen = XDefaultScreen(m_display); // nVidia doesn't seem to report client-side extensions correctly, // so only use server side const char* const extensions = TCU_CHECK_GLX(glXQueryServerString(m_display, screen, GLX_EXTENSIONS)); istringstream extStream(extensions); m_extensions = set<string>(istream_iterator<string>(extStream), istream_iterator<string>()); } } bool GlxDisplay::isGlxExtensionSupported (const char* extName) const { return m_extensions.find(extName) != m_extensions.end(); } //! Throw `tcu::NotSupportedError` if `dpy` is not compatible with GLX //! version `major`.`minor`. static void checkGlxVersion (const GlxDisplay& dpy, int major, int minor) { const int dpyMajor = dpy.getGlxMajorVersion(); const int dpyMinor = dpy.getGlxMinorVersion(); if (!(dpyMajor == major && dpyMinor >= minor)) { ostringstream oss; oss << "Server GLX version " << dpyMajor << "." << dpyMinor << " not compatible with required version " << major << "." << minor; TCU_THROW(NotSupportedError, oss.str().c_str()); } } //! Throw `tcu::NotSupportedError` if `dpy` does not support extension `extName`. static void checkGlxExtension (const GlxDisplay& dpy, const char* extName) { if (!dpy.isGlxExtensionSupported(extName)) { ostringstream oss; oss << "GLX extension \"" << extName << "\" not supported"; TCU_THROW(NotSupportedError, oss.str().c_str()); } } GlxVisual::GlxVisual (GlxDisplay& display, GLXFBConfig fbConfig) : m_display (display) , m_visual (DE_NULL) , m_fbConfig (fbConfig) { XVisualInfo* visualInfo = glXGetVisualFromFBConfig(getXDisplay(), fbConfig); if (visualInfo != DE_NULL) { m_visual = visualInfo->visual; XFree(visualInfo); } } int GlxVisual::getAttrib (int attribute) { int fbvalue; TCU_CHECK_GLX_CONFIG(glXGetFBConfigAttrib(getXDisplay(), m_fbConfig, attribute, &fbvalue)); return fbvalue; } GLXContext GlxVisual::createContext (const GlxContextFactory& factory, const ContextType& contextType) { int profileMask = 0; const ApiType apiType = contextType.getAPI(); checkGlxVersion(m_display, 1, 4); checkGlxExtension(m_display, "GLX_ARB_create_context"); checkGlxExtension(m_display, "GLX_ARB_create_context_profile"); switch (apiType.getProfile()) { case glu::PROFILE_ES: checkGlxExtension(m_display, "GLX_EXT_create_context_es2_profile"); profileMask = GLX_CONTEXT_ES2_PROFILE_BIT_EXT; break; case glu::PROFILE_CORE: profileMask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; case glu::PROFILE_COMPATIBILITY: profileMask = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; default: DE_FATAL("Impossible context profile"); } const int attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, apiType.getMajorVersion(), GLX_CONTEXT_MINOR_VERSION_ARB, apiType.getMinorVersion(), GLX_CONTEXT_FLAGS_ARB, 0, GLX_CONTEXT_PROFILE_MASK_ARB, profileMask, None }; return TCU_CHECK_GLX(factory.m_glXCreateContextAttribsARB( getXDisplay(), m_fbConfig, DE_NULL, True, attribs)); } GLXWindow GlxVisual::createWindow (::Window xWindow) { return TCU_CHECK_GLX(glXCreateWindow(getXDisplay(), m_fbConfig, xWindow, NULL)); } unsigned GlxDrawable::getAttrib (int attrib) { unsigned int value = 0; glXQueryDrawable(getXDisplay(), getGLXDrawable(), attrib, &value); return value; } int GlxDrawable::getWidth (void) { int width = 0; getDimensions(&width, DE_NULL); return width; } int GlxDrawable::getHeight (void) { int height = 0; getDimensions(DE_NULL, &height); return height; } GlxWindow::GlxWindow (GlxVisual& visual, const RenderConfig& cfg) : m_x11Display (visual.getGlxDisplay()) , m_x11Window (m_x11Display, cfg.width, cfg.height, visual.getXVisual()) , m_GLXDrawable (visual.createWindow(m_x11Window.getXID())) { m_x11Window.setVisibility(cfg.windowVisibility != RenderConfig::VISIBILITY_HIDDEN); } void GlxWindow::getDimensions (int* width, int* height) { if (width != DE_NULL) *width = getAttrib(GLX_WIDTH); if (height != DE_NULL) *height = getAttrib(GLX_HEIGHT); // glXQueryDrawable may be buggy, so fall back to X geometry if needed if ((width != DE_NULL && *width == 0) || (height != DE_NULL && *height == 0)) m_x11Window.getDimensions(width, height); } GlxWindow::~GlxWindow (void) { glXDestroyWindow(m_x11Display.getXDisplay(), m_GLXDrawable); } static const struct Attribute { int glxAttribute; int RenderConfig::* cfgMember; } s_attribs[] = { { GLX_RED_SIZE, &RenderConfig::redBits }, { GLX_GREEN_SIZE, &RenderConfig::greenBits }, { GLX_BLUE_SIZE, &RenderConfig::blueBits }, { GLX_ALPHA_SIZE, &RenderConfig::alphaBits }, { GLX_DEPTH_SIZE, &RenderConfig::depthBits }, { GLX_STENCIL_SIZE, &RenderConfig::stencilBits }, { GLX_SAMPLES, &RenderConfig::numSamples }, { GLX_FBCONFIG_ID, &RenderConfig::id }, }; static deUint32 surfaceTypeToDrawableBits (RenderConfig::SurfaceType type) { switch (type) { case RenderConfig::SURFACETYPE_WINDOW: return GLX_WINDOW_BIT; case RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE: return GLX_PIXMAP_BIT; case RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC: return GLX_PBUFFER_BIT; case RenderConfig::SURFACETYPE_DONT_CARE: return GLX_WINDOW_BIT | GLX_PIXMAP_BIT | GLX_PBUFFER_BIT; default: DE_FATAL("Impossible case"); } return 0; } static bool configMatches (GlxVisual& visual, const RenderConfig& renderCfg) { if (renderCfg.id != RenderConfig::DONT_CARE) return visual.getAttrib(GLX_FBCONFIG_ID) == renderCfg.id; for (const Attribute* it = DE_ARRAY_BEGIN(s_attribs); it != DE_ARRAY_END(s_attribs); it++) { const int requested = renderCfg.*it->cfgMember; if (requested != RenderConfig::DONT_CARE && requested != visual.getAttrib(it->glxAttribute)) return false; } { deUint32 bits = surfaceTypeToDrawableBits(renderCfg.surfaceType); if ((visual.getAttrib(GLX_DRAWABLE_TYPE) & bits) == 0) return false; // It shouldn't be possible to have GLX_WINDOW_BIT set without a visual, // but let's make sure. if (renderCfg.surfaceType == RenderConfig::SURFACETYPE_WINDOW && visual.getXVisual() == DE_NULL) return false; } return true; } class Rank { public: Rank (void) : m_value(0), m_bitsLeft(64) {} void add (size_t bits, deUint32 value); void sub (size_t bits, deUint32 value); deUint64 getValue (void) { return m_value; } private: deUint64 m_value; size_t m_bitsLeft; }; void Rank::add (size_t bits, deUint32 value) { TCU_CHECK_INTERNAL(m_bitsLeft >= bits); m_bitsLeft -= bits; m_value = m_value << bits | de::min((1U << bits) - 1, value); } void Rank::sub (size_t bits, deUint32 value) { TCU_CHECK_INTERNAL(m_bitsLeft >= bits); m_bitsLeft -= bits; m_value = m_value << bits | ((1U << bits) - 1 - de::min((1U << bits) - 1U, value)); } static deUint64 configRank (GlxVisual& visual) { // Sanity checks. if (visual.getAttrib(GLX_DOUBLEBUFFER) == False || (visual.getAttrib(GLX_RENDER_TYPE) & GLX_RGBA_BIT) == 0) return 0; Rank rank; int caveat = visual.getAttrib(GLX_CONFIG_CAVEAT); int redSize = visual.getAttrib(GLX_RED_SIZE); int greenSize = visual.getAttrib(GLX_GREEN_SIZE); int blueSize = visual.getAttrib(GLX_BLUE_SIZE); int alphaSize = visual.getAttrib(GLX_ALPHA_SIZE); int depthSize = visual.getAttrib(GLX_DEPTH_SIZE); int stencilSize = visual.getAttrib(GLX_STENCIL_SIZE); int minRGB = de::min(redSize, de::min(greenSize, blueSize)); // Prefer conformant configurations. rank.add(1, (caveat != GLX_NON_CONFORMANT_CONFIG)); // Prefer non-transparent configurations. rank.add(1, visual.getAttrib(GLX_TRANSPARENT_TYPE) == GLX_NONE); // Avoid stereo rank.add(1, visual.getAttrib(GLX_STEREO) == False); // Avoid overlays rank.add(1, visual.getAttrib(GLX_LEVEL) == 0); // Prefer to have some alpha. rank.add(1, alphaSize > 0); // Prefer to have a depth buffer. rank.add(1, depthSize > 0); // Prefer to have a stencil buffer. rank.add(1, stencilSize > 0); // Avoid slow configurations. rank.add(1, (caveat != GLX_SLOW_CONFIG)); // Prefer larger, evenly distributed color depths rank.add(4, de::min(minRGB, alphaSize)); // If alpha is low, choose best RGB rank.add(4, minRGB); // Prefer larger depth and stencil buffers rank.add(6, deUint32(depthSize + stencilSize)); // Avoid excessive sampling rank.sub(5, visual.getAttrib(GLX_SAMPLES)); // Prefer True/DirectColor int visualType = visual.getAttrib(GLX_X_VISUAL_TYPE); rank.add(1, visualType == GLX_TRUE_COLOR || visualType == GLX_DIRECT_COLOR); return rank.getValue(); } static GlxVisual chooseVisual (GlxDisplay& display, const RenderConfig& cfg) { ::Display* dpy = display.getXDisplay(); deUint64 maxRank = 0; GLXFBConfig maxConfig = DE_NULL; int numElems = 0; GLXFBConfig* const fbConfigs = glXGetFBConfigs(dpy, DefaultScreen(dpy), &numElems); TCU_CHECK_MSG(fbConfigs != DE_NULL, "Couldn't query framebuffer configurations"); for (int i = 0; i < numElems; i++) { GlxVisual visual(display, fbConfigs[i]); if (!configMatches(visual, cfg)) continue; deUint64 cfgRank = configRank(visual); if (cfgRank > maxRank) { maxRank = cfgRank; maxConfig = fbConfigs[i]; } } XFree(fbConfigs); if (maxRank == 0) TCU_THROW(NotSupportedError, "Requested GLX configuration not found or unusable"); return GlxVisual(display, maxConfig); } GlxDrawable* createDrawable (GlxVisual& visual, const RenderConfig& config) { RenderConfig::SurfaceType surfaceType = config.surfaceType; if (surfaceType == RenderConfig::SURFACETYPE_DONT_CARE) { if (visual.getXVisual() == DE_NULL) // No visual, cannot create X window surfaceType = RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE; else surfaceType = RenderConfig::SURFACETYPE_WINDOW; } switch (surfaceType) { case RenderConfig::SURFACETYPE_DONT_CARE: DE_FATAL("Impossible case"); case RenderConfig::SURFACETYPE_WINDOW: return new GlxWindow(visual, config); break; case RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE: // \todo [2013-11-28 lauri] Pixmaps case RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC: // \todo [2013-11-28 lauri] Pbuffers default: TCU_THROW(NotSupportedError, "Unsupported surface type"); } return DE_NULL; } struct GlxFunctionLoader : public glw::FunctionLoader { GlxFunctionLoader (void) {} glw::GenericFuncType get (const char* name) const { return glXGetProcAddress(reinterpret_cast<const GLubyte*>(name)); } }; GlxRenderContext::GlxRenderContext (const GlxContextFactory& factory, const RenderConfig& config) : m_glxDisplay (factory.getEventState(), DE_NULL) , m_glxVisual (chooseVisual(m_glxDisplay, config)) , m_type (config.type) , m_GLXContext (m_glxVisual.createContext(factory, config.type)) , m_glxDrawable (createDrawable(m_glxVisual, config)) , m_renderTarget (m_glxDrawable->getWidth(), m_glxDrawable->getHeight(), PixelFormat(m_glxVisual.getAttrib(GLX_RED_SIZE), m_glxVisual.getAttrib(GLX_GREEN_SIZE), m_glxVisual.getAttrib(GLX_BLUE_SIZE), m_glxVisual.getAttrib(GLX_ALPHA_SIZE)), m_glxVisual.getAttrib(GLX_DEPTH_SIZE), m_glxVisual.getAttrib(GLX_STENCIL_SIZE), m_glxVisual.getAttrib(GLX_SAMPLES)) { const GlxFunctionLoader loader; makeCurrent(); glu::initFunctions(&m_functions, &loader, config.type.getAPI()); } GlxRenderContext::~GlxRenderContext (void) { clearCurrent(); if (m_GLXContext != DE_NULL) glXDestroyContext(m_glxDisplay.getXDisplay(), m_GLXContext); } void GlxRenderContext::makeCurrent (void) { const GLXDrawable drawRead = m_glxDrawable->getGLXDrawable(); TCU_CHECK_GLX(glXMakeContextCurrent(m_glxDisplay.getXDisplay(), drawRead, drawRead, m_GLXContext)); } void GlxRenderContext::clearCurrent (void) { TCU_CHECK_GLX(glXMakeContextCurrent(m_glxDisplay.getXDisplay(), None, None, DE_NULL)); } ContextType GlxRenderContext::getType (void) const { return m_type; } void GlxRenderContext::postIterate (void) { m_glxDrawable->swapBuffers(); m_glxDrawable->processEvents(); m_glxDisplay.processEvents(); } const RenderTarget& GlxRenderContext::getRenderTarget (void) const { return m_renderTarget; } const glw::Functions& GlxRenderContext::getFunctions (void) const { return m_functions; } MovePtr<ContextFactory> createContextFactory (EventState& eventState) { return MovePtr<ContextFactory>(new GlxContextFactory(eventState)); } } // glx } // x11 } // tcu