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