| /* |
| 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 |