diff --git a/extensions/EGL_ANGLE_power_preference.txt b/extensions/EGL_ANGLE_power_preference.txt new file mode 100644 index 000000000..43bdc959b --- /dev/null +++ b/extensions/EGL_ANGLE_power_preference.txt @@ -0,0 +1,85 @@ +Name + + ANGLE_power_preference + +Name Strings + + EGL_ANGLE_power_preference + +Contributors + + Kenneth Russell + +Contacts + + Kenneth Russell, Google Inc. (kbr 'at' google.com) + +Status + + Draft + +Version + + Version 1, April 16, 2019 + +Number + + EGL Extension #?? + +Dependencies + + This extension is written against the wording of the EGL 1.4 + Specification. + +Overview + + This extension allows selection of the high- or low-power GPU on + dual-GPU systems, specifically on macOS. + +New Types + + None + +New Procedures and Functions + + None + +New Tokens + + Accepted as an attribute name in the <*attrib_list> argument to + eglCreateContext: + + EGL_POWER_PREFERENCE_ANGLE 0x3482 + + Accepted as an attribute value in the <*attrib_list> argument to + eglCreateContext: + + EGL_LOW_POWER_ANGLE 0x0001 + EGL_HIGH_POWER_ANGLE 0x0002 + +Additions to the EGL 1.4 Specification + + Add the following to section 3.7.1 "Creating Rendering Contexts": + + EGL_POWER_PREFERENCE_ANGLE indicates whether the context should be + created on the integrated (low-power) or discrete (high-power) GPU + on dual-GPU systems. EGL_POWER_PREFERENCE_ANGLE is only a legal + context creation attribute when the EGL_ANGLE_power_preference + extension is advertised. The valid values for this attribute are + EGL_LOW_POWER_ANGLE and EGL_HIGH_POWER_ANGLE. If this extension is + advertised and this context creation attribute is not specified, + the default value is EGL_LOW_POWER_ANGLE. + + The containing application must set the + NSSupportsAutomaticGraphicsSwitching attribute to true in its + Info.plist in order for this extension to operate as advertised. + +Issues + + None yet. + +Revision History + + Rev. Date Author Changes + ---- ------------- --------- ---------------------------------------- + 1 Apr 16, 2019 kbr Initial version diff --git a/include/EGL/eglext_angle.h b/include/EGL/eglext_angle.h index eb52cba76..869432641 100644 --- a/include/EGL/eglext_angle.h +++ b/include/EGL/eglext_angle.h @@ -202,6 +202,13 @@ EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncValuesCHROMIUM(EGLDisplay dpy, #endif #endif /* EGL_CHROMIUM_get_sync_values */ +#ifndef EGL_ANGLE_power_preference +#define EGL_ANGLE_power_preference 1 +#define EGL_POWER_PREFERENCE_ANGLE 0x3482 +#define EGL_LOW_POWER_ANGLE 0x0001 +#define EGL_HIGH_POWER_ANGLE 0x0002 +#endif /* EGL_ANGLE_power_preference */ + // clang-format on #endif // INCLUDE_EGL_EGLEXT_ANGLE_ diff --git a/src/gpu_info_util/SystemInfo.h b/src/gpu_info_util/SystemInfo.h index f8d4ff7ea..f6b3abc47 100644 --- a/src/gpu_info_util/SystemInfo.h +++ b/src/gpu_info_util/SystemInfo.h @@ -67,6 +67,8 @@ struct SystemInfo bool isOptimus = false; bool isAMDSwitchable = false; + // Only true on dual-GPU Mac laptops. + bool isMacSwitchable = false; // Only available on Android std::string machineManufacturer; diff --git a/src/gpu_info_util/SystemInfo_mac.mm b/src/gpu_info_util/SystemInfo_mac.mm index 7a7a62d17..25aed1362 100644 --- a/src/gpu_info_util/SystemInfo_mac.mm +++ b/src/gpu_info_util/SystemInfo_mac.mm @@ -164,6 +164,19 @@ bool GetSystemInfo(SystemInfo *info) FindPrimaryGPU(info); + // Figure out whether this is a dual-GPU system. + // + // TODO(kbr): this code was ported over from Chromium, and its correctness + // could be improved - need to use Mac-specific APIs to determine whether + // offline renderers are allowed, and whether these two GPUs are really the + // integrated/discrete GPUs in a laptop. + if (info->gpus.size() == 2 && + ((IsIntel(info->gpus[0].vendorId) && !IsIntel(info->gpus[1].vendorId)) || + (!IsIntel(info->gpus[0].vendorId) && IsIntel(info->gpus[1].vendorId)))) + { + info->isMacSwitchable = true; + } + return true; } diff --git a/src/libANGLE/AttributeMap.cpp b/src/libANGLE/AttributeMap.cpp index cd78cefa5..9db71745f 100644 --- a/src/libANGLE/AttributeMap.cpp +++ b/src/libANGLE/AttributeMap.cpp @@ -37,7 +37,7 @@ EGLAttrib AttributeMap::get(EGLAttrib key) const EGLAttrib AttributeMap::get(EGLAttrib key, EGLAttrib defaultValue) const { auto iter = mAttributes.find(key); - return (mAttributes.find(key) != mAttributes.end()) ? iter->second : defaultValue; + return (iter != mAttributes.end()) ? iter->second : defaultValue; } EGLint AttributeMap::getAsInt(EGLAttrib key) const diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp index 69774816a..7ef958e8f 100644 --- a/src/libANGLE/Caps.cpp +++ b/src/libANGLE/Caps.cpp @@ -1391,7 +1391,8 @@ DisplayExtensions::DisplayExtensions() blobCache(false), imageNativeBuffer(false), getFrameTimestamps(false), - recordable(false) + recordable(false), + powerPreference(false) {} std::vector DisplayExtensions::getStrings() const @@ -1444,7 +1445,8 @@ std::vector DisplayExtensions::getStrings() const InsertExtensionString("EGL_ANDROID_blob_cache", blobCache, &extensionStrings); InsertExtensionString("EGL_ANDROID_image_native_buffer", imageNativeBuffer, &extensionStrings); InsertExtensionString("EGL_ANDROID_get_frame_timestamps", getFrameTimestamps, &extensionStrings); - InsertExtensionString("EGL_ANDROID_recordable", recordable, &extensionStrings); + InsertExtensionString("EGL_ANDROID_recordable", recordable, &extensionStrings); + InsertExtensionString("EGL_ANGLE_power_preference", powerPreference, &extensionStrings); // TODO(jmadill): Enable this when complete. //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); // clang-format on diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h index dc81ae4ed..8976d45d8 100644 --- a/src/libANGLE/Caps.h +++ b/src/libANGLE/Caps.h @@ -870,6 +870,9 @@ struct DisplayExtensions // EGL_ANDROID_recordable bool recordable; + + // EGL_ANGLE_power_preference + bool powerPreference; }; struct DeviceExtensions diff --git a/src/libANGLE/renderer/gl/cgl/ContextCGL.cpp b/src/libANGLE/renderer/gl/cgl/ContextCGL.cpp new file mode 100644 index 000000000..6b6f7be61 --- /dev/null +++ b/src/libANGLE/renderer/gl/cgl/ContextCGL.cpp @@ -0,0 +1,41 @@ +// +// Copyright 2019 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. +// +// ContextCGL: +// Mac-specific subclass of ContextGL. +// + +#include "libANGLE/renderer/gl/cgl/ContextCGL.h" + +#include "libANGLE/Context.h" +#include "libANGLE/Display.h" +#include "libANGLE/renderer/gl/cgl/DisplayCGL.h" + +namespace rx +{ + +ContextCGL::ContextCGL(const gl::State &state, + gl::ErrorSet *errorSet, + const std::shared_ptr &renderer, + bool usesDiscreteGPU) + : ContextGL(state, errorSet, renderer), mUsesDiscreteGpu(usesDiscreteGPU) +{} + +void ContextCGL::onDestroy(const gl::Context *context) +{ + if (mUsesDiscreteGpu) + { + egl::Display *display = context->getDisplay(); + // TODO(kbr): if the context is created and destroyed without ever + // making it current, it is possible to leak retentions of the + // discrete GPU. + if (display) + { + GetImplAs(display)->unreferenceDiscreteGPU(); + } + } +} + +} // namespace rx diff --git a/src/libANGLE/renderer/gl/cgl/ContextCGL.h b/src/libANGLE/renderer/gl/cgl/ContextCGL.h new file mode 100644 index 000000000..6a2d26523 --- /dev/null +++ b/src/libANGLE/renderer/gl/cgl/ContextCGL.h @@ -0,0 +1,35 @@ +// +// Copyright 2019 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. +// +// ContextCGL: +// Mac-specific subclass of ContextGL. +// + +#ifndef LIBANGLE_RENDERER_GL_CGL_CONTEXTCGL_H_ +#define LIBANGLE_RENDERER_GL_CGL_CONTEXTCGL_H_ + +#include "libANGLE/renderer/gl/ContextGL.h" +#include "libANGLE/renderer/gl/RendererGL.h" + +namespace rx +{ + +class ContextCGL : public ContextGL +{ + public: + ContextCGL(const gl::State &state, + gl::ErrorSet *errorSet, + const std::shared_ptr &renderer, + bool usesDiscreteGPU); + + void onDestroy(const gl::Context *context) override; + + private: + bool mUsesDiscreteGpu; +}; + +} // namespace rx + +#endif // LIBANGLE_RENDERER_GL_CGL_CONTEXTCGL_H_ diff --git a/src/libANGLE/renderer/gl/cgl/DisplayCGL.h b/src/libANGLE/renderer/gl/cgl/DisplayCGL.h index 1664511af..b2b92ed46 100644 --- a/src/libANGLE/renderer/gl/cgl/DisplayCGL.h +++ b/src/libANGLE/renderer/gl/cgl/DisplayCGL.h @@ -74,6 +74,11 @@ class DisplayCGL : public DisplayGL WorkerContext *createWorkerContext(std::string *infoLog); + // Support for dual-GPU MacBook Pros. If the context was created + // preferring the high-power GPU, unreference that GPU during + // context destruction. + void unreferenceDiscreteGPU(); + private: egl::Error makeCurrentSurfaceless(gl::Context *context) override; @@ -85,6 +90,9 @@ class DisplayCGL : public DisplayGL egl::Display *mEGLDisplay; CGLContextObj mContext; CGLPixelFormatObj mPixelFormat; + bool mSupportsGPUSwitching; + CGLPixelFormatObj mDiscreteGPUPixelFormat; + int mDiscreteGPURefs; }; } // namespace rx diff --git a/src/libANGLE/renderer/gl/cgl/DisplayCGL.mm b/src/libANGLE/renderer/gl/cgl/DisplayCGL.mm index 12c2361c3..d2d37b6f5 100644 --- a/src/libANGLE/renderer/gl/cgl/DisplayCGL.mm +++ b/src/libANGLE/renderer/gl/cgl/DisplayCGL.mm @@ -13,8 +13,9 @@ #include #include "common/debug.h" +#include "gpu_info_util/SystemInfo.h" #include "libANGLE/Display.h" -#include "libANGLE/renderer/gl/ContextGL.h" +#include "libANGLE/renderer/gl/cgl/ContextCGL.h" #include "libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.h" #include "libANGLE/renderer/gl/cgl/PbufferSurfaceCGL.h" #include "libANGLE/renderer/gl/cgl/RendererCGL.h" @@ -49,7 +50,13 @@ class FunctionsGLCGL : public FunctionsGL }; DisplayCGL::DisplayCGL(const egl::DisplayState &state) - : DisplayGL(state), mEGLDisplay(nullptr), mContext(nullptr), mPixelFormat(nullptr) + : DisplayGL(state), + mEGLDisplay(nullptr), + mContext(nullptr), + mPixelFormat(nullptr), + mSupportsGPUSwitching(false), + mDiscreteGPUPixelFormat(nullptr), + mDiscreteGPURefs(0) {} DisplayCGL::~DisplayCGL() {} @@ -58,13 +65,25 @@ egl::Error DisplayCGL::initialize(egl::Display *display) { mEGLDisplay = display; + angle::SystemInfo info; + if (!angle::GetSystemInfo(&info)) + { + return egl::EglNotInitialized() << "Unable to query ANGLE's SystemInfo."; + } + mSupportsGPUSwitching = info.isMacSwitchable; + { // TODO(cwallez) investigate which pixel format we want - CGLPixelFormatAttribute attribs[] = { - kCGLPFAOpenGLProfile, static_cast(kCGLOGLPVersion_3_2_Core), - static_cast(0)}; + std::vector attribs; + attribs.push_back(kCGLPFAOpenGLProfile); + attribs.push_back(static_cast(kCGLOGLPVersion_3_2_Core)); + if (mSupportsGPUSwitching) + { + attribs.push_back(kCGLPFAAllowOfflineRenderers); + } + attribs.push_back(static_cast(0)); GLint nVirtualScreens = 0; - CGLChoosePixelFormat(attribs, &mPixelFormat, &nVirtualScreens); + CGLChoosePixelFormat(attribs.data(), &mPixelFormat, &nVirtualScreens); if (mPixelFormat == nullptr) { @@ -161,7 +180,29 @@ ContextImpl *DisplayCGL::createContext(const gl::State &state, const gl::Context *shareContext, const egl::AttributeMap &attribs) { - return new ContextGL(state, errorSet, mRenderer); + bool usesDiscreteGPU = false; + + if (attribs.get(EGL_POWER_PREFERENCE_ANGLE, EGL_LOW_POWER_ANGLE) == EGL_HIGH_POWER_ANGLE) + { + // Should have been rejected by validation if not supported. + ASSERT(mSupportsGPUSwitching); + // Create discrete pixel format if necessary. + if (!mDiscreteGPUPixelFormat) + { + CGLPixelFormatAttribute discreteAttribs[] = {static_cast(0)}; + GLint numPixelFormats = 0; + if (CGLChoosePixelFormat(discreteAttribs, &mDiscreteGPUPixelFormat, &numPixelFormats) != + kCGLNoError) + { + ERR() << "Error choosing discrete pixel format."; + return nullptr; + } + } + ++mDiscreteGPURefs; + usesDiscreteGPU = true; + } + + return new ContextCGL(state, errorSet, mRenderer, usesDiscreteGPU); } DeviceImpl *DisplayCGL::createDevice() @@ -288,6 +329,11 @@ void DisplayCGL::generateExtensions(egl::DisplayExtensions *outExtensions) const // Contexts are virtualized so textures can be shared globally outExtensions->displayTextureShareGroup = true; + if (mSupportsGPUSwitching) + { + outExtensions->powerPreference = true; + } + DisplayGL::generateExtensions(outExtensions); } @@ -370,4 +416,14 @@ WorkerContext *DisplayCGL::createWorkerContext(std::string *infoLog) return new WorkerContextCGL(context); } + +void DisplayCGL::unreferenceDiscreteGPU() +{ + ASSERT(mDiscreteGPURefs > 0); + if (--mDiscreteGPURefs == 0) + { + CGLDestroyPixelFormat(mDiscreteGPUPixelFormat); + mDiscreteGPUPixelFormat = nullptr; + } +} } diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp index a9dfb86fc..1c0b25f41 100644 --- a/src/libANGLE/validationEGL.cpp +++ b/src/libANGLE/validationEGL.cpp @@ -1127,6 +1127,20 @@ Error ValidateCreateContext(Display *display, } break; + case EGL_POWER_PREFERENCE_ANGLE: + if (!display->getExtensions().powerPreference) + { + return EglBadAttribute() << "Attribute EGL_POWER_PREFERENCE_ANGLE " + "requires EGL_ANGLE_power_preference."; + } + if (value != EGL_LOW_POWER_ANGLE && value != EGL_HIGH_POWER_ANGLE) + { + return EglBadAttribute() + << "EGL_POWER_PREFERENCE_ANGLE must be " + "either EGL_LOW_POWER_ANGLE or EGL_HIGH_POWER_ANGLE."; + } + break; + default: return EglBadAttribute() << "Unknown attribute."; } diff --git a/src/libGLESv2.gni b/src/libGLESv2.gni index 1a69ae89d..2060c4326 100644 --- a/src/libGLESv2.gni +++ b/src/libGLESv2.gni @@ -747,6 +747,8 @@ libangle_gl_egl_android_sources = [ ] libangle_gl_cgl_sources = [ + "src/libANGLE/renderer/gl/cgl/ContextCGL.cpp", + "src/libANGLE/renderer/gl/cgl/ContextCGL.h", "src/libANGLE/renderer/gl/cgl/DisplayCGL.mm", "src/libANGLE/renderer/gl/cgl/DisplayCGL.h", "src/libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.mm",