Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosplatformbackingstore.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "render/qohossurface.h"
7#include <native_buffer/native_buffer.h>
8#include <native_window/buffer_handle.h>
9#include <native_window/external_window.h>
10#include <qarkui/vsync.h>
11#include <qohosutils.h>
12#include <render/qohosview.h>
13#include <QtCore/private/qohoslogger_p.h>
14#include <QtCore/qendian.h>
15#include <algorithm>
16#include <cstdint>
17#include <cstring>
18#include <iterator>
19#include <tuple>
20
21QT_BEGIN_NAMESPACE
22
23namespace
24{
25
27
28std::uint64_t getNativeWindowBufferQueueSize(::OHNativeWindow *nativeWindow)
29{
30 std::int32_t bufferQueueSize;
31 auto getBufferQueueSizeResult = ::OH_NativeWindow_NativeWindowHandleOpt(
32 nativeWindow, ::NativeWindowOperation::GET_BUFFERQUEUE_SIZE, &bufferQueueSize);
33 if (Q_UNLIKELY(getBufferQueueSizeResult != ohNativeWindowErrorCodeSuccess)) {
34 qOhosReportFatalErrorAndAbort(
35 "%s: failed to get buffer queue size with error code: %d",
36 Q_FUNC_INFO, getBufferQueueSizeResult);
37 }
38 return bufferQueueSize;
39}
40
41std::size_t qImageBytesPerPixel(const QImage &image)
42{
43 const auto bitsPerPixel = image.depth();
44 const auto bytesPerPixel = bitsPerPixel / 8;
45 return bytesPerPixel;
46}
47
48QSpan<uchar> qImageScanLine(QImage &image, int y)
49{
50 return QSpan(image.scanLine(y), image.width() * qImageBytesPerPixel(image));
51}
52
53void copyImageRow(QSpan<const uchar> srcRow, QSpan<uchar> dstRow)
54{
55 const auto sizeToCopy = std::min(srcRow.size(), dstRow.size());
56 std::memcpy(dstRow.data(), srcRow.data(), sizeToCopy);
57}
58
59void copyImage(QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage)
60{
61 const auto heightToCopy = std::min(srcImage.size().height(), dstImage.height());
62 for (int i = 0; i < heightToCopy; ++i) {
63 auto srcRow = srcImage.constScanLine(i);
64 auto dstRow = qImageScanLine(dstImage, i);
65 copyImageRow(srcRow, dstRow);
66 }
67}
68
69void copyImage(QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage, const QRegion &region)
70{
71 const auto intersectedRegion =
72 region.intersected(QRect({}, srcImage.size())).intersected(QRect({}, dstImage.size()));
73
74 for (const auto &rect : intersectedRegion) {
75 const auto xSrc = rect.x() * srcImage.bytesPerPixel();
76 const auto widthSrc = rect.width() * srcImage.bytesPerPixel();
77 const auto xDst = rect.x() * qImageBytesPerPixel(dstImage);
78 const auto widthDst = rect.width() * qImageBytesPerPixel(dstImage);
79
80 for (int row = 0; row < rect.height(); ++row) {
81 const auto y = rect.y() + row;
82 const auto srcRow = srcImage.constScanLine(y).subspan(xSrc, widthSrc);
83 auto dstRow = qImageScanLine(dstImage, y).subspan(xDst, widthDst);
84 copyImageRow(srcRow, dstRow);
85 }
86 }
87}
88
89void debugDrawFlushedQRegion(QImage &dstImage, const QRegion &region)
90{
91 static const QColor debugBoxColor("#7F00FF00");
92
93 QPainter painter(&dstImage);
94 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
95 for (const QRect &rect : region)
96 painter.fillRect(rect, debugBoxColor);
97}
98
100 const QRegion &region, const QPoint &rootWindowOffset, const QSize &dstImageSize)
101{
102 std::vector<::Region::Rect> rects;
103 if (!region.isEmpty()) {
104 std::transform(
105 region.begin(), region.end(), std::back_inserter(rects),
106 [&](const auto &qrect) {
107 return ::Region::Rect{
108 .x = qrect.x() + rootWindowOffset.x(),
109 .y = (dstImageSize.height() - qrect.y()) + rootWindowOffset.y() - qrect.height(),
110 .w = static_cast<std::uint32_t>(qrect.width()),
111 .h = static_cast<std::uint32_t>(qrect.height()),
112 };
113 });
114 }
115 return rects;
116}
117
119 QtOhos::QThreadSafeRef<QWindow> qWindowRef, ::OHNativeWindow *nativeWindow,
120 QOhosConsumer<QWindow *> qtThreadFlushFunc)
121{
122 auto sharedQtThreadFlushFunc = QtOhos::moveToSharedPtr(std::move(qtThreadFlushFunc));
123 return QtOhos::evalInJsThread(
124 [&](QtOhos::JsState &) {
125 auto sharedFrameRequestFunc = QtOhos::makeProxyWithJsThreadDeleter(
126 QtOhos::moveToSharedPtr(
127 QArkUi::makeVSyncFrameRequester(
128 nativeWindow,
129 [qWindowRef, sharedQtThreadFlushFunc]() {
130 qWindowRef.visitInQtThreadIfAlive(
131 [sharedQtThreadFlushFunc](QWindow &qWindow) {
132 (*sharedQtThreadFlushFunc)(&qWindow);
133 });
134 })));
135
136 return [sharedFrameRequestFunc]() {
137 (*sharedFrameRequestFunc)();
138 };
139 });
140}
141
142}
143
144QOhosPlatformBackingStore::QOhosPlatformBackingStore(QWindow *window, const CreateInfo &createInfo)
145 : QRasterBackingStore(window)
146 , m_debugDrawFlushedRegion(createInfo.debugDrawFlushedRegion)
147 , m_vsyncEnabled(createInfo.enableVsync)
148 , m_windowContextManager(
149 createInfo.enableVsync,
150 [this](QWindow *qWindow) {
151 flushImmediate(qWindow);
152 })
153{
154}
155
156void QOhosPlatformBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
157{
158 if (m_reinitializeContextManager) {
159 m_windowContextManager = WindowContextManager(
160 m_vsyncEnabled,
161 [this](QWindow *qWindow) {
162 flushImmediate(qWindow);
163 });
164 m_reinitializeContextManager = false;
165 }
166
167 auto *platformWindow = QOhosPlatformWindow::fromQWindowOrNull(window);
168 auto *surface = platformWindow != nullptr ? platformWindow->ownedSurfaceOrNull() : nullptr;
169 auto bounds = region.translated(offset).boundingRect() & m_image.rect();
170
171 if (bounds.isEmpty())
172 return;
173
174 if (surface == nullptr) {
175 qOhosPrintfDebug("Window %p has no surface", window);
176 return;
177 }
178
179 m_windowContextManager
180 .getOrCreateWindowContext(window, surface->nativeWindow())
181 .flushData().updateDirtyRegionAndScheduleFlush(region, offset);
182}
183
184void QOhosPlatformBackingStore::resize(const QSize &size, const QRegion &staticContents)
185{
186 m_reinitializeContextManager = (size != m_requestedSize);
187 QRasterBackingStore::resize(size, staticContents);
188}
189
190QImage::Format QOhosPlatformBackingStore::format() const
191{
192 return QOhosSurface::mapNativeBufferFormatToQImageFormatOrFail(QOhosSurface::bufferFormat);
193}
194
195QOhosPlatformBackingStore::QImageView::QImageView(const QImage &srcImage, const QRect &subRect)
196 : m_srcImage(srcImage)
197 , m_subRect(subRect)
198{
199 auto imageRect = QRect(QPoint{}, m_srcImage.size());
200 if (!imageRect.contains(m_subRect)) {
201 qOhosReportFatalErrorAndAbort(
202 "image rect: (%d, %d, %d, %d) does not contain sub-rect: (%d, %d, %d, %d)",
203 imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
204 m_subRect.x(), m_subRect.y(), m_subRect.width(), m_subRect.height());
205 }
206
207 constexpr auto minAcceptedImageDepth = 8;
208 if (srcImage.depth() < minAcceptedImageDepth)
209 qOhosReportFatalErrorAndAbort("QImageView is not supported for <8bpp QImages");
210}
211
212std::size_t QOhosPlatformBackingStore::QImageView::bytesPerPixel() const
213{
214 return qImageBytesPerPixel(m_srcImage);
215}
216
217QSpan<const uchar> QOhosPlatformBackingStore::QImageView::constScanLine(int i) const
218{
219 Q_ASSERT(i >= 0 && i < m_subRect.height());
220 auto srcScanLine = QSpan(
221 m_srcImage.constScanLine(m_subRect.y() + i), m_srcImage.bytesPerLine());
222 return srcScanLine.subspan(
223 m_subRect.x() * bytesPerPixel(), bytesPerLine());
224}
225
226std::size_t QOhosPlatformBackingStore::QImageView::bytesPerLine() const
227{
228 return bytesPerPixel() * m_subRect.width();
229}
230
231QSize QOhosPlatformBackingStore::QImageView::size() const
232{
233 return m_subRect.size();
234}
235
236QOhosPlatformBackingStore::BufferRegionHandler::BufferRegionHandler(::OHNativeWindow *nativeWindow)
237 : m_bufferQueueSize(getNativeWindowBufferQueueSize(nativeWindow))
238{
239}
240
241QOhosOptional<QRegion> QOhosPlatformBackingStore::BufferRegionHandler::mergeRegionForBufferHandle(
242 ::BufferHandle *bufferHandle,
243 QRegion region) const
244{
245 const auto it = m_buffersToFlushSequenceIds.find(bufferHandle);
246 if (it == m_buffersToFlushSequenceIds.end())
247 return {};
248
249 std::uint64_t numberOfRegions = m_flushSequenceId - it->second;
250 if (numberOfRegions > m_bufferQueueSize)
251 return {};
252
253 for (auto it = m_lastFlushedRegions.cend() - numberOfRegions; it != m_lastFlushedRegions.cend(); ++it)
254 region = region.united(*it);
255
256 return makeQOhosOptional(region);
257}
258
259void QOhosPlatformBackingStore::BufferRegionHandler::storeRegionForBufferHandle(
260 ::BufferHandle *bufferHandle,
261 const QRegion &region)
262{
263 m_buffersToFlushSequenceIds[bufferHandle] = m_flushSequenceId;
264
265 if (m_buffersToFlushSequenceIds.size() > m_bufferQueueSize) {
266 m_buffersToFlushSequenceIds.clear();
267 m_lastFlushedRegions.clear();
268 m_flushSequenceId = 0;
269 return;
270 }
271
272 m_lastFlushedRegions.push_back(region);
273 if (m_lastFlushedRegions.size() > m_bufferQueueSize)
274 m_lastFlushedRegions.pop_front();
275
276 ++m_flushSequenceId;
277}
278
279QOhosPlatformBackingStore::FlushData::FlushData(std::function<void()> flushRequestFunc)
280 : m_flushRequestFunc(std::move(flushRequestFunc))
281{
282}
283
284std::pair<QRegion, QPoint> QOhosPlatformBackingStore::FlushData::fetchAndReset()
285{
286 return std::make_pair(
287 std::exchange(m_mergedRegionForFlush, QRegion {}),
288 std::exchange(m_lastWindowOffset, QPoint {}));
289}
290
291void QOhosPlatformBackingStore::FlushData::updateDirtyRegionAndScheduleFlush(
292 const QRegion &region, const QPoint &rootWindowOffset)
293{
294 m_mergedRegionForFlush = m_mergedRegionForFlush.united(region);
295 m_lastWindowOffset = rootWindowOffset;
296 m_flushRequestFunc();
297}
298
299QOhosPlatformBackingStore::WindowContext::WindowContext(
300 ::OHNativeWindow *nativeWindow, std::function<void()> flushRequestFunc)
301 : m_bufferRegionHandler(std::make_unique<BufferRegionHandler>(nativeWindow))
302 , m_flushData(std::move(flushRequestFunc))
303{
304}
305
306QOhosPlatformBackingStore::BufferRegionHandler &
307QOhosPlatformBackingStore::WindowContext::bufferRegionHandler()
308{
309 return *m_bufferRegionHandler;
310}
311
312QOhosPlatformBackingStore::FlushData &QOhosPlatformBackingStore::WindowContext::flushData()
313{
314 return m_flushData;
315}
316
317QOhosPlatformBackingStore::WindowContextManager::WindowContextManager(
318 bool vsyncEnabled,
319 std::function<void(QWindow *)> flushImmediateFunc)
320 : m_flushFunc(QtOhos::moveToSharedPtr(std::move(flushImmediateFunc)))
321 , m_vsyncEnabled(vsyncEnabled)
322{
323}
324
325QOhosPlatformBackingStore::WindowContext &
326QOhosPlatformBackingStore::WindowContextManager::getOrCreateWindowContext(
327 QWindow *window,
328 ::OHNativeWindow *nativeWindow)
329{
330 auto handlerIter = m_windowContexts.find(window);
331 if (handlerIter == m_windowContexts.end()) {
332
333 std::function<void()> flushFunc;
334
335 if (m_vsyncEnabled) {
336 auto weakQtFlushFunc = QtOhos::makeWeakPtr(m_flushFunc);
337 auto qWindowRef = QtOhos::makeQThreadSafeRef(window);
338 flushFunc = makeVSyncFrameRequestFunc(
339 qWindowRef, nativeWindow,
340 [weakQtFlushFunc](QWindow *qWindow) {
341 auto sharedQtThreadFlushFunc = weakQtFlushFunc.lock();
342 if (sharedQtThreadFlushFunc)
343 (*sharedQtThreadFlushFunc)(qWindow);
344 });
345 } else {
346 flushFunc = [window, flushFunc = m_flushFunc]() {
347 (*flushFunc)(window);
348 };
349 }
350
351 std::tie(handlerIter, std::ignore) = m_windowContexts.emplace(
352 window, std::make_unique<WindowContext>(
353 nativeWindow, std::move(flushFunc)));
354 }
355 return *(handlerIter->second);
356}
357
358bool QOhosPlatformBackingStore::scroll(const QRegion &area, int dx, int dy)
359{
360 Q_GUI_EXPORT void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
361
362 const qreal devicePixelRatio = m_image.devicePixelRatio();
363 const QPoint delta(
364 static_cast<int>(dx * devicePixelRatio),
365 static_cast<int>(dy * devicePixelRatio));
366
367 for (const QRect &rect : area) {
368 qt_scrollRectInImage(
369 m_image,
370 QRect(rect.topLeft() * devicePixelRatio, rect.size() * devicePixelRatio), delta);
371 }
372
373 return true;
374}
375
376void QOhosPlatformBackingStore::flushImmediate(QWindow *window)
377{
378 auto *platformWindow = QOhosPlatformWindow::fromQWindowOrNull(window);
379 auto *surface = platformWindow != nullptr ? platformWindow->ownedSurfaceOrNull() : nullptr;
380
381 if (surface == nullptr) {
382 qOhosPrintfDebug("Window %p has no surface", window);
383 return;
384 }
385
386 bool isRootWindow = window == this->window();
387
388 ::OHNativeWindow *nativeWindow = surface->nativeWindow();
389
390 auto &windowContext = m_windowContextManager.getOrCreateWindowContext(window, nativeWindow);
391 QRegion region;
392 QPoint offset;
393 std::tie(region, offset) = windowContext.flushData().fetchAndReset();
394
395 if (region.isEmpty())
396 return;
397
398 auto &bufferRegionHandler = m_windowContextManager
399 .getOrCreateWindowContext(window, nativeWindow).bufferRegionHandler();
400
401 auto srcImageRect = isRootWindow
402 ? QRect({}, m_image.size())
403 : QRect(offset, platformWindow->geometry().size()).intersected(QRect({}, m_image.size()));
404 if (srcImageRect.isEmpty()) {
405 qOhosPrintfDebug("Cannot get source image rect, ignore flush call");
406 return;
407 }
408
409 QImageView srcImage = QImageView(m_image, srcImageRect);
410
411 surface->paintOnNativeWindowSurface(
412 [&](QImage &dstImage, ::BufferHandle *bufferHandle) {
413 if (srcImage.bytesPerPixel() != qImageBytesPerPixel(dstImage)) {
414 qOhosReportFatalErrorAndAbort(
415 "%s: bytes per pixel in src and dst image mismatch. Image formats are not the same.", Q_FUNC_INFO);
416 }
417
418 const auto& mergedRegionOpt = bufferRegionHandler.mergeRegionForBufferHandle(bufferHandle, region);
419 if (mergedRegionOpt.hasValue())
420 copyImage(srcImage, dstImage, mergedRegionOpt.value());
421 else
422 copyImage(srcImage, dstImage);
423
424 if (m_debugDrawFlushedRegion)
425 debugDrawFlushedQRegion(dstImage, region);
426
427 return makeOhosRegionRectsForFlush(
428 mergedRegionOpt.valueOr(QRegion()), isRootWindow ? QPoint{} : offset, dstImage.size());
429 },
430 [&](::BufferHandle *bufferHandle) {
431 bufferRegionHandler.storeRegionForBufferHandle(bufferHandle, region);
432 });
433
434 if (isRootWindow) {
435 auto *view = platformWindow->ownedViewOrNull();
436 if (view != nullptr)
437 view->handleSurfaceContentsUpdated();
438 }
439}
440
441QT_END_NAMESPACE
constexpr std::int32_t ohNativeWindowErrorCodeSuccess
std::function< void()> makeVSyncFrameRequestFunc(QtOhos::QThreadSafeRef< QWindow > qWindowRef, ::OHNativeWindow *nativeWindow, QOhosConsumer< QWindow * > qtThreadFlushFunc)
void copyImageRow(QSpan< const uchar > srcRow, QSpan< uchar > dstRow)
std::vector<::Region::Rect > makeOhosRegionRectsForFlush(const QRegion &region, const QPoint &rootWindowOffset, const QSize &dstImageSize)
QSpan< uchar > qImageScanLine(QImage &image, int y)
void debugDrawFlushedQRegion(QImage &dstImage, const QRegion &region)
void copyImage(QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage)
std::size_t qImageBytesPerPixel(const QImage &image)
void copyImage(QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage, const QRegion &region)
std::uint64_t getNativeWindowBufferQueueSize(::OHNativeWindow *nativeWindow)
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)