[system/karton] src: Support configuring 3d-acceleration of VMs in installer
Derek Lin <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit bfaf5d47b1685523b38d997fd77ebd333675908e by Derek Lin.
Committed on 31/07/2026 at 18:10.
Pushed by kenoi into branch 'master'.
Support configuring 3d-acceleration of VMs in installer
M +4 -0 src/domainconfig.cpp
M +8 -0 src/domainconfig.h
M +24 -17 src/domaininstaller.cpp
M +2 -2 src/domaininstaller.h
M +1 -0 src/karton.cpp
M +13 -6 src/primarysurfacerenderer.cpp
M +1 -0 src/primarysurfacerenderer.h
M +11 -1 src/qml/InstallationDialog.qml
https://invent.kde.org/system/karton/-/commit/bfaf5d47b1685523b38d997fd77ebd333675908e
diff --git a/src/domainconfig.cpp b/src/domainconfig.cpp
index 804cd81..471cd80 100644
--- a/src/domainconfig.cpp
+++ b/src/domainconfig.cpp
@@ -23,6 +23,7 @@ DomainConfig::DomainConfig(QObject *parent)
, m_virtualDiskPath(QString())
, m_screenshotPreviewPath(QString())
, m_autostart(false)
+ , m_enableAccel3d(true)
{
}
@@ -44,6 +45,7 @@ DomainConfig::DomainConfig(const DomainConfigData &data)
data.virtualDiskPath,
data.screenshotPreviewPath,
data.autostart,
+ data.enableAccel3d,
data.parent)
{
}
@@ -65,6 +67,7 @@ DomainConfig::DomainConfig(const QString &hypervisorType,
const QString &virtualDiskPath,
const QString &screenshotPreviewPath,
bool autostart,
+ bool enableAccel3d,
QObject *parent)
: QObject(parent)
, m_hypervisorType(hypervisorType)
@@ -84,6 +87,7 @@ DomainConfig::DomainConfig(const QString &hypervisorType,
, m_virtualDiskPath(virtualDiskPath)
, m_screenshotPreviewPath(screenshotPreviewPath)
, m_autostart(autostart)
+ , m_enableAccel3d(enableAccel3d)
{
}
diff --git a/src/domainconfig.h b/src/domainconfig.h
index 876703b..b7ab4c3 100644
--- a/src/domainconfig.h
+++ b/src/domainconfig.h
@@ -27,6 +27,7 @@ class DomainConfig : public QObject
Q_PROPERTY(QString virtualDiskPath READ virtualDiskPath CONSTANT)
Q_PROPERTY(QString screenshotPreviewPath READ screenshotPreviewPath CONSTANT)
Q_PROPERTY(bool autostart READ autostart NOTIFY autostartChanged)
+ Q_PROPERTY(bool enableAccel3d READ enableAccel3d CONSTANT)
Q_SIGNALS:
void isActiveChanged(bool active);
@@ -53,6 +54,7 @@ public:
QString virtualDiskPath;
QString screenshotPreviewPath;
bool autostart;
+ bool enableAccel3d = true;
QObject *parent = nullptr;
};
explicit DomainConfig(QObject *parent = nullptr);
@@ -74,6 +76,7 @@ public:
const QString &virtualDiskPath,
const QString &screenshotPreviewPath,
bool autostart,
+ bool enableAccel3d = true,
QObject *parent = nullptr);
// getters
@@ -145,6 +148,10 @@ public:
{
return m_autostart;
}
+ [[nodiscard]] bool enableAccel3d() const
+ {
+ return m_enableAccel3d;
+ }
void setActive(bool active);
void setState(const QString &state);
@@ -169,4 +176,5 @@ private:
QString m_virtualDiskPath;
QString m_screenshotPreviewPath;
bool m_autostart;
+ bool m_enableAccel3d;
};
\ No newline at end of file
diff --git a/src/domaininstaller.cpp b/src/domaininstaller.cpp
index abe669c..c4dd206 100644
--- a/src/domaininstaller.cpp
+++ b/src/domaininstaller.cpp
@@ -195,12 +195,13 @@ QString DomainInstaller::generateXML(virConnectPtr conn, const DomainConfig *con
{.type = QStringLiteral("user"), .mac = genMac(), .source = QString(), .linkState = true, .model = QStringLiteral("virtio")});
// devices->graphics element
+ const bool enableAccel3d = config->enableAccel3d();
addGraphicsDevices(document,
devices,
{.type = QStringLiteral("spice"),
.autoport = QStringLiteral("yes"),
- .listen = QStringLiteral("socket"),
- .glEnable = QStringLiteral("yes"),
+ .listen = enableAccel3d ? QStringLiteral("socket") : QStringLiteral("address"),
+ .glEnable = enableAccel3d,
.uuid = uuidString});
// devices->sound element
@@ -210,10 +211,9 @@ QString DomainInstaller::generateXML(virConnectPtr conn, const DomainConfig *con
addAudioDevices(document, devices, {.id = QStringLiteral("1"), .type = QStringLiteral("spice")});
// devices->video element
- addVideoDevices(
- document,
- devices,
- {.model = QStringLiteral("virtio"), .heads = QStringLiteral("1"), .primary = QStringLiteral("yes"), .enableAccel3d = QStringLiteral("yes")});
+ addVideoDevices(document,
+ devices,
+ {.model = QStringLiteral("virtio"), .heads = QStringLiteral("1"), .primary = QStringLiteral("yes"), .enableAccel3d = enableAccel3d});
addInputDevices(document, devices, {.type = QStringLiteral("tablet"), .bus = QStringLiteral("usb")});
@@ -292,18 +292,24 @@ void DomainInstaller::addGraphicsDevices(QDomDocument &document, QDomElement &pa
QDomElement graphics = document.createElement(QStringLiteral("graphics"));
parent.appendChild(graphics);
graphics.setAttribute(QStringLiteral("type"), config.type);
- // graphics.setAttribute(QStringLiteral("autoport"), config.autoport);
- graphics.setAttribute(QStringLiteral("socket"), QStringLiteral("/tmp/spice%1.sock").arg(config.uuid));
QMap<QString, QString> listen;
listen[QStringLiteral("type")] = config.listen;
- listen[QStringLiteral("socket")] = QStringLiteral("/tmp/spice%1.sock").arg(config.uuid);
-
- QMap<QString, QString> gl;
- gl[QStringLiteral("enable")] = config.glEnable;
+ if (config.listen == QStringLiteral("socket")) {
+ const QString socketPath = QStringLiteral("/tmp/spice%1.sock").arg(config.uuid);
+ graphics.setAttribute(QStringLiteral("socket"), socketPath);
+ listen[QStringLiteral("socket")] = socketPath;
+ } else {
+ graphics.setAttribute(QStringLiteral("autoport"), config.autoport);
+ }
addElementWithAttributes(document, graphics, QStringLiteral("listen"), QString(), listen);
- addElementWithAttributes(document, graphics, QStringLiteral("gl"), QString(), gl);
+
+ if (config.glEnable) {
+ QMap<QString, QString> gl;
+ gl[QStringLiteral("enable")] = QStringLiteral("yes");
+ addElementWithAttributes(document, graphics, QStringLiteral("gl"), QString(), gl);
+ }
}
void DomainInstaller::addSoundDevices(QDomDocument &document, QDomElement &parent, const SoundConfig &config)
@@ -336,10 +342,11 @@ void DomainInstaller::addVideoDevices(QDomDocument &document, QDomElement &paren
model.setAttribute(QStringLiteral("heads"), config.heads);
model.setAttribute(QStringLiteral("primary"), config.primary);
- QMap<QString, QString> acceleration;
- acceleration[QStringLiteral("accel3d")] = config.enableAccel3d;
-
- addElementWithAttributes(document, model, QStringLiteral("acceleration"), QString(), acceleration);
+ if (config.enableAccel3d) {
+ QMap<QString, QString> acceleration;
+ acceleration[QStringLiteral("accel3d")] = QStringLiteral("yes");
+ addElementWithAttributes(document, model, QStringLiteral("acceleration"), QString(), acceleration);
+ }
}
void DomainInstaller::addInputDevices(QDomDocument &document, QDomElement &parent, const InputConfig &config)
diff --git a/src/domaininstaller.h b/src/domaininstaller.h
index 4c29cf8..09a737d 100644
--- a/src/domaininstaller.h
+++ b/src/domaininstaller.h
@@ -44,7 +44,7 @@ private:
QString type;
QString autoport;
QString listen;
- QString glEnable;
+ bool glEnable = false;
QString uuid;
};
@@ -62,7 +62,7 @@ private:
QString model;
QString heads;
QString primary;
- QString enableAccel3d;
+ bool enableAccel3d = false;
};
struct InputConfig {
diff --git a/src/karton.cpp b/src/karton.cpp
index fb8b08a..d4dd048 100644
--- a/src/karton.cpp
+++ b/src/karton.cpp
@@ -353,6 +353,7 @@ bool Karton::createDomain(const QVariantMap &config)
.isoDiskPath = config.value(QStringLiteral("isoDiskPath")).toString(),
.virtualDiskPath = getVirtualDiskPath(config.value(QStringLiteral("name")).toString()),
.autostart = false,
+ .enableAccel3d = config.value(QStringLiteral("enableAccel3d"), true).toBool(),
.parent = this};
auto domainConfig = std::make_unique<DomainConfig>(configData);
diff --git a/src/primarysurfacerenderer.cpp b/src/primarysurfacerenderer.cpp
index 05e226a..4ffc8cb 100644
--- a/src/primarysurfacerenderer.cpp
+++ b/src/primarysurfacerenderer.cpp
@@ -32,6 +32,7 @@ void PrimarySurfaceRenderer::detach()
m_channel = nullptr;
QMutexLocker locker(&m_frameLock);
+ m_frameBuffer = nullptr;
m_frame = QImage();
m_frameUpdated = false;
}
@@ -55,10 +56,11 @@ void PrimarySurfaceRenderer::display_primary_create_callback(SpiceChannel *chann
{
QMutexLocker locker(&self->m_frameLock);
- // TODO: map incoming SPICE format properly instead of assuming RGB32.
- self->m_frame = QImage(static_cast<uchar *>(imgdata), width, height, stride, QImage::Format_RGB32);
+ self->m_frameBuffer = static_cast<uchar *>(imgdata);
self->m_imageWidth = width;
self->m_imageHeight = height;
+ // TODO: map incoming SPICE format properly instead of assuming RGB32.
+ self->m_frame = QImage(self->m_frameBuffer, width, height, stride, QImage::Format_RGB32);
self->m_frameUpdated = true;
}
@@ -69,14 +71,19 @@ void PrimarySurfaceRenderer::display_primary_create_callback(SpiceChannel *chann
void PrimarySurfaceRenderer::display_invalidate_callback(SpiceDisplayChannel *channel, gint x, gint y, gint width, gint height, gpointer user_data)
{
Q_UNUSED(channel);
- Q_UNUSED(x);
- Q_UNUSED(y);
- Q_UNUSED(width);
- Q_UNUSED(height);
auto *self = static_cast<PrimarySurfaceRenderer *>(user_data);
{
QMutexLocker locker(&self->m_frameLock);
+ if (self->m_frameBuffer && !self->m_frame.isNull()) {
+ // Copy from spice-glib framebuffer to the QImage to render - inefficient
+ const auto *source = reinterpret_cast<const uint *>(self->m_frameBuffer);
+ for (int row = y; row < y + height; ++row) {
+ for (int col = x; col < x + width; ++col) {
+ self->m_frame.setPixel(col, row, source[self->m_imageWidth * row + col]);
+ }
+ }
+ }
self->m_frameUpdated = true;
}
diff --git a/src/primarysurfacerenderer.h b/src/primarysurfacerenderer.h
index e45cd86..1c1ba06 100644
--- a/src/primarysurfacerenderer.h
+++ b/src/primarysurfacerenderer.h
@@ -37,6 +37,7 @@ private:
int m_imageWidth = 0;
int m_imageHeight = 0;
+ uchar *m_frameBuffer = nullptr;
QImage m_frame;
QMutex m_frameLock;
bool m_frameUpdated = false;
diff --git a/src/qml/InstallationDialog.qml b/src/qml/InstallationDialog.qml
index 0802aba..a2218b2 100644
--- a/src/qml/InstallationDialog.qml
+++ b/src/qml/InstallationDialog.qml
@@ -41,7 +41,8 @@ Kirigami.Dialog {
isoDiskPath: diskImageField.text,
memoryGB: memorySpinBox.value,
storageGB: storageSpinBox.value,
- cpus: cpuSpinBox.value
+ cpus: cpuSpinBox.value,
+ enableAccel3d: enableAccel3dCheckBox.checked
};
Karton.createDomain(domainConfig);
showPassiveNotification(i18nc("%1 is the name of the virtual machine", "Created VM: %1", nameField.text));
@@ -215,6 +216,15 @@ Kirigami.Dialog {
value: 2
Layout.fillWidth: true
}
+
+ // FormCard.FormDelegateSeparator {}
+
+ FormCard.FormCheckDelegate {
+ id: enableAccel3dCheckBox
+ text: i18nc("@option:check", "Enable hardware acceleration")
+ checked: true
+ Layout.fillWidth: true
+ }
}
}
}
\ No newline at end of file