1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qiosurfacegraphicsbuffer.h"
41
42#include <QtCore/qdebug.h>
43#include <QtCore/qloggingcategory.h>
44
45#include <CoreGraphics/CoreGraphics.h>
46#include <IOSurface/IOSurface.h>
47
48// CGColorSpaceCopyPropertyList is available on 10.12 and above,
49// but was only added in the 10.14 SDK, so declare it just in case.
50extern "C" CFPropertyListRef CGColorSpaceCopyPropertyList(CGColorSpaceRef space);
51
52QT_BEGIN_NAMESPACE
53
54Q_LOGGING_CATEGORY(lcQpaIOSurface, "qt.qpa.backingstore.iosurface");
55
56QIOSurfaceGraphicsBuffer::QIOSurfaceGraphicsBuffer(const QSize &size, const QPixelFormat &format)
57    : QPlatformGraphicsBuffer(size, format)
58{
59    const size_t width = size.width();
60    const size_t height = size.height();
61
62    Q_ASSERT(width <= IOSurfaceGetPropertyMaximum(kIOSurfaceWidth));
63    Q_ASSERT(height <= IOSurfaceGetPropertyMaximum(kIOSurfaceHeight));
64
65    static const char bytesPerElement = 4;
66
67    const size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement);
68    const size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow);
69
70    NSDictionary *options = @{
71        (id)kIOSurfaceWidth: @(width),
72        (id)kIOSurfaceHeight: @(height),
73        (id)kIOSurfacePixelFormat: @(unsigned('BGRA')),
74        (id)kIOSurfaceBytesPerElement: @(bytesPerElement),
75        (id)kIOSurfaceBytesPerRow: @(bytesPerRow),
76        (id)kIOSurfaceAllocSize: @(totalBytes),
77    };
78
79    m_surface = IOSurfaceCreate((CFDictionaryRef)options);
80    Q_ASSERT(m_surface);
81
82    Q_ASSERT(size_t(bytesPerLine()) == bytesPerRow);
83    Q_ASSERT(size_t(byteCount()) == totalBytes);
84}
85
86QIOSurfaceGraphicsBuffer::~QIOSurfaceGraphicsBuffer()
87{
88}
89
90void QIOSurfaceGraphicsBuffer::setColorSpace(QCFType<CGColorSpaceRef> colorSpace)
91{
92    static const auto kIOSurfaceColorSpace = CFSTR("IOSurfaceColorSpace");
93
94    qCDebug(lcQpaIOSurface) << "Tagging" << this << "with color space" << colorSpace;
95
96    if (colorSpace) {
97        IOSurfaceSetValue(m_surface, kIOSurfaceColorSpace,
98            QCFType<CFPropertyListRef>(CGColorSpaceCopyPropertyList(colorSpace)));
99    } else {
100        IOSurfaceRemoveValue(m_surface, kIOSurfaceColorSpace);
101    }
102}
103
104const uchar *QIOSurfaceGraphicsBuffer::data() const
105{
106    return (const uchar *)IOSurfaceGetBaseAddress(m_surface);
107}
108
109uchar *QIOSurfaceGraphicsBuffer::data()
110{
111    return (uchar *)IOSurfaceGetBaseAddress(m_surface);
112}
113
114int QIOSurfaceGraphicsBuffer::bytesPerLine() const
115{
116    return IOSurfaceGetBytesPerRow(m_surface);
117}
118
119IOSurfaceRef QIOSurfaceGraphicsBuffer::surface()
120{
121    return m_surface;
122}
123
124bool QIOSurfaceGraphicsBuffer::isInUse() const
125{
126    return IOSurfaceIsInUse(m_surface);
127}
128
129IOSurfaceLockOptions lockOptionsForAccess(QPlatformGraphicsBuffer::AccessTypes access)
130{
131    IOSurfaceLockOptions lockOptions = 0;
132    if (!(access & QPlatformGraphicsBuffer::SWWriteAccess))
133        lockOptions |= kIOSurfaceLockReadOnly;
134    return lockOptions;
135}
136
137bool QIOSurfaceGraphicsBuffer::doLock(AccessTypes access, const QRect &rect)
138{
139    Q_UNUSED(rect);
140    Q_ASSERT(!isLocked());
141
142    qCDebug(lcQpaIOSurface) << "Locking" << this << "for" << access;
143
144    // FIXME: Teach QPlatformBackingStore::composeAndFlush about non-2D texture
145    // targets, so that we can use CGLTexImageIOSurface2D to support TextureAccess.
146    if (access & (TextureAccess | HWCompositor))
147        return false;
148
149    auto lockOptions = lockOptionsForAccess(access);
150
151    // Try without read-back first
152    lockOptions |= kIOSurfaceLockAvoidSync;
153    kern_return_t ret = IOSurfaceLock(m_surface, lockOptions, nullptr);
154    if (ret == kIOSurfaceSuccess)
155        return true;
156
157    if (ret == kIOReturnCannotLock) {
158        qCWarning(lcQpaIOSurface) << "Locking of" << this << "requires read-back";
159        lockOptions ^= kIOSurfaceLockAvoidSync;
160        ret = IOSurfaceLock(m_surface, lockOptions, nullptr);
161    }
162
163    if (ret != kIOSurfaceSuccess) {
164        qCWarning(lcQpaIOSurface) << "Failed to lock" << this << ret;
165        return false;
166    }
167
168    return true;
169}
170
171void QIOSurfaceGraphicsBuffer::doUnlock()
172{
173    qCDebug(lcQpaIOSurface) << "Unlocking" << this << "from" << isLocked();
174
175    auto lockOptions = lockOptionsForAccess(isLocked());
176    bool success = IOSurfaceUnlock(m_surface, lockOptions, nullptr) == kIOSurfaceSuccess;
177    Q_ASSERT_X(success, "QIOSurfaceGraphicsBuffer", "Unlocking surface should succeed");
178}
179
180#ifndef QT_NO_DEBUG_STREAM
181QDebug operator<<(QDebug debug, const QIOSurfaceGraphicsBuffer *graphicsBuffer)
182{
183    QDebugStateSaver saver(debug);
184    debug.nospace();
185    debug << "QIOSurfaceGraphicsBuffer(" << (const void *)graphicsBuffer;
186    if (graphicsBuffer) {
187        debug << ", surface=" << graphicsBuffer->m_surface;
188        debug << ", size=" << graphicsBuffer->size();
189        debug << ", isLocked=" << bool(graphicsBuffer->isLocked());
190        debug << ", isInUse=" << graphicsBuffer->isInUse();
191    }
192    debug << ')';
193    return debug;
194}
195#endif // !QT_NO_DEBUG_STREAM
196
197QT_END_NAMESPACE
198