blob: 8597eb4e274b987885dd9f45370026aaf48f3cca [file] [log] [blame]
/*
Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "GraphicsSurface.h"
#if USE(GRAPHICS_SURFACE) && OS(DARWIN)
#include "TextureMapperGL.h"
#include <CFNumber.h>
#include <CGLContext.h>
#include <CGLCurrent.h>
#include <CGLIOSurface.h>
#include <IOSurface/IOSurface.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <mach/mach.h>
#if PLATFORM(QT)
#include <QGuiApplication>
#include <QOpenGLContext>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace WebCore {
static uint32_t createTexture(IOSurfaceRef handle)
{
GLuint texture = 0;
GLuint format = GL_BGRA;
GLuint type = GL_UNSIGNED_INT_8_8_8_8_REV;
GLuint internalFormat = GL_RGBA;
CGLContextObj context = CGLGetCurrentContext();
if (!context)
return 0;
GLint prevTexture;
GLboolean wasEnabled = glIsEnabled(GL_TEXTURE_RECTANGLE_ARB);
glGetIntegerv(GL_TEXTURE_RECTANGLE_ARB, &prevTexture);
if (!wasEnabled)
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);
CGLError error = CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_ARB, internalFormat, IOSurfaceGetWidth(handle), IOSurfaceGetHeight(handle), format, type, handle, 0);
if (error) {
glDeleteTextures(1, &texture);
texture = 0;
}
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, prevTexture);
if (!wasEnabled)
glDisable(GL_TEXTURE_RECTANGLE_ARB);
return texture;
}
struct GraphicsSurfacePrivate {
public:
GraphicsSurfacePrivate(const GraphicsSurfaceToken& token)
: m_context(0)
, m_token(token)
, m_frontBufferTexture(0)
, m_backBufferTexture(0)
, m_readFbo(0)
, m_drawFbo(0)
{
m_frontBuffer = IOSurfaceLookupFromMachPort(m_token.frontBufferHandle);
m_backBuffer = IOSurfaceLookupFromMachPort(m_token.backBufferHandle);
}
GraphicsSurfacePrivate(const PlatformGraphicsContext3D shareContext, const IntSize& size, GraphicsSurface::Flags flags)
: m_context(0)
, m_frontBufferTexture(0)
, m_backBufferTexture(0)
, m_readFbo(0)
, m_drawFbo(0)
{
#if PLATFORM(QT)
QPlatformNativeInterface* nativeInterface = QGuiApplication::platformNativeInterface();
CGLContextObj shareContextObject = static_cast<CGLContextObj>(nativeInterface->nativeResourceForContext(QByteArrayLiteral("cglContextObj"), shareContext));
if (!shareContextObject)
return;
CGLPixelFormatObj pixelFormatObject = CGLGetPixelFormat(shareContextObject);
if (kCGLNoError != CGLCreateContext(pixelFormatObject, shareContextObject, &m_context))
return;
CGLRetainContext(m_context);
#endif
unsigned pixelFormat = 'BGRA';
unsigned bytesPerElement = 4;
int width = size.width();
int height = size.height();
unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement);
if (!bytesPerRow)
return;
unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow);
if (!allocSize)
return;
const void *keys[6];
const void *values[6];
keys[0] = kIOSurfaceWidth;
values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
keys[1] = kIOSurfaceHeight;
values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
keys[2] = kIOSurfacePixelFormat;
values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
keys[3] = kIOSurfaceBytesPerElement;
values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
keys[4] = kIOSurfaceBytesPerRow;
values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
keys[5] = kIOSurfaceAllocSize;
values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
CFDictionaryRef dict = CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
for (unsigned i = 0; i < 6; i++)
CFRelease(values[i]);
m_frontBuffer = IOSurfaceCreate(dict);
m_backBuffer = IOSurfaceCreate(dict);
if (!(flags & GraphicsSurface::SupportsSharing))
return;
m_token = GraphicsSurfaceToken(IOSurfaceCreateMachPort(m_frontBuffer), IOSurfaceCreateMachPort(m_backBuffer));
}
~GraphicsSurfacePrivate()
{
if (m_frontBufferTexture)
glDeleteTextures(1, &m_frontBufferTexture);
if (m_backBufferTexture)
glDeleteTextures(1, &m_backBufferTexture);
if (m_frontBuffer)
CFRelease(IOSurfaceRef(m_frontBuffer));
if (m_backBuffer)
CFRelease(IOSurfaceRef(m_backBuffer));
if (m_readFbo)
glDeleteFramebuffers(1, &m_readFbo);
if (m_drawFbo)
glDeleteFramebuffers(1, &m_drawFbo);
if (m_context)
CGLReleaseContext(m_context);
if (m_token.frontBufferHandle)
mach_port_deallocate(mach_task_self(), m_token.frontBufferHandle);
if (m_token.backBufferHandle)
mach_port_deallocate(mach_task_self(), m_token.backBufferHandle);
}
uint32_t swapBuffers()
{
std::swap(m_frontBuffer, m_backBuffer);
std::swap(m_frontBufferTexture, m_backBufferTexture);
return IOSurfaceGetID(m_frontBuffer);
}
void makeCurrent()
{
m_detachedContext = CGLGetCurrentContext();
if (m_context)
CGLSetCurrentContext(m_context);
}
void doneCurrent()
{
CGLSetCurrentContext(m_detachedContext);
m_detachedContext = 0;
}
void copyFromTexture(uint32_t texture, const IntRect& sourceRect)
{
// FIXME: The following glFlush can possibly be replaced by using the GL_ARB_sync extension.
glFlush(); // Make sure the texture has actually been completely written in the original context.
makeCurrent();
glEnable(GL_TEXTURE_RECTANGLE_ARB);
int x = sourceRect.x();
int y = sourceRect.y();
int width = sourceRect.width();
int height = sourceRect.height();
glPushAttrib(GL_ALL_ATTRIB_BITS);
GLint previousFBO;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFBO);
if (!m_drawFbo)
glGenFramebuffers(1, &m_drawFbo);
if (!m_readFbo)
glGenFramebuffers(1, &m_readFbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_readFbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, backBufferTextureID(), 0);
glBlitFramebuffer(x, y, width, height, x, x+height, y+width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); // Flip the texture upside down.
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glBindFramebuffer(GL_FRAMEBUFFER, previousFBO);
glPopAttrib();
// Flushing the gl command buffer is necessary to ensure the texture has correctly been bound to the IOSurface.
glFlush();
doneCurrent();
}
GraphicsSurfaceToken token() const
{
return m_token;
}
uint32_t frontBufferTextureID()
{
if (!m_frontBufferTexture)
m_frontBufferTexture = createTexture(m_frontBuffer);
return m_frontBufferTexture;
}
uint32_t backBufferTextureID()
{
if (!m_backBufferTexture)
m_backBufferTexture = createTexture(m_backBuffer);
return m_backBufferTexture;
}
PlatformGraphicsSurface frontBuffer() const
{
return m_frontBuffer;
}
PlatformGraphicsSurface backBuffer() const
{
return m_backBuffer;
}
private:
CGLContextObj m_context;
CGLContextObj m_detachedContext;
PlatformGraphicsSurface m_frontBuffer;
PlatformGraphicsSurface m_backBuffer;
uint32_t m_frontBufferTexture;
uint32_t m_backBufferTexture;
uint32_t m_readFbo;
uint32_t m_drawFbo;
GraphicsSurfaceToken m_token;
};
GraphicsSurfaceToken GraphicsSurface::platformExport()
{
return m_private->token();
}
uint32_t GraphicsSurface::platformGetTextureID()
{
return m_private->frontBufferTextureID();
}
void GraphicsSurface::platformCopyToGLTexture(uint32_t target, uint32_t id, const IntRect& targetRect, const IntPoint& offset)
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
if (!m_fbo)
glGenFramebuffers(1, &m_fbo);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glEnable(target);
glBindTexture(target, id);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, m_private->frontBufferTextureID(), 0);
glCopyTexSubImage2D(target, 0, targetRect.x(), targetRect.y(), offset.x(), offset.y(), targetRect.width(), targetRect.height());
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glPopAttrib();
// According to IOSurface's documentation, glBindFramebuffer is the one triggering an update of the surface's cache.
// If the web process starts rendering and unlocks the surface before this happens, we might copy contents
// of the currently rendering frame on our texture instead of the previously completed frame.
// Flush the command buffer to reduce the odds of this happening, this would not be necessary with double buffering.
glFlush();
}
void GraphicsSurface::platformCopyFromTexture(uint32_t texture, const IntRect& sourceRect)
{
m_private->copyFromTexture(texture, sourceRect);
}
void GraphicsSurface::platformPaintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& transform, float opacity, BitmapTexture* mask)
{
TransformationMatrix adjustedTransform = transform;
adjustedTransform.multiply(TransformationMatrix::rectToRect(FloatRect(FloatPoint::zero(), m_size), targetRect));
static_cast<TextureMapperGL*>(textureMapper)->drawTextureRectangleARB(m_private->frontBufferTextureID(), 0, m_size, targetRect, adjustedTransform, opacity, mask);
}
uint32_t GraphicsSurface::platformFrontBuffer() const
{
return IOSurfaceGetID(m_private->frontBuffer());
}
uint32_t GraphicsSurface::platformSwapBuffers()
{
return m_private->swapBuffers();
}
PassRefPtr<GraphicsSurface> GraphicsSurface::platformCreate(const IntSize& size, Flags flags, const PlatformGraphicsContext3D shareContext)
{
// We currently disable support for CopyToTexture on Mac, because this is used for single buffered Tiles.
// The single buffered nature of this requires a call to glFlush, as described in platformCopyToTexture.
// This call blocks the GPU for about 40ms, which makes smooth animations impossible.
if (flags & SupportsCopyToTexture || flags & SupportsSingleBuffered)
return PassRefPtr<GraphicsSurface>();
RefPtr<GraphicsSurface> surface = adoptRef(new GraphicsSurface(size, flags));
surface->m_private = new GraphicsSurfacePrivate(shareContext, size, flags);
if (!surface->m_private->frontBuffer() || !surface->m_private->backBuffer())
return PassRefPtr<GraphicsSurface>();
return surface;
}
PassRefPtr<GraphicsSurface> GraphicsSurface::platformImport(const IntSize& size, Flags flags, const GraphicsSurfaceToken& token)
{
// We currently disable support for CopyToTexture on Mac, because this is used for single buffered Tiles.
// The single buffered nature of this requires a call to glFlush, as described in platformCopyToTexture.
// This call blocks the GPU for about 40ms, which makes smooth animations impossible.
if (flags & SupportsCopyToTexture || flags & SupportsSingleBuffered)
return PassRefPtr<GraphicsSurface>();
RefPtr<GraphicsSurface> surface = adoptRef(new GraphicsSurface(size, flags));
surface->m_private = new GraphicsSurfacePrivate(token);
if (!surface->m_private->frontBuffer() || !surface->m_private->backBuffer())
return PassRefPtr<GraphicsSurface>();
return surface;
}
static int ioSurfaceLockOptions(int lockOptions)
{
int options = 0;
if (lockOptions & GraphicsSurface::ReadOnly)
options |= kIOSurfaceLockReadOnly;
if (!(lockOptions & GraphicsSurface::RetainPixels))
options |= kIOSurfaceLockAvoidSync;
return options;
}
char* GraphicsSurface::platformLock(const IntRect& rect, int* outputStride, LockOptions lockOptions)
{
// Locking is only necessary for single buffered use.
// In this case we only have a front buffer, so we only lock this one.
m_lockOptions = lockOptions;
IOReturn status = IOSurfaceLock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
if (status == kIOReturnCannotLock) {
m_lockOptions |= RetainPixels;
IOSurfaceLock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
}
int stride = IOSurfaceGetBytesPerRow(m_private->frontBuffer());
if (outputStride)
*outputStride = stride;
char* base = static_cast<char*>(IOSurfaceGetBaseAddress(m_private->frontBuffer()));
return base + stride * rect.y() + rect.x() * 4;
}
void GraphicsSurface::platformUnlock()
{
IOSurfaceUnlock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
}
void GraphicsSurface::platformDestroy()
{
if (m_fbo)
glDeleteFramebuffers(1, &m_fbo);
if (m_private)
delete m_private;
m_private = 0;
}
}
#endif