Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion waylib/src/server/qtquick/wrenderhelper.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 JiDe Zhang <zhangjide@deepin.org>.
// Copyright (C) 2023-2026 JiDe Zhang <zhangjide@deepin.org>.
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "wrenderhelper.h"
Expand Down Expand Up @@ -846,6 +846,36 @@ bool WRenderHelper::makeTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *
return true;
}

bool WRenderHelper::makeTextureFromShm(qw_buffer *buffer, QSGPlainTexture *texture)
{
void *data = nullptr;
uint32_t format = 0;
size_t stride = 0;
if (!buffer->begin_data_ptr_access(WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride))
return false;

const QSize size(buffer->handle()->width, buffer->handle()->height);
const QImage::Format imageFormat = WTools::toImageFormat(format);
QImage image;
if (imageFormat != QImage::Format_Invalid && data && size.isValid()) {
// Deep-copy the shm pixels while the data pointer is accessible. The
// QSGPlainTexture uploads the QImage lazily during the render cycle, so
// the image must own its data instead of referencing the shm mapping
// (which is only safely accessible between begin/end_data_ptr_access).
image = QImage(static_cast<const uchar *>(data), size.width(), size.height(),
static_cast<int>(stride), imageFormat).copy();
Comment on lines +865 to +866

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Guard against potential stride truncation when casting from size_t to int.

Here stride comes from wlroots as size_t and is cast to int for the QImage constructor without validation. If stride ever exceeds INT_MAX, this could overflow and produce a malformed image or undefined behavior. Please guard the cast (e.g., stride <= std::numeric_limits<int>::max() with an early return) or convert to qsizetype, which matches Qt’s internal size type, before passing it to QImage.

Suggested implementation:

    const QSize size(buffer->handle()->width, buffer->handle()->height);
    const QImage::Format imageFormat = WTools::toImageFormat(format);
    QImage image;
    if (imageFormat != QImage::Format_Invalid && data && size.isValid()) {
        // Deep-copy the shm pixels while the data pointer is accessible. The
        // QSGPlainTexture uploads the QImage lazily during the render cycle, so
        // the image must own its data instead of referencing the shm mapping
        // (which is only safely accessible between begin/end_data_ptr_access).

        // Guard against stride exceeding the range of int expected by QImage.
        if (stride > static_cast<size_t>(std::numeric_limits<int>::max())) {
            buffer->end_data_ptr_access();
            return false;
        }

        const int bytesPerLine = static_cast<int>(stride);
        image = QImage(static_cast<const uchar *>(data),
                       size.width(),
                       size.height(),
                       bytesPerLine,
                       imageFormat)
                    .copy();
    }
    buffer->end_data_ptr_access();

If std::numeric_limits<int> is not already available in this file, add #include <limits> to the include list at the top of wrenderhelper.cpp.

}
buffer->end_data_ptr_access();

if (image.isNull())
return false;

texture->setImage(image);
texture->setTextureSize(size);
texture->setHasAlphaChannel(image.hasAlphaChannel());
return true;
}

WRenderHelper::TextureEntry
WRenderHelper::newTexture(qw_allocator *allocator, qw_renderer *renderer,
uint32_t drmFormat, uint64_t drmModifier,
Expand Down
3 changes: 2 additions & 1 deletion waylib/src/server/qtquick/wrenderhelper.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 JiDe Zhang <zhangjide@deepin.org>.
// Copyright (C) 2023-2026 JiDe Zhang <zhangjide@deepin.org>.
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#pragma once
Expand Down Expand Up @@ -56,6 +56,7 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject
static QSGRendererInterface::GraphicsApi probe(QW_NAMESPACE::qw_backend *testBackend, const QList<QSGRendererInterface::GraphicsApi> &apiList);

static bool makeTexture(QRhi *rhi, QW_NAMESPACE::qw_texture *handle, QSGPlainTexture *texture);
static bool makeTextureFromShm(QW_NAMESPACE::qw_buffer *buffer, QSGPlainTexture *texture);

struct TextureEntry {
wlr_buffer *buffer;
Expand Down
104 changes: 91 additions & 13 deletions waylib/src/server/qtquick/wsgtextureprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
#include <qwbuffer.h>
#include <qwrenderer.h>

#include <rhi/qrhi.h>

Check warning on line 14 in waylib/src/server/qtquick/wsgtextureprovider.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <rhi/qrhi.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <private/qsgplaintexture_p.h>

Check warning on line 15 in waylib/src/server/qtquick/wsgtextureprovider.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <private/qsgplaintexture_p.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <memory>

Check warning on line 16 in waylib/src/server/qtquick/wsgtextureprovider.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <memory> not found. Please note: Cppcheck does not need standard library headers to get proper results.

WAYLIB_SERVER_BEGIN_NAMESPACE

Expand All @@ -35,25 +36,38 @@
}

void cleanTexture() {
class TextureCleanupJob : public QRunnable
{
public:
TextureCleanupJob(QRhiTexture *texture)
: texture(texture) { }
void run() override {
texture->deleteLater();
}
QRhiTexture *texture;
};

if (rhiTexture) {
Q_ASSERT(window);
class TextureCleanupJob : public QRunnable
{
public:
TextureCleanupJob(QRhiTexture *texture)
: texture(texture) { }
void run() override {
texture->deleteLater();
}
QRhiTexture *texture;
};

// Delay clean the qt rhi textures.
window->scheduleRenderJob(new TextureCleanupJob(rhiTexture),
QQuickWindow::AfterSynchronizingStage);
rhiTexture = nullptr;
}

if (shmTexture) {
if (QRhiTexture *tex = shmTexture->rhiTexture()) {
Q_ASSERT(window);
window->scheduleRenderJob(new TextureCleanupJob(tex),
QQuickWindow::AfterSynchronizingStage);
}
// Prevent QSGPlainTexture from deleting the texture in its
// destructor; the deferred job above owns the cleanup.
shmTexture->setOwnsTexture(false);
shmTexture.reset();
hasShmImage = false;
}

if (ownsTexture && texture)
delete texture;
texture = nullptr;
Expand Down Expand Up @@ -85,6 +99,19 @@
QSGPlainTexture qtTexture;
QRhiTexture *rhiTexture = nullptr;
bool smooth = true;

// For shm buffers in vulkan mode: a Qt-owned texture uploaded from shm
// pixels, instead of borrowing wlroots' device-local VkImage.
std::unique_ptr<QSGPlainTexture> shmTexture;
bool hasShmImage = false;

QSGPlainTexture *createPlainTexture() {
QSGPlainTexture *t = new QSGPlainTexture;
t->setOwnsTexture(true);
t->setFiltering(smooth ? QSGTexture::Linear : QSGTexture::Nearest);
t->setMipmapFiltering(smooth ? QSGTexture::Linear : QSGTexture::Nearest);
return t;
}
};

WSGTextureProvider::WSGTextureProvider(WOutputRenderWindow *window)
Expand All @@ -101,15 +128,58 @@

void WSGTextureProvider::setBuffer(qw_buffer *buffer)
{
W_D(WSGTextureProvider);

if (buffer == qwBuffer()) {
// The buffer object is not changed, but maybe the buffer's content is changed.
// So should emit textureChanged() signal too.
if (buffer)
if (buffer) {
#ifdef ENABLE_VULKAN_RENDER
// For the shm upload path, wlroots applies damage to its own texture
// (which we don't use). Re-read the shm pixels so the Qt-owned
// texture reflects the latest content.
//
// NOTE: This re-reads and re-uploads the entire buffer, not just
// the damaged region. Partial dirty-region optimization (via
// QRhiTextureUpdater or a custom QSGTexture) is deferred to a
// future iteration.
//
// makeTextureFromShm calls setImage() internally; QSGPlainTexture
// compares the new and old images via QImage::operator==, which
// does a memcmp on pixel data (not a pointer comparison), so
// unchanged content skips the GPU upload but is not zero-cost.
if (d->hasShmImage)
WRenderHelper::makeTextureFromShm(buffer, d->shmTexture.get());
#endif
Q_EMIT textureChanged();
}
return;
}

W_D(WSGTextureProvider);
#ifdef ENABLE_VULKAN_RENDER
// In vulkan mode, shm client buffers must be uploaded by Qt itself instead of
// borrowing wlroots' device-local VkImage. That VkImage shares the VkDevice
// with Qt but runs on a separate command stream without cross-stream sync,
// so Qt may sample it before the upload is complete. dmabuf buffers are
// unaffected (dma-buf sync provides cross-stream synchronization).
if (buffer && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan) {
wlr_shm_attributes shmAttrs;
if (buffer->get_shm(&shmAttrs)) {
std::unique_ptr<QSGPlainTexture> shmTex(d->createPlainTexture());
if (WRenderHelper::makeTextureFromShm(buffer, shmTex.get())) {
d->cleanTexture();
d->buffer = buffer;
d->shmTexture = std::move(shmTex);
d->hasShmImage = true;
Q_EMIT textureChanged();
return;
}
// Shm upload failed (e.g. unsupported pixel format): fall back
// to the wlroots texture path below.
}
}
#endif

d->cleanTexture();
d->buffer = buffer;

Expand Down Expand Up @@ -164,6 +234,8 @@
QSGTexture *WSGTextureProvider::texture() const
{
W_DC(WSGTextureProvider);
if (d->hasShmImage)
return d->shmTexture.get();
return d->texture ? const_cast<QSGPlainTexture*>(&d->qtTexture) : nullptr;
}

Expand Down Expand Up @@ -195,6 +267,12 @@
: QSGTexture::Nearest);
d->qtTexture.setMipmapFiltering(newSmooth ? QSGTexture::Linear
: QSGTexture::Nearest);
if (d->shmTexture) {
d->shmTexture->setFiltering(newSmooth ? QSGTexture::Linear
: QSGTexture::Nearest);
d->shmTexture->setMipmapFiltering(newSmooth ? QSGTexture::Linear
: QSGTexture::Nearest);
}

Q_EMIT smoothChanged();
}
Expand Down
Loading