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
60 QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage, const QRegion &region, const QPoint &dstOffset)
61{
62 const auto intersectedRegion =
63 region.intersected(QRect({}, srcImage.size())).intersected(QRect({}, dstImage.size()));
64
65 for (const auto &rect : intersectedRegion) {
66 const auto xSrc = rect.x() * srcImage.bytesPerPixel();
67 const auto widthSrc = rect.width() * srcImage.bytesPerPixel();
68 const auto xDst = (rect.x() + dstOffset.x()) * qImageBytesPerPixel(dstImage);
69 const auto widthDst = rect.width() * qImageBytesPerPixel(dstImage);
70
71 for (int row = 0; row < rect.height(); ++row) {
72 const int srcY = rect.y() + row;
73 const int dstY = srcY + dstOffset.y();
74 const auto srcRow = srcImage.constScanLine(srcY).subspan(xSrc, widthSrc);
75 auto dstRow = qImageScanLine(dstImage, dstY).subspan(xDst, widthDst);
76 copyImageRow(srcRow, dstRow);
77 }
78 }
79}
80
81void debugDrawFlushedQRegion(QImage &dstImage, const QRegion &region)
82{
83 static const QColor debugBoxColor("#7F00FF00");
84
85 QPainter painter(&dstImage);
86 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
87 for (const QRect &rect : region)
88 painter.fillRect(rect, debugBoxColor);
89}
90
92 const QRegion &region, const QPoint &rootWindowOffset, const QSize &dstImageSize)
93{
94 std::vector<::Region::Rect> rects;
95 if (!region.isEmpty()) {
96 std::transform(
97 region.begin(), region.end(), std::back_inserter(rects),
98 [&](const auto &qrect) {
99 return ::Region::Rect{
100 .x = qrect.x() + rootWindowOffset.x(),
101 .y = (dstImageSize.height() - qrect.y()) + rootWindowOffset.y() - qrect.height(),
102 .w = static_cast<std::uint32_t>(qrect.width()),
103 .h = static_cast<std::uint32_t>(qrect.height()),
104 };
105 });
106 }
107 return rects;
108}
109
111 QtOhos::QThreadSafeRef<QWindow> qWindowRef, ::OHNativeWindow *nativeWindow,
112 QOhosConsumer<QWindow *> qtThreadFlushFunc)
113{
114 auto sharedQtThreadFlushFunc = QtOhos::moveToSharedPtr(std::move(qtThreadFlushFunc));
115 return QtOhos::evalInJsThread(
116 [&](QtOhos::JsState &) {
117 auto sharedFrameRequestFunc = QtOhos::makeProxyWithJsThreadDeleter(
118 QtOhos::moveToSharedPtr(
119 QArkUi::makeVSyncFrameRequester(
120 nativeWindow,
121 [qWindowRef, sharedQtThreadFlushFunc]() {
122 qWindowRef.visitInQtThreadIfAlive(
123 [sharedQtThreadFlushFunc](QWindow &qWindow) {
124 (*sharedQtThreadFlushFunc)(&qWindow);
125 });
126 })));
127
128 return [sharedFrameRequestFunc]() {
129 (*sharedFrameRequestFunc)();
130 };
131 },
132 Q_FUNC_INFO);
133}
134
135QPoint extractNegativeOffset(const QPoint &offset)
136{
137 return QPoint(
138 std::max(0, -offset.x()),
139 std::max(0, -offset.y()));
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_flushFunc(
149 std::make_shared<std::function<void(QWindow *)>>(
150 [this](QWindow *qWindow) {
151 flushImmediate(qWindow);
152 }))
153 , m_windowContextManager(createInfo.enableVsync, m_flushFunc)
154{
155}
156
157void QOhosPlatformBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
158{
159 if (m_reinitializeContextManager) {
160 m_windowContextManager = WindowContextManager(m_vsyncEnabled, m_flushFunc);
161 m_reinitializeContextManager = false;
162 }
163
164 auto *platformWindow = QOhosPlatformWindow::fromQWindowOrNull(window);
165 auto *surface = platformWindow != nullptr ? platformWindow->ownedSurfaceOrNull() : nullptr;
166 auto bounds = region.translated(offset).boundingRect() & m_image.rect();
167
168 if (bounds.isEmpty())
169 return;
170
171 if (surface == nullptr) {
172 qOhosPrintfDebug("Window %p has no surface", window);
173 return;
174 }
175
176 m_windowContextManager
177 .getOrCreateWindowContext(window, surface->nativeWindow())
178 .flushData().updateDirtyRegionAndScheduleFlush(region, offset);
179}
180
181void QOhosPlatformBackingStore::resize(const QSize &size, const QRegion &staticContents)
182{
183 m_reinitializeContextManager = (size != m_requestedSize);
184 QRasterBackingStore::resize(size, staticContents);
185}
186
187QImage::Format QOhosPlatformBackingStore::format() const
188{
189 return QOhosSurface::mapNativeBufferFormatToQImageFormatOrFail(QOhosSurface::bufferFormat);
190}
191
192QOhosPlatformBackingStore::QImageView::QImageView(const QImage &srcImage, const QRect &subRect)
193 : m_srcImage(srcImage)
194 , m_subRect(subRect)
195{
196 auto imageRect = QRect(QPoint{}, m_srcImage.size());
197 if (!imageRect.contains(m_subRect)) {
198 qOhosReportFatalErrorAndAbort(
199 "image rect: (%d, %d, %d, %d) does not contain sub-rect: (%d, %d, %d, %d)",
200 imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
201 m_subRect.x(), m_subRect.y(), m_subRect.width(), m_subRect.height());
202 }
203
204 constexpr auto minAcceptedImageDepth = 8;
205 if (srcImage.depth() < minAcceptedImageDepth)
206 qOhosReportFatalErrorAndAbort("QImageView is not supported for <8bpp QImages");
207}
208
209std::size_t QOhosPlatformBackingStore::QImageView::bytesPerPixel() const
210{
211 return qImageBytesPerPixel(m_srcImage);
212}
213
214QSpan<const uchar> QOhosPlatformBackingStore::QImageView::constScanLine(int i) const
215{
216 Q_ASSERT(i >= 0 && i < m_subRect.height());
217 auto srcScanLine = QSpan(
218 m_srcImage.constScanLine(m_subRect.y() + i), m_srcImage.bytesPerLine());
219 return srcScanLine.subspan(
220 m_subRect.x() * bytesPerPixel(), bytesPerLine());
221}
222
223std::size_t QOhosPlatformBackingStore::QImageView::bytesPerLine() const
224{
225 return bytesPerPixel() * m_subRect.width();
226}
227
228QSize QOhosPlatformBackingStore::QImageView::size() const
229{
230 return m_subRect.size();
231}
232
233QOhosPlatformBackingStore::BufferRegionHandler::BufferRegionHandler(::OHNativeWindow *nativeWindow)
234 : m_bufferQueueSize(getNativeWindowBufferQueueSize(nativeWindow))
235{
236}
237
238QOhosOptional<QRegion> QOhosPlatformBackingStore::BufferRegionHandler::mergeRegionForBufferHandle(
239 ::BufferHandle *bufferHandle,
240 QRegion region) const
241{
242 const auto it = m_buffersToFlushSequenceIds.find(bufferHandle);
243 if (it == m_buffersToFlushSequenceIds.end())
244 return {};
245
246 std::uint64_t numberOfRegions = m_flushSequenceId - it->second;
247 if (numberOfRegions > m_bufferQueueSize)
248 return {};
249
250 for (auto it = m_lastFlushedRegions.cend() - numberOfRegions; it != m_lastFlushedRegions.cend(); ++it)
251 region = region.united(*it);
252
253 return makeQOhosOptional(region);
254}
255
256void QOhosPlatformBackingStore::BufferRegionHandler::storeRegionForBufferHandle(
257 ::BufferHandle *bufferHandle,
258 const QRegion &region)
259{
260 m_buffersToFlushSequenceIds[bufferHandle] = m_flushSequenceId;
261
262 if (m_buffersToFlushSequenceIds.size() > m_bufferQueueSize) {
263 m_buffersToFlushSequenceIds.clear();
264 m_lastFlushedRegions.clear();
265 m_flushSequenceId = 0;
266 return;
267 }
268
269 m_lastFlushedRegions.push_back(region);
270 if (m_lastFlushedRegions.size() > m_bufferQueueSize)
271 m_lastFlushedRegions.pop_front();
272
273 ++m_flushSequenceId;
274}
275
276QOhosPlatformBackingStore::FlushData::FlushData(std::function<void()> flushRequestFunc)
277 : m_flushRequestFunc(std::move(flushRequestFunc))
278{
279}
280
281std::pair<QRegion, QPoint> QOhosPlatformBackingStore::FlushData::fetchAndReset()
282{
283 return std::make_pair(
284 std::exchange(m_mergedRegionForFlush, QRegion {}),
285 std::exchange(m_lastWindowOffset, QPoint {}));
286}
287
288void QOhosPlatformBackingStore::FlushData::updateDirtyRegionAndScheduleFlush(
289 const QRegion &region, const QPoint &rootWindowOffset)
290{
291 m_mergedRegionForFlush = m_mergedRegionForFlush.united(region);
292 m_lastWindowOffset = rootWindowOffset;
293 m_flushRequestFunc();
294}
295
296QOhosPlatformBackingStore::WindowContext::WindowContext(
297 ::OHNativeWindow *nativeWindow, std::function<void()> flushRequestFunc)
298 : m_bufferRegionHandler(std::make_unique<BufferRegionHandler>(nativeWindow))
299 , m_flushData(std::move(flushRequestFunc))
300{
301}
302
303QOhosPlatformBackingStore::BufferRegionHandler &
304QOhosPlatformBackingStore::WindowContext::bufferRegionHandler()
305{
306 return *m_bufferRegionHandler;
307}
308
309QOhosPlatformBackingStore::FlushData &QOhosPlatformBackingStore::WindowContext::flushData()
310{
311 return m_flushData;
312}
313
314QOhosPlatformBackingStore::WindowContextManager::WindowContextManager(
315 bool vsyncEnabled,
316 std::shared_ptr<std::function<void(QWindow *)>> flushImmediateFunc)
317 : m_flushFunc(flushImmediateFunc)
318 , m_vsyncEnabled(vsyncEnabled)
319{
320}
321
322QOhosPlatformBackingStore::WindowContext &
323QOhosPlatformBackingStore::WindowContextManager::getOrCreateWindowContext(
324 QWindow *window,
325 ::OHNativeWindow *nativeWindow)
326{
327 auto handlerIter = m_windowContexts.find(window);
328 if (handlerIter == m_windowContexts.end()) {
329
330 std::function<void()> flushFunc;
331
332 if (m_vsyncEnabled) {
333 auto weakQtFlushFunc = QtOhos::makeWeakPtr(m_flushFunc);
334 auto qWindowRef = QtOhos::makeQThreadSafeRef(window);
335 flushFunc = makeVSyncFrameRequestFunc(
336 qWindowRef, nativeWindow,
337 [weakQtFlushFunc](QWindow *qWindow) {
338 auto sharedQtThreadFlushFunc = weakQtFlushFunc.lock();
339 if (sharedQtThreadFlushFunc)
340 (*sharedQtThreadFlushFunc)(qWindow);
341 });
342 } else {
343 flushFunc = [window, flushFunc = m_flushFunc]() {
344 (*flushFunc)(window);
345 };
346 }
347
348 std::tie(handlerIter, std::ignore) = m_windowContexts.emplace(
349 window, std::make_unique<WindowContext>(
350 nativeWindow, std::move(flushFunc)));
351 }
352 return *(handlerIter->second);
353}
354
355bool QOhosPlatformBackingStore::scroll(const QRegion &area, int dx, int dy)
356{
357 Q_GUI_EXPORT void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset);
358
359 const qreal devicePixelRatio = m_image.devicePixelRatio();
360 const QPoint delta(
361 static_cast<int>(dx * devicePixelRatio),
362 static_cast<int>(dy * devicePixelRatio));
363
364 for (const QRect &rect : area) {
365 qt_scrollRectInImage(
366 m_image,
367 QRect(rect.topLeft() * devicePixelRatio, rect.size() * devicePixelRatio), delta);
368 }
369
370 return true;
371}
372
373void QOhosPlatformBackingStore::beginPaint(const QRegion &region)
374{
375 auto previousImageSize = m_image.size();
376 QRasterBackingStore::beginPaint(region);
377 bool imageWasResized = previousImageSize != m_image.size();
378 if (imageWasResized)
379 m_windowContextManager = WindowContextManager(m_vsyncEnabled, m_flushFunc);
380}
381
382void QOhosPlatformBackingStore::flushImmediate(QWindow *window)
383{
384 auto *platformWindow = QOhosPlatformWindow::fromQWindowOrNull(window);
385 auto *surface = platformWindow != nullptr ? platformWindow->ownedSurfaceOrNull() : nullptr;
386
387 if (surface == nullptr) {
388 qOhosPrintfDebug("Window %p has no surface", window);
389 return;
390 }
391
392 bool isRootWindow = window == this->window();
393
394 ::OHNativeWindow *nativeWindow = surface->nativeWindow();
395
396 auto &windowContext = m_windowContextManager.getOrCreateWindowContext(window, nativeWindow);
397 QRegion region;
398 QPoint rootWindowOffset;
399 std::tie(region, rootWindowOffset) = windowContext.flushData().fetchAndReset();
400
401 if (region.isEmpty())
402 return;
403
404 auto &bufferRegionHandler = m_windowContextManager
405 .getOrCreateWindowContext(window, nativeWindow).bufferRegionHandler();
406
407 auto srcImageRect = isRootWindow
408 ? QRect({}, m_image.size())
409 : QRect(rootWindowOffset, platformWindow->geometry().size()).intersected(QRect({}, m_image.size()));
410 if (srcImageRect.isEmpty()) {
411 qOhosPrintfDebug("Cannot get source image rect, ignore flush call");
412 return;
413 }
414
415 QImageView srcImage = QImageView(m_image, srcImageRect);
416
417 const auto rootWindowRegionToFlush = region
418 .translated(rootWindowOffset)
419 .intersected(srcImageRect)
420 .translated(-rootWindowOffset);
421
422 surface->paintOnNativeWindowSurface(
423 [&](QImage &dstImage, ::BufferHandle *bufferHandle) {
424 if (srcImage.bytesPerPixel() != qImageBytesPerPixel(dstImage)) {
425 qOhosReportFatalErrorAndAbort(
426 "%s: bytes per pixel in src and dst image mismatch. Image formats are not the same.", Q_FUNC_INFO);
427 }
428
429 const auto& mergedRegionOpt = bufferRegionHandler.mergeRegionForBufferHandle(bufferHandle, rootWindowRegionToFlush);
430 const auto dstImageOffset = extractNegativeOffset(rootWindowOffset);
431 const auto windowVisibleRegion = QRegion(QRect(dstImageOffset, srcImage.size()));
432 const auto requestedRegion = mergedRegionOpt.value_or(windowVisibleRegion);
433 const auto sourceRegionToFlush = requestedRegion.translated(-dstImageOffset);
434 copyImage(srcImage, dstImage, sourceRegionToFlush, dstImageOffset);
435
436 if (m_debugDrawFlushedRegion)
437 debugDrawFlushedQRegion(dstImage, rootWindowRegionToFlush);
438
439 return makeOhosRegionRectsForFlush(
440 mergedRegionOpt.value_or(QRegion()), isRootWindow ? QPoint{} : rootWindowOffset, dstImage.size());
441 },
442 [&](::BufferHandle *bufferHandle) {
443 bufferRegionHandler.storeRegionForBufferHandle(bufferHandle, rootWindowRegionToFlush);
444 });
445
446 if (isRootWindow) {
447 auto *view = platformWindow->ownedViewOrNull();
448 if (view != nullptr)
449 view->handleSurfaceContentsUpdated();
450 }
451}
452
453QT_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)
std::size_t qImageBytesPerPixel(const QImage &image)
QPoint extractNegativeOffset(const QPoint &offset)
void copyImage(QOhosPlatformBackingStore::QImageView srcImage, QImage &dstImage, const QRegion &region, const QPoint &dstOffset)
std::uint64_t getNativeWindowBufferQueueSize(::OHNativeWindow *nativeWindow)
QT_BEGIN_NAMESPACE void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &)