Commit 40eefabd by Corentin Wallez

Rewrite WindowSurfaceCGL to use CAOpenGLLayer

It used to render to IOSurfaces and use setContents to directly give them to the OSX compositor. This was worth the complexity only because Chrome going to use that code path. This is no longer the case. Instead we replace the implementation with one based on CAOpenGLLayer, that basically gets a "draw" callback every frame. The complexity comes from the fact that this callback is called from another thread, with another CGL context. BUG=angleproject:1233 Change-Id: I1878d0071d057e043e0bb9043d9849f50e00d023 Reviewed-on: https://chromium-review.googlesource.com/314031Reviewed-by: 's avatarccameron chromium <ccameron@chromium.org> Tryjob-Request: Corentin Wallez <cwallez@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Tested-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent dd5c5b79
...@@ -113,7 +113,7 @@ SurfaceImpl *DisplayCGL::createWindowSurface(const egl::Config *configuration, ...@@ -113,7 +113,7 @@ SurfaceImpl *DisplayCGL::createWindowSurface(const egl::Config *configuration,
EGLNativeWindowType window, EGLNativeWindowType window,
const egl::AttributeMap &attribs) const egl::AttributeMap &attribs)
{ {
return new WindowSurfaceCGL(this->getRenderer(), window, mFunctions); return new WindowSurfaceCGL(this->getRenderer(), window, mFunctions, mContext);
} }
SurfaceImpl *DisplayCGL::createPbufferSurface(const egl::Config *configuration, SurfaceImpl *DisplayCGL::createPbufferSurface(const egl::Config *configuration,
......
...@@ -11,10 +11,14 @@ ...@@ -11,10 +11,14 @@
#include "libANGLE/renderer/gl/SurfaceGL.h" #include "libANGLE/renderer/gl/SurfaceGL.h"
struct _CGLContextObject;
typedef _CGLContextObject *CGLContextObj;
@class CALayer; @class CALayer;
struct __IOSurface; struct __IOSurface;
typedef __IOSurface *IOSurfaceRef; typedef __IOSurface *IOSurfaceRef;
@class SwapLayer;
namespace rx namespace rx
{ {
...@@ -24,12 +28,36 @@ class FunctionsGL; ...@@ -24,12 +28,36 @@ class FunctionsGL;
class StateManagerGL; class StateManagerGL;
struct WorkaroundsGL; struct WorkaroundsGL;
class DisplayLink; struct SharedSwapState
{
struct SwapTexture
{
GLuint texture;
unsigned int width;
unsigned int height;
uint64_t swapId;
};
SwapTexture textures[3];
// This code path is not going to be used by Chrome so we take the liberty
// to use pthreads directly instead of using mutexes and condition variables
// via the Platform API.
pthread_mutex_t mutex;
// The following members should be accessed only when holding the mutex
// (or doing construction / destruction)
SwapTexture *beingRendered;
SwapTexture *lastRendered;
SwapTexture *beingPresented;
};
class WindowSurfaceCGL : public SurfaceGL class WindowSurfaceCGL : public SurfaceGL
{ {
public: public:
WindowSurfaceCGL(RendererGL *renderer, CALayer *layer, const FunctionsGL *functions); WindowSurfaceCGL(RendererGL *renderer,
CALayer *layer,
const FunctionsGL *functions,
CGLContextObj context);
~WindowSurfaceCGL() override; ~WindowSurfaceCGL() override;
egl::Error initialize() override; egl::Error initialize() override;
...@@ -51,27 +79,16 @@ class WindowSurfaceCGL : public SurfaceGL ...@@ -51,27 +79,16 @@ class WindowSurfaceCGL : public SurfaceGL
FramebufferImpl *createDefaultFramebuffer(const gl::Framebuffer::Data &data) override; FramebufferImpl *createDefaultFramebuffer(const gl::Framebuffer::Data &data) override;
private: private:
struct Surface SwapLayer *mSwapLayer;
{ SharedSwapState mSwapState;
IOSurfaceRef ioSurface; uint64_t mCurrentSwapId;
GLuint texture;
uint64_t lastPresentNanos;
};
void freeSurfaceData(Surface *surface);
egl::Error initializeSurfaceData(Surface *surface, int width, int height);
CALayer *mLayer; CALayer *mLayer;
CGLContextObj mContext;
const FunctionsGL *mFunctions; const FunctionsGL *mFunctions;
StateManagerGL *mStateManager; StateManagerGL *mStateManager;
const WorkaroundsGL &mWorkarounds; const WorkaroundsGL &mWorkarounds;
DisplayLink *mDisplayLink;
// CGL doesn't have a default framebuffer, we instead render to an IOSurface
// that will be set as the content of the CALayer which is our native window.
// We use two IOSurfaces to do double buffering.
Surface mSurfaces[2];
int mCurrentSurface;
GLuint mFramebuffer; GLuint mFramebuffer;
GLuint mDSRenderbuffer; GLuint mDSRenderbuffer;
}; };
......
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
#include "libANGLE/renderer/gl/cgl/WindowSurfaceCGL.h" #include "libANGLE/renderer/gl/cgl/WindowSurfaceCGL.h"
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <OpenGL/OpenGL.h> #include <OpenGL/OpenGL.h>
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
...@@ -20,146 +18,148 @@ ...@@ -20,146 +18,148 @@
#include "libANGLE/renderer/gl/RendererGL.h" #include "libANGLE/renderer/gl/RendererGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h" #include "libANGLE/renderer/gl/StateManagerGL.h"
#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 @interface SwapLayer : CAOpenGLLayer
#include <iostream>
namespace
{ {
CGLContextObj mDisplayContext;
// All time computations in the file are done in nanoseconds but both the bool initialized;
// current time and the screen's present time are given as "mach time". rx::SharedSwapState *mSwapState;
// The first thing we will do after receiving a mach time is convert it. const rx::FunctionsGL *mFunctions;
uint64_t MachTimeToNanoseconds(uint64_t machTime)
{
static mach_timebase_info_data_t timebaseInfo;
if (timebaseInfo.denom == 0)
{
mach_timebase_info(&timebaseInfo);
}
return (machTime * timebaseInfo.numer) / timebaseInfo.denom;
}
uint64_t NowInNanoseconds()
{
return MachTimeToNanoseconds(mach_absolute_time());
}
GLuint mReadFramebuffer;
} }
- (id)initWithSharedState:(rx::SharedSwapState *)swapState
namespace rx withContext:(CGLContextObj)displayContext
{ withFunctions:(const rx::FunctionsGL *)functions;
@end
// A wrapper around CoreVideo's DisplayLink. It polls the screen vsync time,
// and allows computing when is the next present time after a given time point. @implementation SwapLayer
class DisplayLink - (id)initWithSharedState:(rx::SharedSwapState *)swapState
{ withContext:(CGLContextObj)displayContext
public: withFunctions:(const rx::FunctionsGL *)functions
DisplayLink() : mDisplayLink(nullptr), mHostTimeRemainderNanos(0), mPresentIntervalNanos(0) {}
~DisplayLink()
{ {
if (mDisplayLink != nullptr) self = [super init];
if (self != nil)
{ {
stop(); self.asynchronous = YES;
CVDisplayLinkRelease(mDisplayLink); mDisplayContext = displayContext;
mDisplayLink = nullptr;
initialized = false;
mSwapState = swapState;
mFunctions = functions;
[self setFrame:CGRectMake(0, 0, mSwapState->textures[0].width,
mSwapState->textures[0].height)];
} }
return self;
} }
void start() - (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
{ {
CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink); CGLPixelFormatAttribute attribs[] = {
ASSERT(mDisplayLink != nullptr); kCGLPFADisplayMask, static_cast<CGLPixelFormatAttribute>(mask), kCGLPFAOpenGLProfile,
CVDisplayLinkSetOutputCallback(mDisplayLink, &Callback, this); static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_3_2_Core),
CVDisplayLinkStart(mDisplayLink); static_cast<CGLPixelFormatAttribute>(0)};
CGLPixelFormatObj pixelFormat = nullptr;
GLint numFormats = 0;
CGLChoosePixelFormat(attribs, &pixelFormat, &numFormats);
return pixelFormat;
} }
void stop() - (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
{ {
ASSERT(mDisplayLink != nullptr); CGLContextObj context = nullptr;
CVDisplayLinkStop(mDisplayLink); CGLCreateContext(pixelFormat, mDisplayContext, &context);
return context;
} }
uint64_t nextPresentAfter(uint64_t time) - (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
pixelFormat:(CGLPixelFormatObj)pixelFormat
forLayerTime:(CFTimeInterval)timeInterval
displayTime:(const CVTimeStamp *)timeStamp
{ {
// Load the two variables locally to avoid them changing in the middle of the computation. BOOL result = NO;
uint64_t presentInterval = mPresentIntervalNanos;
uint64_t remainder = mHostTimeRemainderNanos;
if (presentInterval == 0) pthread_mutex_lock(&mSwapState->mutex);
{ {
return time; if (mSwapState->lastRendered->swapId > mSwapState->beingPresented->swapId)
{
std::swap(mSwapState->lastRendered, mSwapState->beingPresented);
result = YES;
}
} }
pthread_mutex_unlock(&mSwapState->mutex);
uint64_t nextSwapNumber = (time - remainder) / presentInterval + 1; return result;
return nextSwapNumber * presentInterval + remainder;
} }
uint64_t getPresentInterval() const { return mPresentIntervalNanos; } - (void)drawInCGLContext:(CGLContextObj)glContext
pixelFormat:(CGLPixelFormatObj)pixelFormat
private: forLayerTime:(CFTimeInterval)timeInterval
static CVReturn Callback(CVDisplayLinkRef displayLink, displayTime:(const CVTimeStamp *)timeStamp
const CVTimeStamp *now,
const CVTimeStamp *outputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *userData)
{ {
DisplayLink *link = reinterpret_cast<DisplayLink *>(userData); CGLSetCurrentContext(glContext);
link->tick(*outputTime); if (!initialized)
return kCVReturnSuccess; {
initialized = true;
mFunctions->genFramebuffers(1, &mReadFramebuffer);
}
const auto &texture = *mSwapState->beingPresented;
if ([self frame].size.width != texture.width || [self frame].size.height != texture.height)
{
[self setFrame:CGRectMake(0, 0, texture.width, texture.height)];
}
// TODO(cwallez) support 2.1 contexts too that don't have blitFramebuffer nor the
// GL_DRAW_FRAMEBUFFER_BINDING query
GLint drawFBO;
mFunctions->getIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFBO);
mFunctions->bindFramebuffer(GL_FRAMEBUFFER, mReadFramebuffer);
mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
texture.texture, 0);
mFunctions->bindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer);
mFunctions->bindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFBO);
mFunctions->blitFramebuffer(0, 0, texture.width, texture.height, 0, 0, texture.width,
texture.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Call the super method to flush the context
[super drawInCGLContext:glContext
pixelFormat:pixelFormat
forLayerTime:timeInterval
displayTime:timeStamp];
} }
@end
void tick(const CVTimeStamp &outputTime) namespace rx
{ {
// This is called from a special high priority thread so by setting
// both member variables here we have a data race. However if we assume
// the present interval doesn't change, the data race disappears after
// the first tick.
// In addition computations using these member variables are made to
// produce a sensible result if one of the two members has the default
// value of 0.
uint64_t hostTimeNanos = MachTimeToNanoseconds(outputTime.hostTime);
uint64_t numerator = outputTime.videoRefreshPeriod;
uint64_t denominator = outputTime.videoTimeScale;
uint64_t presentIntervalNanos = (1000000000 * numerator) / denominator;
mHostTimeRemainderNanos = hostTimeNanos % presentIntervalNanos;
mPresentIntervalNanos = presentIntervalNanos;
}
CVDisplayLinkRef mDisplayLink; WindowSurfaceCGL::WindowSurfaceCGL(RendererGL *renderer,
CALayer *layer,
uint64_t mHostTimeRemainderNanos; const FunctionsGL *functions,
uint64_t mPresentIntervalNanos; CGLContextObj context)
}; : SurfaceGL(renderer),
mSwapLayer(nil),
WindowSurfaceCGL::WindowSurfaceCGL(RendererGL *renderer, mCurrentSwapId(0),
CALayer *layer, mLayer(layer),
const FunctionsGL *functions) mContext(context),
: SurfaceGL(renderer), mFunctions(functions),
mLayer(layer), mStateManager(renderer->getStateManager()),
mFunctions(functions), mWorkarounds(renderer->getWorkarounds()),
mStateManager(renderer->getStateManager()), mFramebuffer(0),
mWorkarounds(renderer->getWorkarounds()), mDSRenderbuffer(0)
mDisplayLink(nullptr),
mCurrentSurface(0),
mFramebuffer(0),
mDSRenderbuffer(0)
{ {
for (auto &surface : mSurfaces) pthread_mutex_init(&mSwapState.mutex, nullptr);
{
surface.texture = 0;
surface.ioSurface = nil;
}
} }
WindowSurfaceCGL::~WindowSurfaceCGL() WindowSurfaceCGL::~WindowSurfaceCGL()
{ {
pthread_mutex_destroy(&mSwapState.mutex);
if (mFramebuffer != 0) if (mFramebuffer != 0)
{ {
mFunctions->deleteFramebuffers(1, &mFramebuffer); mFunctions->deleteFramebuffers(1, &mFramebuffer);
...@@ -172,14 +172,19 @@ WindowSurfaceCGL::~WindowSurfaceCGL() ...@@ -172,14 +172,19 @@ WindowSurfaceCGL::~WindowSurfaceCGL()
mDSRenderbuffer = 0; mDSRenderbuffer = 0;
} }
for (auto &surface : mSurfaces) if (mSwapLayer != nil)
{ {
freeSurfaceData(&surface); [mSwapLayer release];
mSwapLayer = nil;
} }
if (mDisplayLink != nullptr) for (size_t i = 0; i < ArraySize(mSwapState.textures); ++i)
{ {
SafeDelete(mDisplayLink); if (mSwapState.textures[i].texture != 0)
{
mFunctions->deleteTextures(1, &mSwapState.textures[i].texture);
mSwapState.textures[i].texture = 0;
}
} }
} }
...@@ -188,11 +193,24 @@ egl::Error WindowSurfaceCGL::initialize() ...@@ -188,11 +193,24 @@ egl::Error WindowSurfaceCGL::initialize()
unsigned width = getWidth(); unsigned width = getWidth();
unsigned height = getHeight(); unsigned height = getHeight();
for (auto &surface : mSurfaces) for (size_t i = 0; i < ArraySize(mSwapState.textures); ++i)
{ {
surface.lastPresentNanos = 0; mFunctions->genTextures(1, &mSwapState.textures[i].texture);
initializeSurfaceData(&surface, width, height); mStateManager->bindTexture(GL_TEXTURE_2D, mSwapState.textures[i].texture);
mFunctions->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
mSwapState.textures[i].width = width;
mSwapState.textures[i].height = height;
mSwapState.textures[i].swapId = 0;
} }
mSwapState.beingRendered = &mSwapState.textures[0];
mSwapState.lastRendered = &mSwapState.textures[1];
mSwapState.beingPresented = &mSwapState.textures[2];
mSwapLayer = [[SwapLayer alloc] initWithSharedState:&mSwapState
withContext:mContext
withFunctions:mFunctions];
[mLayer addSublayer:mSwapLayer];
mFunctions->genRenderbuffers(1, &mDSRenderbuffer); mFunctions->genRenderbuffers(1, &mDSRenderbuffer);
mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mDSRenderbuffer); mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mDSRenderbuffer);
...@@ -200,93 +218,47 @@ egl::Error WindowSurfaceCGL::initialize() ...@@ -200,93 +218,47 @@ egl::Error WindowSurfaceCGL::initialize()
mFunctions->genFramebuffers(1, &mFramebuffer); mFunctions->genFramebuffers(1, &mFramebuffer);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
mSurfaces[0].texture, 0); mSwapState.beingRendered->texture, 0);
mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
mDSRenderbuffer); mDSRenderbuffer);
mDisplayLink = new DisplayLink;
mDisplayLink->start();
return egl::Error(EGL_SUCCESS); return egl::Error(EGL_SUCCESS);
} }
egl::Error WindowSurfaceCGL::makeCurrent() egl::Error WindowSurfaceCGL::makeCurrent()
{ {
// TODO(cwallez) if it is the first makeCurrent set the viewport and scissor?
return egl::Error(EGL_SUCCESS); return egl::Error(EGL_SUCCESS);
} }
egl::Error WindowSurfaceCGL::swap() egl::Error WindowSurfaceCGL::swap()
{ {
// Because we are rendering to IOSurfaces we have to implement swapBuffers ourselves mFunctions->flush();
// (contrary to GLX, WGL or EGL) and have to send the IOSurfaces for presentation at the mSwapState.beingRendered->swapId = ++mCurrentSwapId;
// right time to avoid causing tearing or flickering. Likewise we must make sure we do
// not render to an IOSurface that is still being presented.
// ANGLE standalone cannot post function to be executed at a later time so we can only
// implement basic synchronization with the present time by sleeping.
uint64_t presentInterval = mDisplayLink->getPresentInterval();
uint64_t now = NowInNanoseconds();
pthread_mutex_lock(&mSwapState.mutex);
{ {
Surface &surface = mSurfaces[mCurrentSurface]; std::swap(mSwapState.beingRendered, mSwapState.lastRendered);
// A flush is needed for the IOSurface to get the result of the GL operations
// as specified in the documentation of CGLTexImageIOSurface2D
mFunctions->flush();
// We can only send the IOSurface to the CALayer during a present window
// that is in the middle of two vsyncs, otherwise flickering can happen.
{
// The present window was determined empirically.
static const double kPresentWindowMin = 0.3;
static const double kPresentWindowMax = 0.7;
uint64_t nextPresent = mDisplayLink->nextPresentAfter(now);
uint64_t presentWindowMin = nextPresent - presentInterval * (1.0 - kPresentWindowMin);
uint64_t presentWindowMax = nextPresent - presentInterval * (1.0 - kPresentWindowMax);
if (now <= presentWindowMin)
{
usleep((presentWindowMin - now) / 1000);
}
else if (now >= presentWindowMax)
{
uint64_t presentTarget = nextPresent + presentInterval * kPresentWindowMin;
usleep((presentTarget - now) / 1000);
}
}
// Put the IOSurface as the content of the layer
[CATransaction begin];
[mLayer setContents:(id)surface.ioSurface];
[CATransaction commit];
surface.lastPresentNanos = NowInNanoseconds();
} }
pthread_mutex_unlock(&mSwapState.mutex);
mCurrentSurface = (mCurrentSurface + 1) % ArraySize(mSurfaces); unsigned width = getWidth();
unsigned height = getHeight();
auto &texture = *mSwapState.beingRendered;
if (texture.width != width || texture.height != texture.height)
{ {
Surface &surface = mSurfaces[mCurrentSurface]; mStateManager->bindTexture(GL_TEXTURE_2D, texture.texture);
mFunctions->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
{ GL_UNSIGNED_BYTE, nullptr);
// We need to wait a bit after a swap before rendering to the IOSurface again texture.width = width;
// otherwise with a small desync between clocks could cause a flickering. texture.height = height;
static const double kDrawWindowStart = 0.05;
uint64_t timePresentFinishes = mDisplayLink->nextPresentAfter(surface.lastPresentNanos);
timePresentFinishes += presentInterval * kDrawWindowStart;
if (now < timePresentFinishes)
{
usleep((timePresentFinishes - now) / 1000);
}
}
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_RECTANGLE_ARB, surface.texture, 0);
} }
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
mSwapState.beingRendered->texture, 0);
return egl::Error(EGL_SUCCESS); return egl::Error(EGL_SUCCESS);
} }
...@@ -316,7 +288,7 @@ egl::Error WindowSurfaceCGL::releaseTexImage(EGLint buffer) ...@@ -316,7 +288,7 @@ egl::Error WindowSurfaceCGL::releaseTexImage(EGLint buffer)
void WindowSurfaceCGL::setSwapInterval(EGLint interval) void WindowSurfaceCGL::setSwapInterval(EGLint interval)
{ {
// TODO(cwallez) investigate the need for swapIntervals other than 1 // TODO(cwallez) investigate implementing swap intervals other than 0
} }
EGLint WindowSurfaceCGL::getWidth() const EGLint WindowSurfaceCGL::getWidth() const
...@@ -346,53 +318,4 @@ FramebufferImpl *WindowSurfaceCGL::createDefaultFramebuffer(const gl::Framebuffe ...@@ -346,53 +318,4 @@ FramebufferImpl *WindowSurfaceCGL::createDefaultFramebuffer(const gl::Framebuffe
return new FramebufferGL(mFramebuffer, data, mFunctions, mWorkarounds, mStateManager); return new FramebufferGL(mFramebuffer, data, mFunctions, mWorkarounds, mStateManager);
} }
void WindowSurfaceCGL::freeSurfaceData(Surface *surface)
{
if (surface->texture != 0)
{
mFunctions->deleteTextures(1, &surface->texture);
surface->texture = 0;
}
if (surface->ioSurface != nil)
{
CFRelease(surface->ioSurface);
surface->ioSurface = nil;
}
}
egl::Error WindowSurfaceCGL::initializeSurfaceData(Surface *surface, int width, int height)
{
CFDictionaryRef ioSurfaceOptions = nil;
{
unsigned pixelFormat = 'BGRA';
const unsigned kBytesPerElement = 4;
NSDictionary *options = @{
(id) kIOSurfaceWidth : @(width),
(id) kIOSurfaceHeight : @(height),
(id) kIOSurfacePixelFormat : @(pixelFormat),
(id) kIOSurfaceBytesPerElement : @(kBytesPerElement),
};
ioSurfaceOptions = reinterpret_cast<CFDictionaryRef>(options);
}
surface->ioSurface = IOSurfaceCreate(ioSurfaceOptions);
mFunctions->genTextures(1, &surface->texture);
mFunctions->bindTexture(GL_TEXTURE_RECTANGLE_ARB, surface->texture);
CGLError error =
CGLTexImageIOSurface2D(CGLGetCurrentContext(), GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, width,
height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface->ioSurface, 0);
if (error != kCGLNoError)
{
std::string errorMessage =
"Could not create the IOSurfaces: " + std::string(CGLErrorString(error));
return egl::Error(EGL_BAD_NATIVE_WINDOW, errorMessage.c_str());
}
return egl::Error(EGL_SUCCESS);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment