Qt 6.7.0 (libraries downloaded from Qt) on x86_64 Ubuntu 22.04, X11, nvidia driver 550.78
I am using QQuickRenderControl to render a QML scene to an offscreen Vulkan texture (QRhiTexture). This works but QML’s WebEngineView{}
does not with the following 2 errors:
No native pixmap.
Compositor returned null texture
However, I do see output related to WebEngineView loading the webpage:
js: While parsing speculation rules: A rule contains an unknown key: "eagerness".
js: The resource https://fonts.googleapis.com/css?family=Titillium+Web:400,400i,600 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.
I am assuming WebEngineView is working, just not rendering.
My QML is the following:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtWebEngine
Rectangle {
color: "green"
anchors.fill: parent
WebEngineView {
anchors.fill: parent
url: "https://qt.io"
}
Text {
color: "white"
text: "some test string"
anchors.centerIn: parent
font.pointSize: 32
}
}
In my test program, a frame looks like this, where no browser is displayed:
minimal reproducible example
I tried to keep it small but due to the nature of QRhi the code is a bit verbose. Nonetheless, it is a minimal example that demonstrates the problem. It is based on Qt’s own example rendercontrol_rhi and:
- Sets up a QVulkanInstance
- Loads a QML file
- Set render target to a texture
- Every second call
render()
and write frame PNG’s to disk
demo.qml (see above)
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(rhi_web LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets QuickWidgets Gui Quick WebEngineQuick)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
qt_add_executable(rhi_web main.cpp)
target_link_libraries(rhi_web PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::GuiPrivate
Qt6::Widgets
Qt6::QuickWidgets
Qt6::WebEngineQuick
)
qt_add_qml_module(rhi_web
URI rhi
VERSION 1.0
QML_FILES
"demo.qml"
RESOURCE_PREFIX
"/"
)
set_target_properties(rhi_web PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
main.cpp
#include <QMainWindow>
#include <QApplication>
#include <QTimer>
#include <QQuickWindow>
#include <QQuickRenderControl>
#include <QQuickRenderTarget>
#include <QQuickGraphicsDevice>
#include <QQuickGraphicsConfiguration>
#include <QQuickItem>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QtWebEngineQuick/qtwebenginequickglobal.h>
#include <rhi/qrhi.h>
class RhiWeb : public QObject
{
Q_OBJECT
public:
RhiWeb(QObject *parent = nullptr);
void load();
void render();
private:
QVulkanInstance m_vulkanInstance;
std::unique_ptr<QQuickRenderControl> m_renderControl;
std::unique_ptr<QQuickWindow> m_scene;
std::unique_ptr<QQmlEngine> m_qmlEngine;
std::unique_ptr<QQmlComponent> m_qmlComponent;
std::unique_ptr<QRhiTexture> m_texture;
std::unique_ptr<QRhiRenderBuffer> m_ds;
std::unique_ptr<QRhiTextureRenderTarget> m_rt;
std::unique_ptr<QRhiRenderPassDescriptor> m_rpDesc;
qint64 m_frameCount = 0;
};
RhiWeb::RhiWeb(QObject *parent) : QObject(parent) {
m_vulkanInstance.setExtensions(QQuickGraphicsConfiguration::preferredInstanceExtensions());
if(!m_vulkanInstance.create()) {
throw std::runtime_error("Failed to create QVulkanInstance");
exit(1);
}
}
void RhiWeb::load() {
QString filename = ":/rhi/demo.qml";
m_renderControl.reset(new QQuickRenderControl);
m_scene.reset(new QQuickWindow(m_renderControl.get()));
QQuickGraphicsConfiguration config;
config.setTimestamps(true);
m_scene->setGraphicsConfiguration(config);
m_scene->setVulkanInstance(&m_vulkanInstance);
m_qmlEngine.reset(new QQmlEngine);
m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.get(), QUrl::fromLocalFile(filename)));
if (m_qmlComponent->isError()) {
for (const QQmlError &error : m_qmlComponent->errors())
qWarning() << error.url() << error.line() << error;
throw std::runtime_error(QString("Failed to load %1").arg(filename).toStdString());
}
QObject *rootObject = m_qmlComponent->create();
if (m_qmlComponent->isError()) {
for (const QQmlError &error : m_qmlComponent->errors())
qWarning() << error.url() << error.line() << error;
throw std::runtime_error("Failed to create component");
}
QQuickItem *rootItem = qobject_cast<QQuickItem *>(rootObject);
if (!rootItem) {
if (QQuickWindow *w = qobject_cast<QQuickWindow *>(rootObject))
delete w;
throw std::runtime_error("Root object is not a QQuickItem. If this is a scene with Window in it, note that such scenes are not supported.");
}
if (rootItem->size().width() < 16)
rootItem->setSize(QSizeF(800, 400));
m_scene->contentItem()->setSize(rootItem->size());
m_scene->setGeometry(0, 0, rootItem->width(), rootItem->height());
rootItem->setParentItem(m_scene->contentItem());
const bool initSuccess = m_renderControl->initialize();
if (!initSuccess)
throw std::runtime_error("QQuickRenderControl::initialize() failed");
const QSGRendererInterface::GraphicsApi api = m_scene->rendererInterface()->graphicsApi();
QRhi *rhi = m_renderControl->rhi();
if (!rhi)
throw std::runtime_error("No QRhi");
qDebug() << "device name" << rhi->driverInfo().deviceName;
const QSize pixelSize = rootItem->size().toSize();
m_texture.reset(rhi->newTexture(QRhiTexture::RGBA8, pixelSize, 1,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
if (!m_texture->create())
throw std::runtime_error("Cannot create texture object");
m_ds.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, 1));
if (!m_ds->create())
throw std::runtime_error("Cannot create depth-stencil buffer");
QRhiTextureRenderTargetDescription rtDesc(QRhiColorAttachment(m_texture.get()));
rtDesc.setDepthStencilBuffer(m_ds.get());
m_rt.reset(rhi->newTextureRenderTarget(rtDesc));
m_rpDesc.reset(m_rt->newCompatibleRenderPassDescriptor());
m_rt->setRenderPassDescriptor(m_rpDesc.get());
if (!m_rt->create())
throw std::runtime_error("Cannot create render target");
m_scene->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(m_rt.get()));
qDebug() << "RhiWeb::load() done";
}
void RhiWeb::render() {
QString filename = QString("frame_%1.png").arg(m_frameCount);
qDebug() << "rendering" << filename;
if (!m_renderControl)
return;
m_renderControl->polishItems();
m_renderControl->beginFrame();
m_renderControl->sync();
m_renderControl->render();
QRhi *rhi = m_renderControl->rhi();
QRhiReadbackResult readResult;
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture(m_texture.get(), &readResult);
m_renderControl->commandBuffer()->resourceUpdate(readbackBatch);
m_renderControl->endFrame();
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied);
QImage result;
if (rhi->isYUpInFramebuffer())
result = wrapperImage.mirrored();
else
result = wrapperImage.copy();
result.save(filename, "PNG");
m_frameCount += 1;
}
int main(int argc, char **argv) {
QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan);
QtWebEngineQuick::initialize();
QApplication app(argc, argv);
// setup vkinstance
auto *rhiweb = new RhiWeb();
// load QML, setup texture
rhiweb->load();
// render every second, write a file (frame_X.png)
auto *renderTimer = new QTimer();
renderTimer->setInterval(1000);
QObject::connect(renderTimer, &QTimer::timeout, [rhiweb] {
rhiweb->render();
});
renderTimer->start();
return app.exec();
}
#include "main.moc"
building
cmake -Bbuild .
make -Cbuild -j6
./build/bin/rhi_web
Or in my case, since I have a custom Qt location:
cmake -DCMAKE_PREFIX_PATH=/path/to/qt/6.7.0/gcc_64/ -B build .