1/*
2 Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB.  If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "GraphicsSurface.h"
22
23#if USE(GRAPHICS_SURFACE) && OS(DARWIN)
24#include "TextureMapperGL.h"
25#include <CFNumber.h>
26#include <CGLContext.h>
27#include <CGLCurrent.h>
28#include <CGLIOSurface.h>
29#include <IOSurface/IOSurface.h>
30#include <OpenGL/OpenGL.h>
31#include <OpenGL/gl.h>
32#include <mach/mach.h>
33
34#if PLATFORM(QT)
35#include <QGuiApplication>
36#include <QOpenGLContext>
37#include <qpa/qplatformnativeinterface.h>
38#endif
39
40namespace WebCore {
41
42static uint32_t createTexture(IOSurfaceRef handle)
43{
44    GLuint texture = 0;
45    GLuint format = GL_BGRA;
46    GLuint type = GL_UNSIGNED_INT_8_8_8_8_REV;
47    GLuint internalFormat = GL_RGBA;
48    CGLContextObj context = CGLGetCurrentContext();
49    if (!context)
50        return 0;
51
52    GLint prevTexture;
53    glGetIntegerv(GL_TEXTURE_RECTANGLE_ARB, &prevTexture);
54
55    glGenTextures(1, &texture);
56    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);
57    CGLError error = CGLTexImageIOSurface2D(context, GL_TEXTURE_RECTANGLE_ARB, internalFormat, IOSurfaceGetWidth(handle), IOSurfaceGetHeight(handle), format, type, handle, 0);
58    if (error) {
59        glDeleteTextures(1, &texture);
60        texture = 0;
61    }
62
63    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, prevTexture);
64
65    return texture;
66}
67
68struct GraphicsSurfacePrivate {
69public:
70    GraphicsSurfacePrivate(const GraphicsSurfaceToken& token, const IntSize& size)
71        : m_context(0)
72        , m_size(size)
73        , m_token(token)
74        , m_frontBufferTexture(0)
75        , m_frontBufferReadTexture(0)
76        , m_backBufferTexture(0)
77        , m_backBufferReadTexture(0)
78        , m_readFbo(0)
79        , m_drawFbo(0)
80    {
81        m_frontBuffer = IOSurfaceLookupFromMachPort(m_token.frontBufferHandle);
82        m_backBuffer = IOSurfaceLookupFromMachPort(m_token.backBufferHandle);
83    }
84
85    GraphicsSurfacePrivate(const PlatformGraphicsContext3D shareContext, const IntSize& size, GraphicsSurface::Flags flags)
86        : m_context(0)
87        , m_size(size)
88        , m_frontBufferTexture(0)
89        , m_frontBufferReadTexture(0)
90        , m_backBufferTexture(0)
91        , m_backBufferReadTexture(0)
92        , m_readFbo(0)
93        , m_drawFbo(0)
94    {
95#if PLATFORM(QT)
96        QPlatformNativeInterface* nativeInterface = QGuiApplication::platformNativeInterface();
97        CGLContextObj shareContextObject = static_cast<CGLContextObj>(nativeInterface->nativeResourceForContext(QByteArrayLiteral("cglContextObj"), shareContext));
98        if (!shareContextObject)
99            return;
100
101        CGLPixelFormatObj pixelFormatObject = CGLGetPixelFormat(shareContextObject);
102        if (kCGLNoError != CGLCreateContext(pixelFormatObject, shareContextObject, &m_context))
103            return;
104
105        CGLRetainContext(m_context);
106#endif
107
108        unsigned pixelFormat = 'BGRA';
109        unsigned bytesPerElement = 4;
110        int width = m_size.width();
111        int height = m_size.height();
112
113        unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement);
114        if (!bytesPerRow)
115            return;
116
117        unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow);
118        if (!allocSize)
119            return;
120
121        const void *keys[6];
122        const void *values[6];
123        keys[0] = kIOSurfaceWidth;
124        values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
125        keys[1] = kIOSurfaceHeight;
126        values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
127        keys[2] = kIOSurfacePixelFormat;
128        values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
129        keys[3] = kIOSurfaceBytesPerElement;
130        values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
131        keys[4] = kIOSurfaceBytesPerRow;
132        values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
133        keys[5] = kIOSurfaceAllocSize;
134        values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
135
136        CFDictionaryRef dict = CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
137        for (unsigned i = 0; i < 6; i++)
138            CFRelease(values[i]);
139
140        m_frontBuffer = IOSurfaceCreate(dict);
141        m_backBuffer = IOSurfaceCreate(dict);
142
143        if (!(flags & GraphicsSurface::SupportsSharing))
144            return;
145
146        m_token = GraphicsSurfaceToken(IOSurfaceCreateMachPort(m_frontBuffer), IOSurfaceCreateMachPort(m_backBuffer));
147    }
148
149    ~GraphicsSurfacePrivate()
150    {
151        if (m_frontBufferTexture)
152            glDeleteTextures(1, &m_frontBufferTexture);
153
154        if (m_frontBufferReadTexture)
155            glDeleteTextures(1, &m_frontBufferReadTexture);
156
157        if (m_backBufferTexture)
158            glDeleteTextures(1, &m_backBufferTexture);
159
160        if (m_backBufferReadTexture)
161            glDeleteTextures(1, &m_backBufferReadTexture);
162
163        if (m_frontBuffer)
164            CFRelease(IOSurfaceRef(m_frontBuffer));
165
166        if (m_backBuffer)
167            CFRelease(IOSurfaceRef(m_backBuffer));
168
169        if (m_readFbo)
170            glDeleteFramebuffers(1, &m_readFbo);
171
172        if (m_drawFbo)
173            glDeleteFramebuffers(1, &m_drawFbo);
174
175        if (m_context)
176            CGLReleaseContext(m_context);
177
178        if (m_token.frontBufferHandle)
179            mach_port_deallocate(mach_task_self(), m_token.frontBufferHandle);
180        if (m_token.backBufferHandle)
181            mach_port_deallocate(mach_task_self(), m_token.backBufferHandle);
182
183    }
184
185    uint32_t swapBuffers()
186    {
187        std::swap(m_frontBuffer, m_backBuffer);
188        std::swap(m_frontBufferTexture, m_backBufferTexture);
189        std::swap(m_frontBufferReadTexture, m_backBufferReadTexture);
190
191        return IOSurfaceGetID(m_frontBuffer);
192    }
193
194    void makeCurrent()
195    {
196        m_detachedContext = CGLGetCurrentContext();
197
198        if (m_context)
199            CGLSetCurrentContext(m_context);
200    }
201
202    void doneCurrent()
203    {
204        CGLSetCurrentContext(m_detachedContext);
205        m_detachedContext = 0;
206    }
207
208    void copyFromTexture(uint32_t texture, const IntRect& sourceRect)
209    {
210        // FIXME: The following glFlush can possibly be replaced by using the GL_ARB_sync extension.
211        glFlush(); // Make sure the texture has actually been completely written in the original context.
212
213        makeCurrent();
214
215        int x = sourceRect.x();
216        int y = sourceRect.y();
217        int width = sourceRect.width();
218        int height = sourceRect.height();
219
220        glPushAttrib(GL_ALL_ATTRIB_BITS);
221        GLint previousFBO;
222        glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFBO);
223
224        if (!m_drawFbo)
225            glGenFramebuffers(1, &m_drawFbo);
226
227        if (!m_readFbo)
228            glGenFramebuffers(1, &m_readFbo);
229
230        glBindFramebuffer(GL_READ_FRAMEBUFFER, m_readFbo);
231        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
232
233        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFbo);
234        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, backBufferTextureID(), 0);
235        glBlitFramebuffer(x, y, width, height, x, x+height, y+width, y, GL_COLOR_BUFFER_BIT, GL_LINEAR); // Flip the texture upside down.
236
237        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0);
238        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
239        glBindFramebuffer(GL_FRAMEBUFFER, previousFBO);
240        glPopAttrib();
241
242        // Flushing the gl command buffer is necessary to ensure the texture has correctly been bound to the IOSurface.
243        glFlush();
244
245        swapBuffers();
246        doneCurrent();
247    }
248
249    GraphicsSurfaceToken token() const
250    {
251        return m_token;
252    }
253
254    uint32_t frontBufferTextureID()
255    {
256        if (!m_frontBufferReadTexture)
257            m_frontBufferReadTexture = createTexture(m_frontBuffer);
258
259        return m_frontBufferReadTexture;
260    }
261
262    uint32_t backBufferTextureID()
263    {
264        if (!m_backBufferTexture)
265            m_backBufferTexture = createTexture(m_backBuffer);
266
267        return m_backBufferTexture;
268    }
269
270    PlatformGraphicsSurface frontBuffer() const
271    {
272        return m_frontBuffer;
273    }
274
275    PlatformGraphicsSurface backBuffer() const
276    {
277        return m_backBuffer;
278    }
279
280    IntSize size() const
281    {
282        return m_size;
283    }
284
285private:
286    CGLContextObj m_context;
287    IntSize m_size;
288    CGLContextObj m_detachedContext;
289    PlatformGraphicsSurface m_frontBuffer;
290    PlatformGraphicsSurface m_backBuffer;
291    uint32_t m_frontBufferTexture;
292    uint32_t m_frontBufferReadTexture;
293    uint32_t m_backBufferTexture;
294    uint32_t m_backBufferReadTexture;
295    uint32_t m_readFbo;
296    uint32_t m_drawFbo;
297    GraphicsSurfaceToken m_token;
298};
299
300GraphicsSurfaceToken GraphicsSurface::platformExport()
301{
302    return m_private->token();
303}
304
305uint32_t GraphicsSurface::platformGetTextureID()
306{
307    return m_private->frontBufferTextureID();
308}
309
310void GraphicsSurface::platformCopyToGLTexture(uint32_t target, uint32_t id, const IntRect& targetRect, const IntPoint& offset)
311{
312    glPushAttrib(GL_ALL_ATTRIB_BITS);
313    if (!m_fbo)
314        glGenFramebuffers(1, &m_fbo);
315    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
316    glBindTexture(target, id);
317    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
318    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, m_private->frontBufferTextureID(), 0);
319    glCopyTexSubImage2D(target, 0, targetRect.x(), targetRect.y(), offset.x(), offset.y(), targetRect.width(), targetRect.height());
320    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0);
321    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
322    glPopAttrib();
323
324    // According to IOSurface's documentation, glBindFramebuffer is the one triggering an update of the surface's cache.
325    // If the web process starts rendering and unlocks the surface before this happens, we might copy contents
326    // of the currently rendering frame on our texture instead of the previously completed frame.
327    // Flush the command buffer to reduce the odds of this happening, this would not be necessary with double buffering.
328    glFlush();
329}
330
331void GraphicsSurface::platformCopyFromTexture(uint32_t texture, const IntRect& sourceRect)
332{
333    m_private->copyFromTexture(texture, sourceRect);
334}
335
336void GraphicsSurface::platformPaintToTextureMapper(TextureMapper* textureMapper, const FloatRect& targetRect, const TransformationMatrix& transform, float opacity)
337{
338    IntSize size = m_private->size();
339    FloatRect rectOnContents(FloatPoint::zero(), size);
340    TransformationMatrix adjustedTransform = transform;
341    adjustedTransform.multiply(TransformationMatrix::rectToRect(rectOnContents, targetRect));
342    static_cast<TextureMapperGL*>(textureMapper)->drawTexture(m_private->frontBufferTextureID(), TextureMapperGL::ShouldBlend | TextureMapperGL::ShouldUseARBTextureRect, size, rectOnContents, adjustedTransform, opacity);
343}
344
345uint32_t GraphicsSurface::platformFrontBuffer() const
346{
347    return IOSurfaceGetID(m_private->frontBuffer());
348}
349
350uint32_t GraphicsSurface::platformSwapBuffers()
351{
352    return m_private->swapBuffers();
353}
354
355IntSize GraphicsSurface::platformSize() const
356{
357    return m_private->size();
358}
359
360PassRefPtr<GraphicsSurface> GraphicsSurface::platformCreate(const IntSize& size, Flags flags, const PlatformGraphicsContext3D shareContext)
361{
362    // We currently disable support for CopyToTexture on Mac, because this is used for single buffered Tiles.
363    // The single buffered nature of this requires a call to glFlush, as described in platformCopyToTexture.
364    // This call blocks the GPU for about 40ms, which makes smooth animations impossible.
365    if (flags & SupportsCopyToTexture || flags & SupportsSingleBuffered)
366        return PassRefPtr<GraphicsSurface>();
367
368    RefPtr<GraphicsSurface> surface = adoptRef(new GraphicsSurface(size, flags));
369    surface->m_private = new GraphicsSurfacePrivate(shareContext, size, flags);
370
371    if (!surface->m_private->frontBuffer() || !surface->m_private->backBuffer())
372        return PassRefPtr<GraphicsSurface>();
373
374    return surface;
375}
376
377PassRefPtr<GraphicsSurface> GraphicsSurface::platformImport(const IntSize& size, Flags flags, const GraphicsSurfaceToken& token)
378{
379    // We currently disable support for CopyToTexture on Mac, because this is used for single buffered Tiles.
380    // The single buffered nature of this requires a call to glFlush, as described in platformCopyToTexture.
381    // This call blocks the GPU for about 40ms, which makes smooth animations impossible.
382    if (flags & SupportsCopyToTexture || flags & SupportsSingleBuffered)
383        return PassRefPtr<GraphicsSurface>();
384
385    RefPtr<GraphicsSurface> surface = adoptRef(new GraphicsSurface(size, flags));
386    surface->m_private = new GraphicsSurfacePrivate(token, size);
387
388    if (!surface->m_private->frontBuffer() || !surface->m_private->backBuffer())
389        return PassRefPtr<GraphicsSurface>();
390
391    return surface;
392}
393
394static int ioSurfaceLockOptions(int lockOptions)
395{
396    int options = 0;
397    if (lockOptions & GraphicsSurface::ReadOnly)
398        options |= kIOSurfaceLockReadOnly;
399    if (!(lockOptions & GraphicsSurface::RetainPixels))
400        options |= kIOSurfaceLockAvoidSync;
401
402    return options;
403}
404
405char* GraphicsSurface::platformLock(const IntRect& rect, int* outputStride, LockOptions lockOptions)
406{
407    // Locking is only necessary for single buffered use.
408    // In this case we only have a front buffer, so we only lock this one.
409    m_lockOptions = lockOptions;
410    IOReturn status = IOSurfaceLock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
411    if (status == kIOReturnCannotLock) {
412        m_lockOptions |= RetainPixels;
413        IOSurfaceLock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
414    }
415
416    int stride = IOSurfaceGetBytesPerRow(m_private->frontBuffer());
417    if (outputStride)
418        *outputStride = stride;
419
420    char* base = static_cast<char*>(IOSurfaceGetBaseAddress(m_private->frontBuffer()));
421
422    return base + stride * rect.y() + rect.x() * 4;
423}
424
425void GraphicsSurface::platformUnlock()
426{
427    IOSurfaceUnlock(m_private->frontBuffer(), ioSurfaceLockOptions(m_lockOptions), 0);
428}
429
430void GraphicsSurface::platformDestroy()
431{
432    if (m_fbo)
433        glDeleteFramebuffers(1, &m_fbo);
434    if (m_private)
435        delete m_private;
436    m_private = 0;
437}
438
439}
440#endif
441