blob: 36b76efe62a51c6536f6a54cc2ad09c096f34fcb [file] [log] [blame]
/*
* Copyright (C) 2009-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if ENABLE(WEBGL)
#import "GraphicsContextGLCocoa.h"
#import "ANGLEHeaders.h"
#import "ANGLEUtilities.h"
#import "ANGLEUtilitiesCocoa.h"
#import "CVUtilities.h"
#import "GraphicsContextGLIOSurfaceSwapChain.h"
#import "IOSurfacePool.h"
#import "Logging.h"
#import "PixelBuffer.h"
#import "ProcessIdentity.h"
#import "RuntimeApplicationChecks.h"
#import <CoreGraphics/CGBitmapContext.h>
#import <Metal/Metal.h>
#import <pal/spi/cocoa/MetalSPI.h>
#import <wtf/BlockObjCExceptions.h>
#import <wtf/darwin/WeakLinking.h>
#import <wtf/text/CString.h>
#if PLATFORM(IOS_FAMILY)
#import "WebCoreThread.h"
#endif
#if ENABLE(VIDEO)
#import "GraphicsContextGLCVCocoa.h"
#import "MediaPlayerPrivate.h"
#import "VideoFrameCV.h"
#endif
#if ENABLE(MEDIA_STREAM)
#import "ImageRotationSessionVT.h"
#endif
// FIXME: Checking for EGL_Initialize does not seem to be robust in recovery OS.
WTF_WEAK_LINK_FORCE_IMPORT(EGL_GetPlatformDisplayEXT);
namespace WebCore {
// In isCurrentContextPredictable() == true case this variable is accessed in single-threaded manner.
// In isCurrentContextPredictable() == false case this variable is accessed from multiple threads but always sequentially
// and it always contains nullptr and nullptr is always written to it.
static GraphicsContextGLANGLE* currentContext;
static bool isCurrentContextPredictable()
{
static bool value = isInWebProcess() || isInGPUProcess();
return value;
}
#if ASSERT_ENABLED
// Returns true if we have volatile context extension for the particular API or
// if the particular API is not used.
static bool checkVolatileContextSupportIfDeviceExists(EGLDisplay display, const char* deviceContextVolatileExtension,
const char* deviceContextExtension, EGLint deviceContextType)
{
const char *clientExtensions = EGL_QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (clientExtensions && strstr(clientExtensions, deviceContextVolatileExtension))
return true;
EGLDeviceEXT device = EGL_NO_DEVICE_EXT;
if (!EGL_QueryDisplayAttribEXT(display, EGL_DEVICE_EXT, reinterpret_cast<EGLAttrib*>(&device)))
return true;
if (device == EGL_NO_DEVICE_EXT)
return true;
const char* deviceExtensions = EGL_QueryDeviceStringEXT(device, EGL_EXTENSIONS);
if (!deviceExtensions || !strstr(deviceExtensions, deviceContextExtension))
return true;
void* deviceContext = nullptr;
if (!EGL_QueryDeviceAttribEXT(device, deviceContextType, reinterpret_cast<EGLAttrib*>(&deviceContext)))
return true;
return !deviceContext;
}
#endif
static bool platformSupportsMetal()
{
auto device = adoptNS(MTLCreateSystemDefaultDevice());
if (device) {
#if PLATFORM(IOS_FAMILY) && !PLATFORM(IOS_FAMILY_SIMULATOR)
// A8 devices (iPad Mini 4, iPad Air 2) cannot use WebGL via Metal.
// This check can be removed once they are no longer supported.
return [device supportsFamily:MTLGPUFamilyApple3];
#endif
return true;
}
return false;
}
static EGLDisplay initializeEGLDisplay(const GraphicsContextGLAttributes& attrs)
{
if (!platformIsANGLEAvailable()) {
WTFLogAlways("Failed to load ANGLE shared library.");
return EGL_NO_DISPLAY;
}
// FIXME(http://webkit.org/b/238448): Why is checking EGL_Initialize not robust in recovery OS?
if (EGL_GetPlatformDisplayEXT == NULL) { // NOLINT
WTFLogAlways("Inconsistent weak linking for ANGLE shared library.");
return EGL_NO_DISPLAY;
}
#if ASSERT_ENABLED
const char* clientExtensions = EGL_QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
ASSERT(clientExtensions);
#endif
Vector<EGLint> displayAttributes;
// FIXME: This should come in from the GraphicsContextGLAttributes.
bool shouldInitializeWithVolatileContextSupport = !isCurrentContextPredictable();
if (shouldInitializeWithVolatileContextSupport) {
// For WK1 type APIs we need to set "volatile platform context" for specific
// APIs, since client code will be able to override the thread-global context
// that ANGLE expects.
displayAttributes.append(EGL_PLATFORM_ANGLE_DEVICE_CONTEXT_VOLATILE_EAGL_ANGLE);
displayAttributes.append(EGL_TRUE);
displayAttributes.append(EGL_PLATFORM_ANGLE_DEVICE_CONTEXT_VOLATILE_CGL_ANGLE);
displayAttributes.append(EGL_TRUE);
}
LOG(WebGL, "Attempting to use ANGLE's %s backend.", attrs.useMetal ? "Metal" : "OpenGL");
if (attrs.useMetal) {
displayAttributes.append(EGL_PLATFORM_ANGLE_TYPE_ANGLE);
displayAttributes.append(EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE);
// These properties are defined for EGL_ANGLE_power_preference as EGLContext attributes,
// but Metal backend uses EGLDisplay attributes.
auto powerPreference = attrs.effectivePowerPreference();
if (powerPreference == GraphicsContextGLAttributes::PowerPreference::HighPerformance) {
displayAttributes.append(EGL_POWER_PREFERENCE_ANGLE);
displayAttributes.append(EGL_HIGH_POWER_ANGLE);
} else {
if (powerPreference == GraphicsContextGLAttributes::PowerPreference::LowPower) {
displayAttributes.append(EGL_POWER_PREFERENCE_ANGLE);
displayAttributes.append(EGL_LOW_POWER_ANGLE);
}
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
ASSERT(strstr(clientExtensions, "EGL_ANGLE_platform_angle_device_id"));
// If the power preference is default, use the GPU the context window is on.
// If the power preference is low power, and we know which GPU the context window is on,
// most likely the lowest power is the GPU that drives the context window, as that GPU
// is anyway already powered on.
if (attrs.windowGPUID) {
// EGL_PLATFORM_ANGLE_DEVICE_ID_*_ANGLE is the IOKit registry id on EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE.
displayAttributes.append(EGL_PLATFORM_ANGLE_DEVICE_ID_HIGH_ANGLE);
displayAttributes.append(static_cast<EGLAttrib>(attrs.windowGPUID >> 32));
displayAttributes.append(EGL_PLATFORM_ANGLE_DEVICE_ID_LOW_ANGLE);
displayAttributes.append(static_cast<EGLAttrib>(attrs.windowGPUID));
}
#endif
}
}
displayAttributes.append(EGL_NONE);
EGLDisplay display = EGL_GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void*>(EGL_DEFAULT_DISPLAY), displayAttributes.data());
EGLint majorVersion = 0;
EGLint minorVersion = 0;
if (EGL_Initialize(display, &majorVersion, &minorVersion) == EGL_FALSE) {
LOG(WebGL, "EGLDisplay Initialization failed.");
return EGL_NO_DISPLAY;
}
LOG(WebGL, "ANGLE initialised Major: %d Minor: %d", majorVersion, minorVersion);
if (shouldInitializeWithVolatileContextSupport) {
ASSERT(checkVolatileContextSupportIfDeviceExists(display, "EGL_ANGLE_platform_device_context_volatile_eagl", "EGL_ANGLE_device_eagl", EGL_EAGL_CONTEXT_ANGLE));
ASSERT(checkVolatileContextSupportIfDeviceExists(display, "EGL_ANGLE_platform_device_context_volatile_cgl", "EGL_ANGLE_device_cgl", EGL_CGL_CONTEXT_ANGLE));
}
return display;
}
static const unsigned statusCheckThreshold = 5;
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
static bool needsEAGLOnMac()
{
#if PLATFORM(MACCATALYST) && CPU(ARM64)
return true;
#else
return false;
#endif
}
#endif
RefPtr<GraphicsContextGLCocoa> GraphicsContextGLCocoa::create(GraphicsContextGLAttributes&& attributes, ProcessIdentity&& resourceOwner)
{
auto context = adoptRef(*new GraphicsContextGLCocoa(WTFMove(attributes), WTFMove(resourceOwner)));
if (!context->initialize())
return nullptr;
return context;
}
GraphicsContextGLCocoa::GraphicsContextGLCocoa(GraphicsContextGLAttributes&& creationAttributes, ProcessIdentity&& resourceOwner)
: GraphicsContextGLANGLE(WTFMove(creationAttributes))
, m_resourceOwner(WTFMove(resourceOwner))
{
}
GraphicsContextGLCocoa::~GraphicsContextGLCocoa() = default;
IOSurface* GraphicsContextGLCocoa::displayBuffer()
{
return m_swapChain.displayBuffer().surface.get();
}
void GraphicsContextGLCocoa::markDisplayBufferInUse()
{
return m_swapChain.markDisplayBufferInUse();
}
// FIXME: Below is functionality that should be moved to GraphicsContextGLCocoa to simplify the base class.
GraphicsContextGLANGLE::GraphicsContextGLANGLE(GraphicsContextGLAttributes attrs)
: GraphicsContextGL(attrs)
{
}
bool GraphicsContextGLCocoa::platformInitializeContext()
{
GraphicsContextGLAttributes attributes = contextAttributes();
m_isForWebGL2 = attributes.webGLVersion == GraphicsContextGLWebGLVersion::WebGL2;
if (attributes.useMetal && !platformSupportsMetal()) {
attributes.useMetal = false;
setContextAttributes(attributes);
}
#if ENABLE(WEBXR)
if (attributes.xrCompatible) {
// FIXME: It's almost certain that any connected headset will require the high-power GPU,
// which is the same GPU we need this context to use. However, this is not guaranteed, and
// there is also the chance that there are multiple GPUs. Given that you can request the
// GraphicsContextGL before initializing the WebXR session, we'll need some way to
// migrate the context to the appropriate GPU when the code here does not work.
LOG(WebGL, "WebXR compatible context requested. This will also trigger a request for the high-power GPU.");
attributes.forceRequestForHighPerformanceGPU = true;
setContextAttributes(attributes);
}
#endif
m_displayObj = initializeEGLDisplay(attributes);
if (!m_displayObj)
return false;
#if PLATFORM(MAC)
if (!attributes.useMetal) {
// For OpenGL, EGL_ANGLE_power_preference is used. The context is initialized with the
// default, low-power device. For high-performance contexts, we request the high-performance
// GPU in setContextVisibility. When the request is fullfilled by the system, we get the
// display reconfiguration callback. Upon this, we update the CGL contexts inside ANGLE.
const char *displayExtensions = EGL_QueryString(m_displayObj, EGL_EXTENSIONS);
bool supportsPowerPreference = strstr(displayExtensions, "EGL_ANGLE_power_preference");
if (!supportsPowerPreference) {
attributes.forceRequestForHighPerformanceGPU = false;
if (attributes.powerPreference == GraphicsContextGLPowerPreference::HighPerformance) {
attributes.powerPreference = GraphicsContextGLPowerPreference::Default;
}
setContextAttributes(attributes);
}
}
#endif
EGLint configAttributes[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_NONE
};
EGLint numberConfigsReturned = 0;
EGL_ChooseConfig(m_displayObj, configAttributes, &m_configObj, 1, &numberConfigsReturned);
if (numberConfigsReturned != 1) {
LOG(WebGL, "EGLConfig Initialization failed.");
return false;
}
LOG(WebGL, "Got EGLConfig");
EGL_BindAPI(EGL_OPENGL_ES_API);
if (EGL_GetError() != EGL_SUCCESS) {
LOG(WebGL, "Unable to bind to OPENGL_ES_API");
return false;
}
Vector<EGLint> eglContextAttributes;
if (m_isForWebGL2) {
eglContextAttributes.append(EGL_CONTEXT_CLIENT_VERSION);
eglContextAttributes.append(3);
} else {
eglContextAttributes.append(EGL_CONTEXT_CLIENT_VERSION);
eglContextAttributes.append(2);
// ANGLE will upgrade the context to ES3 automatically unless this is specified.
eglContextAttributes.append(EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE);
eglContextAttributes.append(EGL_FALSE);
}
eglContextAttributes.append(EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE);
eglContextAttributes.append(EGL_TRUE);
// WebGL requires that all resources are cleared at creation.
eglContextAttributes.append(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE);
eglContextAttributes.append(EGL_TRUE);
// WebGL doesn't allow client arrays.
eglContextAttributes.append(EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE);
eglContextAttributes.append(EGL_FALSE);
// WebGL doesn't allow implicit creation of objects on bind.
eglContextAttributes.append(EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM);
eglContextAttributes.append(EGL_FALSE);
#if HAVE(TASK_IDENTITY_TOKEN)
auto displayExtensions = EGL_QueryString(m_displayObj, EGL_EXTENSIONS);
bool supportsOwnershipIdentity = strstr(displayExtensions, "EGL_ANGLE_metal_create_context_ownership_identity");
if (attributes.useMetal && m_resourceOwner && supportsOwnershipIdentity) {
eglContextAttributes.append(EGL_CONTEXT_METAL_OWNERSHIP_IDENTITY_ANGLE);
eglContextAttributes.append(m_resourceOwner.taskIdToken());
}
#endif
eglContextAttributes.append(EGL_NONE);
m_contextObj = EGL_CreateContext(m_displayObj, m_configObj, EGL_NO_CONTEXT, eglContextAttributes.data());
if (m_contextObj == EGL_NO_CONTEXT || !makeCurrent(m_displayObj, m_contextObj)) {
LOG(WebGL, "EGLContext Initialization failed.");
return false;
}
return true;
}
bool GraphicsContextGLCocoa::platformInitialize()
{
auto attributes = contextAttributes();
if (m_isForWebGL2)
GL_Enable(GraphicsContextGL::PRIMITIVE_RESTART_FIXED_INDEX);
Vector<ASCIILiteral, 4> requiredExtensions;
if (m_isForWebGL2) {
// For WebGL 2.0 occlusion queries to work.
requiredExtensions.append("GL_EXT_occlusion_query_boolean"_s);
}
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
if (!needsEAGLOnMac()) {
// For IOSurface-backed textures.
if (!attributes.useMetal)
requiredExtensions.append("GL_ANGLE_texture_rectangle"_s);
// For creating the EGL surface from an IOSurface.
requiredExtensions.append("GL_EXT_texture_format_BGRA8888"_s);
}
#endif // PLATFORM(MAC) || PLATFORM(MACCATALYST)
#if ENABLE(WEBXR) && !PLATFORM(IOS_FAMILY_SIMULATOR)
if (attributes.xrCompatible)
requiredExtensions.append("GL_OES_EGL_image"_s);
#endif
if (m_isForWebGL2)
requiredExtensions.append("GL_ANGLE_framebuffer_multisample"_s);
for (auto& extension : requiredExtensions) {
if (!supportsExtension(extension)) {
LOG(WebGL, "Missing required extension. %s", extension.characters());
return false;
}
ensureExtensionEnabled(extension);
}
if (attributes.useMetal) {
// GraphicsContextGLANGLE uses sync objects to throttle display on Metal implementations.
// OpenGL sync objects are not signaling upon completion on Catalina-era drivers, so
// OpenGL cannot use this method of throttling. OpenGL drivers typically implement
// some sort of internal throttling.
if (supportsExtension("GL_ARB_sync"_s)) {
m_useFenceSyncForDisplayRateLimit = true;
ensureExtensionEnabled("GL_ARB_sync"_s);
}
}
validateAttributes();
attributes = contextAttributes(); // They may have changed during validation.
// Create the texture that will be used for the framebuffer.
GLenum textureTarget = drawingBufferTextureTarget();
GL_GenTextures(1, &m_texture);
GL_BindTexture(textureTarget, m_texture);
GL_TexParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL_TexParameteri(textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
GL_TexParameteri(textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GL_BindTexture(textureTarget, 0);
GL_GenFramebuffers(1, &m_fbo);
GL_BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
m_state.boundDrawFBO = m_state.boundReadFBO = m_fbo;
if (!attributes.antialias && (attributes.stencil || attributes.depth))
GL_GenRenderbuffers(1, &m_depthStencilBuffer);
// If necessary, create another framebuffer for the multisample results.
if (attributes.antialias) {
GL_GenFramebuffers(1, &m_multisampleFBO);
GL_BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO);
m_state.boundDrawFBO = m_state.boundReadFBO = m_multisampleFBO;
GL_GenRenderbuffers(1, &m_multisampleColorBuffer);
if (attributes.stencil || attributes.depth)
GL_GenRenderbuffers(1, &m_multisampleDepthStencilBuffer);
} else if (attributes.preserveDrawingBuffer) {
// If necessary, create another texture to handle preserveDrawingBuffer:true without
// antialiasing.
GL_GenTextures(1, &m_preserveDrawingBufferTexture);
GL_BindTexture(GL_TEXTURE_2D, m_preserveDrawingBufferTexture);
GL_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
GL_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GL_BindTexture(GL_TEXTURE_2D, 0);
// Create an FBO with which to perform BlitFramebuffer from one texture to the other.
GL_GenFramebuffers(1, &m_preserveDrawingBufferFBO);
}
GL_ClearColor(0, 0, 0, 0);
#if PLATFORM(MAC)
if (!attributes.useMetal && attributes.effectivePowerPreference() == GraphicsContextGLPowerPreference::HighPerformance)
m_switchesGPUOnDisplayReconfiguration = true;
#endif
return GraphicsContextGLANGLE::platformInitialize();
}
GraphicsContextGLANGLE::~GraphicsContextGLANGLE()
{
if (makeContextCurrent()) {
if (m_texture)
GL_DeleteTextures(1, &m_texture);
if (m_multisampleColorBuffer)
GL_DeleteRenderbuffers(1, &m_multisampleColorBuffer);
if (m_multisampleDepthStencilBuffer)
GL_DeleteRenderbuffers(1, &m_multisampleDepthStencilBuffer);
if (m_multisampleFBO)
GL_DeleteFramebuffers(1, &m_multisampleFBO);
if (m_depthStencilBuffer)
GL_DeleteRenderbuffers(1, &m_depthStencilBuffer);
if (m_fbo)
GL_DeleteFramebuffers(1, &m_fbo);
if (m_preserveDrawingBufferTexture)
GL_DeleteTextures(1, &m_preserveDrawingBufferTexture);
if (m_preserveDrawingBufferFBO)
GL_DeleteFramebuffers(1, &m_preserveDrawingBufferFBO);
// If fences are not enabled, this loop will not execute.
for (auto& fence : m_frameCompletionFences)
fence.reset();
} else {
for (auto& fence : m_frameCompletionFences)
fence.abandon();
}
if (m_displayBufferPbuffer)
EGL_DestroySurface(m_displayObj, m_displayBufferPbuffer);
auto recycledBuffer = m_swapChain.recycleBuffer();
if (recycledBuffer.handle)
EGL_DestroySurface(m_displayObj, recycledBuffer.handle);
auto contentsHandle = m_swapChain.detachClient();
if (contentsHandle)
EGL_DestroySurface(m_displayObj, contentsHandle);
if (m_contextObj) {
makeCurrent(m_displayObj, EGL_NO_CONTEXT);
EGL_DestroyContext(m_displayObj, m_contextObj);
}
ASSERT(currentContext != this);
m_drawingBufferTextureTarget = -1;
LOG(WebGL, "Destroyed a GraphicsContextGLANGLE (%p).", this);
}
bool GraphicsContextGLANGLE::makeContextCurrent()
{
if (!m_contextObj)
return false;
// If there is no drawing buffer, we failed to allocate one during preparing for display.
// The exception is the case when the context is used before reshaping.
if (!m_displayBufferBacking && !getInternalFramebufferSize().isEmpty())
return false;
if (currentContext == this)
return true;
// Calling MakeCurrent is important to set volatile platform context. See initializeEGLDisplay().
if (!EGL_MakeCurrent(m_displayObj, EGL_NO_SURFACE, EGL_NO_SURFACE, m_contextObj))
return false;
if (isCurrentContextPredictable())
currentContext = this;
return true;
}
void GraphicsContextGLANGLE::checkGPUStatus()
{
if (m_failNextStatusCheck) {
LOG(WebGL, "Pretending the GPU has reset (%p). Lose the context.", this);
m_failNextStatusCheck = false;
forceContextLost();
makeCurrent(m_displayObj, EGL_NO_CONTEXT);
return;
}
// Only do the check every statusCheckThreshold calls.
if (m_statusCheckCount)
return;
m_statusCheckCount = (m_statusCheckCount + 1) % statusCheckThreshold;
// FIXME: check via KHR_robustness.
}
void GraphicsContextGLCocoa::setContextVisibility(bool isVisible)
{
#if PLATFORM(MAC)
if (!m_switchesGPUOnDisplayReconfiguration)
return;
if (isVisible)
m_highPerformanceGPURequest = ScopedHighPerformanceGPURequest::acquire();
else
m_highPerformanceGPURequest = { };
#else
UNUSED_PARAM(isVisible);
#endif
}
#if PLATFORM(MAC)
void GraphicsContextGLCocoa::updateContextOnDisplayReconfiguration()
{
if (m_switchesGPUOnDisplayReconfiguration)
EGL_HandleGPUSwitchANGLE(m_displayObj);
dispatchContextChangedNotification();
}
#endif
bool GraphicsContextGLCocoa::reshapeDisplayBufferBacking()
{
ASSERT(!getInternalFramebufferSize().isEmpty());
// Reset the current backbuffer now before allocating a new one in order to slightly reduce memory pressure.
if (m_displayBufferBacking) {
m_displayBufferBacking.reset();
EGL_ReleaseTexImage(m_displayObj, m_displayBufferPbuffer, EGL_BACK_BUFFER);
EGL_DestroySurface(m_displayObj, m_displayBufferPbuffer);
m_displayBufferPbuffer = EGL_NO_SURFACE;
}
// Reset the future recycled buffer now, because it most likely will not be reusable at the time it will be reused.
auto recycledBuffer = m_swapChain.recycleBuffer();
if (recycledBuffer.handle)
EGL_DestroySurface(m_displayObj, recycledBuffer.handle);
recycledBuffer.surface.reset();
return allocateAndBindDisplayBufferBacking();
}
bool GraphicsContextGLCocoa::allocateAndBindDisplayBufferBacking()
{
ASSERT(!getInternalFramebufferSize().isEmpty());
auto backing = IOSurface::create(nullptr, getInternalFramebufferSize(), DestinationColorSpace::SRGB());
if (!backing)
return false;
if (m_resourceOwner)
backing->setOwnershipIdentity(m_resourceOwner);
backing->migrateColorSpaceToProperties();
const bool usingAlpha = contextAttributes().alpha;
const auto size = getInternalFramebufferSize();
const EGLint surfaceAttributes[] = {
EGL_WIDTH, size.width(),
EGL_HEIGHT, size.height(),
EGL_IOSURFACE_PLANE_ANGLE, 0,
EGL_TEXTURE_TARGET, EGLDrawingBufferTextureTargetForDrawingTarget(drawingBufferTextureTarget()),
EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, usingAlpha ? GL_BGRA_EXT : GL_RGB,
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_TEXTURE_TYPE_ANGLE, GL_UNSIGNED_BYTE,
// Only has an effect on the iOS Simulator.
EGL_IOSURFACE_USAGE_HINT_ANGLE, EGL_IOSURFACE_WRITE_HINT_ANGLE,
EGL_NONE, EGL_NONE
};
EGLSurface pbuffer = EGL_CreatePbufferFromClientBuffer(m_displayObj, EGL_IOSURFACE_ANGLE, backing->surface(), m_configObj, surfaceAttributes);
if (!pbuffer)
return false;
return bindDisplayBufferBacking(WTFMove(backing), pbuffer);
}
bool GraphicsContextGLCocoa::bindDisplayBufferBacking(std::unique_ptr<IOSurface> backing, void* pbuffer)
{
GCGLenum textureTarget = drawingBufferTextureTarget();
ScopedRestoreTextureBinding restoreBinding(drawingBufferTextureTargetQueryForDrawingTarget(textureTarget), textureTarget, textureTarget != TEXTURE_RECTANGLE_ARB);
GL_BindTexture(textureTarget, m_texture);
if (!EGL_BindTexImage(m_displayObj, pbuffer, EGL_BACK_BUFFER)) {
EGL_DestroySurface(m_displayObj, pbuffer);
return false;
}
m_displayBufferPbuffer = pbuffer;
m_displayBufferBacking = WTFMove(backing);
return true;
}
bool GraphicsContextGLANGLE::makeCurrent(GCGLDisplay display, GCGLContext context)
{
currentContext = nullptr;
return EGL_MakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
}
void* GraphicsContextGLCocoa::createPbufferAndAttachIOSurface(GCGLenum target, PbufferAttachmentUsage usage, GCGLenum internalFormat, GCGLsizei width, GCGLsizei height, GCGLenum type, IOSurfaceRef surface, GCGLuint plane)
{
if (target != GraphicsContextGLANGLE::drawingBufferTextureTarget()) {
LOG(WebGL, "Unknown texture target %d.", static_cast<int>(target));
return nullptr;
}
auto usageHint = [&] () -> EGLint {
if (usage == PbufferAttachmentUsage::Read)
return EGL_IOSURFACE_READ_HINT_ANGLE;
if (usage == PbufferAttachmentUsage::Write)
return EGL_IOSURFACE_WRITE_HINT_ANGLE;
return EGL_IOSURFACE_READ_HINT_ANGLE | EGL_IOSURFACE_WRITE_HINT_ANGLE;
}();
return WebCore::createPbufferAndAttachIOSurface(m_displayObj, m_configObj, target, usageHint, internalFormat, width, height, type, surface, plane);
}
void GraphicsContextGLCocoa::destroyPbufferAndDetachIOSurface(void* handle)
{
WebCore::destroyPbufferAndDetachIOSurface(m_displayObj, handle);
}
#if !PLATFORM(IOS_FAMILY_SIMULATOR)
void* GraphicsContextGLCocoa::attachIOSurfaceToSharedTexture(GCGLenum target, IOSurface* surface)
{
constexpr EGLint emptyAttributes[] = { EGL_NONE };
// Create a MTLTexture out of the IOSurface.
// FIXME: We need to use the same device that ANGLE is using, which might not be the default.
RetainPtr<MTLSharedTextureHandle> handle = adoptNS([[MTLSharedTextureHandle alloc] initWithIOSurface:surface->surface() label:@"WebXR"]);
if (!handle) {
LOG(WebGL, "Unable to create a MTLSharedTextureHandle from the IOSurface in attachIOSurfaceToTexture.");
return nullptr;
}
if (!handle.get().device) {
LOG(WebGL, "MTLSharedTextureHandle does not have a Metal device in attachIOSurfaceToTexture.");
return nullptr;
}
auto texture = adoptNS([handle.get().device newSharedTextureWithHandle:handle.get()]);
if (!texture) {
LOG(WebGL, "Unable to create a MTLSharedTexture from the texture handle in attachIOSurfaceToTexture.");
return nullptr;
}
// FIXME: Does the texture have the correct usage mode?
// Create an EGLImage out of the MTLTexture
auto display = platformDisplay();
auto eglImage = EGL_CreateImageKHR(display, EGL_NO_CONTEXT, EGL_METAL_TEXTURE_ANGLE, reinterpret_cast<EGLClientBuffer>(texture.get()), emptyAttributes);
if (!eglImage) {
LOG(WebGL, "Unable to create an EGLImage from the Metal handle in attachIOSurfaceToTexture.");
return nullptr;
}
// Tell the currently bound texture to use the EGLImage.
GL_EGLImageTargetTexture2DOES(target, eglImage);
return eglImage;
}
void GraphicsContextGLCocoa::detachIOSurfaceFromSharedTexture(void* handle)
{
auto display = platformDisplay();
EGL_DestroyImageKHR(display, handle);
}
#endif
void GraphicsContextGLCocoa::prepareForDisplay()
{
if (m_layerComposited)
return;
if (!makeContextCurrent())
return;
prepareTexture();
// The IOSurface will be used from other graphics subsystem, so flush GL commands.
GL_Flush();
auto recycledBuffer = m_swapChain.recycleBuffer();
EGL_ReleaseTexImage(m_displayObj, m_displayBufferPbuffer, EGL_BACK_BUFFER);
m_swapChain.present({ WTFMove(m_displayBufferBacking), m_displayBufferPbuffer });
m_displayBufferPbuffer = EGL_NO_SURFACE;
bool hasNewBacking = false;
if (recycledBuffer.surface && recycledBuffer.surface->size() == getInternalFramebufferSize()) {
hasNewBacking = bindDisplayBufferBacking(WTFMove(recycledBuffer.surface), recycledBuffer.handle);
recycledBuffer.handle = nullptr;
}
recycledBuffer.surface.reset();
if (recycledBuffer.handle)
EGL_DestroySurface(m_displayObj, recycledBuffer.handle);
// Error will be handled by next call to makeContextCurrent() which will notice lack of display buffer.
if (!hasNewBacking)
allocateAndBindDisplayBufferBacking();
markLayerComposited();
if (m_useFenceSyncForDisplayRateLimit) {
bool success = waitAndUpdateOldestFrame();
UNUSED_VARIABLE(success); // FIXME: implement context lost.
}
}
#if ENABLE(VIDEO)
GraphicsContextGLCV* GraphicsContextGLCocoa::asCV()
{
if (!m_cv)
m_cv = GraphicsContextGLCVCocoa::create(*this);
return m_cv.get();
}
#endif
RefPtr<PixelBuffer> GraphicsContextGLANGLE::readCompositedResults()
{
auto& displayBuffer = m_swapChain.displayBuffer();
if (!displayBuffer.surface || !displayBuffer.handle)
return nullptr;
if (displayBuffer.surface->size() != getInternalFramebufferSize())
return nullptr;
// Note: We are using GL to read the IOSurface. At the time of writing, there are no convinient
// functions to convert the IOSurface pixel data to ImageData. The image data ends up being
// drawn to a ImageBuffer, but at the time there's no functions to construct a NativeImage
// out of an IOSurface in such a way that drawing the NativeImage would be guaranteed leave
// the IOSurface be unrefenced after the draw call finishes.
ScopedTexture texture;
GCGLenum textureTarget = drawingBufferTextureTarget();
ScopedRestoreTextureBinding restoreBinding(drawingBufferTextureTargetQueryForDrawingTarget(drawingBufferTextureTarget()), textureTarget, textureTarget != TEXTURE_RECTANGLE_ARB);
GL_BindTexture(textureTarget, texture);
if (!EGL_BindTexImage(m_displayObj, displayBuffer.handle, EGL_BACK_BUFFER))
return nullptr;
GL_TexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
ScopedFramebuffer fbo;
ScopedRestoreReadFramebufferBinding fboBinding(m_isForWebGL2, m_state.boundReadFBO, fbo);
GL_FramebufferTexture2D(fboBinding.framebufferTarget(), GL_COLOR_ATTACHMENT0, textureTarget, texture, 0);
ASSERT(GL_CheckFramebufferStatus(fboBinding.framebufferTarget()) == GL_FRAMEBUFFER_COMPLETE);
auto result = readPixelsForPaintResults();
EGLBoolean releaseOk = EGL_ReleaseTexImage(m_displayObj, displayBuffer.handle, EGL_BACK_BUFFER);
ASSERT_UNUSED(releaseOk, releaseOk);
return result;
}
#if ENABLE(MEDIA_STREAM)
RefPtr<VideoFrame> GraphicsContextGLCocoa::paintCompositedResultsToVideoFrame()
{
auto &displayBuffer = m_swapChain.displayBuffer();
if (!displayBuffer.surface || !displayBuffer.handle)
return nullptr;
if (displayBuffer.surface->size() != getInternalFramebufferSize())
return nullptr;
// Display surface is not marked in use since we will mirror and rotate it explicitly.
auto pixelBuffer = createCVPixelBuffer(displayBuffer.surface->surface());
if (!pixelBuffer)
return nullptr;
// Mirror and rotate the pixel buffer explicitly, as WebRTC encoders cannot mirror.
auto size = getInternalFramebufferSize();
if (!m_mediaSampleRotationSession || m_mediaSampleRotationSessionSize != size)
m_mediaSampleRotationSession = makeUnique<ImageRotationSessionVT>(ImageRotationSessionVT::RotationProperties { true, false, 180 }, size, ImageRotationSessionVT::IsCGImageCompatible::No);
auto mediaSamplePixelBuffer = m_mediaSampleRotationSession->rotate(pixelBuffer->get());
if (!mediaSamplePixelBuffer)
return nullptr;
if (m_resourceOwner)
setOwnershipIdentityForCVPixelBuffer(mediaSamplePixelBuffer.get(), m_resourceOwner);
return VideoFrameCV::create({ }, false, VideoFrame::Rotation::None, WTFMove(mediaSamplePixelBuffer));
}
#endif
void GraphicsContextGLANGLE::platformReleaseThreadResources()
{
currentContext = nullptr;
}
#if ENABLE(VIDEO)
bool GraphicsContextGLCocoa::copyTextureFromMedia(MediaPlayer& player, PlatformGLObject outputTexture, GCGLenum outputTarget, GCGLint level, GCGLenum internalFormat, GCGLenum format, GCGLenum type, bool premultiplyAlpha, bool flipY)
{
auto videoFrame = player.videoFrameForCurrentTime();
if (!videoFrame)
return false;
auto videoFrameCV = videoFrame->asVideoFrameCV();
if (!videoFrameCV)
return false;
auto contextCV = asCV();
if (!contextCV)
return false;
UNUSED_VARIABLE(premultiplyAlpha);
ASSERT_UNUSED(outputTarget, outputTarget == GraphicsContextGL::TEXTURE_2D);
return contextCV->copyVideoSampleToTexture(*videoFrameCV, outputTexture, level, internalFormat, format, type, GraphicsContextGL::FlipY(flipY));
}
#endif
GCGLDisplay GraphicsContextGLANGLE::platformDisplay() const
{
return m_displayObj;
}
GCGLConfig GraphicsContextGLANGLE::platformConfig() const
{
return m_configObj;
}
RefPtr<GraphicsLayerContentsDisplayDelegate> GraphicsContextGLCocoa::layerContentsDisplayDelegate()
{
return nullptr;
}
void GraphicsContextGLCocoa::invalidateKnownTextureContent(GCGLuint texture)
{
if (m_cv)
m_cv->invalidateKnownTextureContent(texture);
}
}
#endif // ENABLE(WEBGL)