[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.