Qt Hosting¶
This example hosts a Datoviz Vulkan scene in live Qt Widgets.
Preview¶

Run And Adapt¶
Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.
| Route | Availability | Command or action |
|---|---|---|
| C++ | Canonical native source | DVZ_CMAKE_ARGS="-DDVZ_ENABLE_QT_BRIDGE=ON" just build, then ./build/examples/qt/qt_hosting |
| PyQt | Additional integration source; check optional dependencies | python3 -m examples.python.qt.hosted_pyqt |
| Browser | Native only | Qt and PyQt hosting use native toolkit windows and an optional bridge provider |
Use this example as capability or integration evidence, not as a minimal copy-paste template. Start from the nearest supported, copy-safe example and add this feature after verifying the linked API reference.
Source¶
/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/* qt_hosting - This example hosts a Datoviz Vulkan scene in live Qt Widgets.
*
* Qt owns the event loop and widgets. Datoviz creates the Vulkan instance and renders into a
* Qt-created VkSurfaceKHR through the hosted view contract.
*
* Build: just build
* Run: ./build/examples/qt/qt_hosting
* Smoke: ./build/examples/qt/qt_hosting --smoke-ms 1000
* Capture: ./build/examples/qt/qt_hosting --png
*/
#include "hosted_qt_adapter.h"
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QDir>
#include <QFont>
#include <QHBoxLayout>
#include <QImage>
#include <QLabel>
#include <QPixmap>
#include <QPushButton>
#include <QScreen>
#include <QSlider>
#include <QString>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <vector>
namespace
{
constexpr int EXAMPLE_WINDOW_WIDTH = 1280;
constexpr int EXAMPLE_WINDOW_HEIGHT = 720;
} // namespace
static const uint32_t POINT_COUNT = 96;
static QString _capture_path()
{
const QString directory = qEnvironmentVariable("DVZ_CAPTURE_DIR", QStringLiteral("."));
const QString basename =
qEnvironmentVariable("DVZ_CAPTURE_BASENAME", QStringLiteral("qt_hosting"));
return QDir(directory).filePath(basename + QStringLiteral(".png"));
}
static bool _capture_window(QWidget* widget)
{
QScreen* screen = widget->screen();
if (screen == nullptr)
return false;
const QPixmap pixmap = screen->grabWindow(widget->winId());
if (pixmap.isNull())
return false;
const QImage image = pixmap.toImage().scaled(
EXAMPLE_WINDOW_WIDTH, EXAMPLE_WINDOW_HEIGHT, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
return image.save(_capture_path(), "PNG");
}
struct SceneState
{
DvzScene* scene = nullptr;
DvzFigure* figure = nullptr;
DvzPanel* panel = nullptr;
DvzVisual* visual = nullptr;
vec3 positions[POINT_COUNT] = {};
DvzColor colors[POINT_COUNT] = {};
float sizes[POINT_COUNT] = {};
float point_size = 18.0f;
float phase = 0.0f;
};
static void _fill_scene_data(SceneState* state)
{
for (uint32_t i = 0; i < POINT_COUNT; i++)
{
const float t = (float)i / (float)POINT_COUNT;
const float angle = t * 6.2831853f * 3.0f;
const float radius = 0.18f + 0.62f * t;
state->positions[i][0] = radius * std::cos(angle);
state->positions[i][1] = radius * std::sin(angle);
state->positions[i][2] = 0.0f;
state->colors[i] = dvz_color_rgb(
(uint8_t)(80 + (i * 97) % 176), (uint8_t)(80 + (i * 53) % 176),
(uint8_t)(80 + (i * 29) % 176));
state->sizes[i] = state->point_size;
}
}
static int _apply_point_size(SceneState* state, float point_size)
{
state->point_size = point_size;
for (uint32_t i = 0; i < POINT_COUNT; i++)
state->sizes[i] = point_size;
return dvz_visual_set_data(state->visual, "diameter_px", state->sizes, POINT_COUNT);
}
static int _apply_palette(SceneState* state, int palette)
{
for (uint32_t i = 0; i < POINT_COUNT; i++)
{
const uint8_t a = (uint8_t)((i * 37) % 256);
const uint8_t b = (uint8_t)((i * 67) % 256);
if (palette == 0)
{
state->colors[i] = dvz_color_rgb(
(uint8_t)(40 + (a % 180)), (uint8_t)(90 + (b % 130)),
(uint8_t)(160 + ((a + b) % 80)));
}
else if (palette == 1)
{
state->colors[i] = dvz_color_rgb(
(uint8_t)(180 + (a % 70)), (uint8_t)(70 + (b % 120)),
(uint8_t)(80 + ((a + b) % 90)));
}
else
{
state->colors[i] = dvz_color_rgb(
(uint8_t)(70 + (b % 110)), (uint8_t)(170 + (a % 70)),
(uint8_t)(110 + ((a + b) % 120)));
}
}
return dvz_visual_set_data(state->visual, "color", state->colors, POINT_COUNT);
}
static int _apply_animation_step(SceneState* state)
{
state->phase += 0.035f;
for (uint32_t i = 0; i < POINT_COUNT; i++)
{
const float t = (float)i / (float)POINT_COUNT;
const float angle = t * 6.2831853f * 3.0f + state->phase;
const float wave = 0.08f * std::sin(state->phase * 2.0f + t * 18.0f);
const float radius = 0.18f + 0.62f * t + wave;
state->positions[i][0] = radius * std::cos(angle);
state->positions[i][1] = radius * std::sin(angle);
}
return dvz_visual_set_data(state->visual, "position", state->positions, POINT_COUNT);
}
static void _set_background(SceneState* state, int background)
{
if (background == 0)
dvz_panel_set_background_color(state->panel, dvz_color_from_unit(0.08f, 0.10f, 0.14f, 1.0f));
else if (background == 1)
dvz_panel_set_background_color(state->panel, dvz_color_from_unit(0.94f, 0.95f, 0.92f, 1.0f));
else
dvz_panel_set_background_color(state->panel, dvz_color_from_unit(0.03f, 0.11f, 0.10f, 1.0f));
}
static DvzScene* _make_scene(SceneState* state)
{
DvzScene* scene = dvz_scene();
if (scene == nullptr)
return nullptr;
DvzFigure* figure = dvz_figure(scene, EXAMPLE_WINDOW_WIDTH, EXAMPLE_WINDOW_HEIGHT, 0);
DvzPanelDesc panel_desc = {};
panel_desc.x = 0.0f;
panel_desc.y = 0.0f;
panel_desc.width = 1.0f;
panel_desc.height = 1.0f;
DvzPanel* panel = figure != nullptr ? dvz_panel(figure, &panel_desc) : nullptr;
DvzVisual* visual = panel != nullptr ? dvz_point(scene, 0) : nullptr;
if (figure == nullptr || panel == nullptr || visual == nullptr)
{
dvz_scene_destroy(scene);
return nullptr;
}
state->scene = scene;
state->figure = figure;
state->panel = panel;
state->visual = visual;
_fill_scene_data(state);
dvz_visual_set_data(visual, "position", state->positions, POINT_COUNT);
dvz_visual_set_data(visual, "color", state->colors, POINT_COUNT);
dvz_visual_set_data(visual, "diameter_px", state->sizes, POINT_COUNT);
dvz_panel_add_visual(panel, visual, nullptr);
_set_background(state, 0);
return scene;
}
static QWidget* _controls_widget(SceneState* state, DvzQtHostedWindow* view_window, QTimer* timer)
{
QWidget* controls = new QWidget();
controls->setMinimumWidth(260);
QVBoxLayout* layout = new QVBoxLayout(controls);
layout->setContentsMargins(12, 12, 12, 12);
layout->setSpacing(10);
QLabel* title = new QLabel(QStringLiteral("Scene controls"));
QFont title_font = title->font();
title_font.setBold(true);
title->setFont(title_font);
layout->addWidget(title);
QLabel* size_label = new QLabel(QStringLiteral("Point size"));
QSlider* size_slider = new QSlider(Qt::Horizontal);
size_slider->setRange(4, 48);
size_slider->setValue((int)state->point_size);
layout->addWidget(size_label);
layout->addWidget(size_slider);
QLabel* wheel_label = new QLabel(QStringLiteral("Wheel sensitivity 100%"));
QSlider* wheel_slider = new QSlider(Qt::Horizontal);
wheel_slider->setRange(10, 200);
wheel_slider->setValue(100);
layout->addWidget(wheel_label);
layout->addWidget(wheel_slider);
QLabel* palette_label = new QLabel(QStringLiteral("Palette"));
QComboBox* palette_combo = new QComboBox();
palette_combo->addItem(QStringLiteral("Cool"));
palette_combo->addItem(QStringLiteral("Warm"));
palette_combo->addItem(QStringLiteral("Green"));
layout->addWidget(palette_label);
layout->addWidget(palette_combo);
QLabel* background_label = new QLabel(QStringLiteral("Background"));
QComboBox* background_combo = new QComboBox();
background_combo->addItem(QStringLiteral("Dark"));
background_combo->addItem(QStringLiteral("Light"));
background_combo->addItem(QStringLiteral("Deep green"));
layout->addWidget(background_label);
layout->addWidget(background_combo);
QCheckBox* animate_check = new QCheckBox(QStringLiteral("Animate"));
QPushButton* reset_button = new QPushButton(QStringLiteral("Reset positions"));
layout->addWidget(animate_check);
layout->addWidget(reset_button);
layout->addStretch(1);
QObject::connect(size_slider, &QSlider::valueChanged, controls, [state, view_window](int value) {
(void)_apply_point_size(state, (float)value);
view_window->request_scene_frame();
});
QObject::connect(
wheel_slider, &QSlider::valueChanged, controls,
[view_window, wheel_label](int value) {
view_window->set_wheel_scale((float)value / 100.0f);
wheel_label->setText(QStringLiteral("Wheel sensitivity %1%").arg(value));
});
QObject::connect(
palette_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), controls,
[state, view_window](int index) {
(void)_apply_palette(state, index);
view_window->request_scene_frame();
});
QObject::connect(
background_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), controls,
[state, view_window](int index) {
_set_background(state, index);
view_window->request_scene_frame();
});
QObject::connect(animate_check, &QCheckBox::toggled, controls, [timer](bool checked) {
if (checked)
timer->start(16);
else
timer->stop();
});
QObject::connect(reset_button, &QPushButton::clicked, controls, [state, view_window]() {
state->phase = 0.0f;
_fill_scene_data(state);
(void)dvz_visual_set_data(state->visual, "position", state->positions, POINT_COUNT);
(void)dvz_visual_set_data(state->visual, "diameter_px", state->sizes, POINT_COUNT);
view_window->request_scene_frame();
});
QObject::connect(timer, &QTimer::timeout, controls, [state, view_window]() {
(void)_apply_animation_step(state);
view_window->request_scene_frame();
});
return controls;
}
int main(int argc, char** argv)
{
int smoke_ms = 0;
bool capture_png = false;
for (int i = 1; i < argc; i++)
{
const QString arg = QString::fromUtf8(argv[i]);
if (arg == QStringLiteral("--png"))
capture_png = true;
else if (arg == QStringLiteral("--smoke-ms") && i + 1 < argc)
smoke_ms = std::atoi(argv[i + 1]);
}
QApplication qt_app(argc, argv);
std::vector<const char*> extensions;
if (!dvz_qt_instance_extensions(qt_app, &extensions, "qt_hosting"))
return 0;
SceneState scene_state = {};
DvzScene* scene = _make_scene(&scene_state);
if (scene == nullptr)
{
std::fprintf(stderr, "qt_hosting: failed to create scene\n");
return 1;
}
DvzAppConfig app_cfg = dvz_app_config();
app_cfg.instance_extension_count = (uint32_t)extensions.size();
app_cfg.instance_extensions = extensions.data();
app_cfg.enable_canvas_extensions = true;
app_cfg.enable_glfw_extensions = false;
DvzApp* app = dvz_app_with_config(scene, &app_cfg);
if (app == nullptr)
{
std::fprintf(stderr, "qt_hosting: skipped, Datoviz GPU context creation failed\n");
dvz_scene_destroy(scene);
return 0;
}
VkInstance instance = dvz_app_vk_instance(app);
if (instance == VK_NULL_HANDLE)
{
std::fprintf(stderr, "qt_hosting: Datoviz returned no Vulkan instance\n");
dvz_app_destroy(app);
dvz_scene_destroy(scene);
return 1;
}
QVulkanInstance qt_instance;
qt_instance.setVkInstance(instance);
if (!qt_instance.create())
{
std::fprintf(
stderr, "qt_hosting: Qt failed to adopt the Datoviz Vulkan instance (%d)\n",
(int)qt_instance.errorCode());
dvz_app_destroy(app);
dvz_scene_destroy(scene);
return 0;
}
DvzQtHostedWindow* view_window = new DvzQtHostedWindow(
app, scene_state.figure, scene_state.panel, &qt_instance,
QStringLiteral("qt_hosting_view"),
QSize(EXAMPLE_WINDOW_WIDTH, EXAMPLE_WINDOW_HEIGHT));
QWidget* view_container = QWidget::createWindowContainer(view_window);
view_container->setMinimumSize(640, 480);
view_container->setFocusPolicy(Qt::StrongFocus);
QTimer timer;
QWidget* controls = _controls_widget(&scene_state, view_window, &timer);
QWidget main_widget;
main_widget.setWindowTitle(QStringLiteral("Datoviz hosted Qt Widgets"));
QHBoxLayout* root_layout = new QHBoxLayout(&main_widget);
root_layout->setContentsMargins(0, 0, 0, 0);
root_layout->setSpacing(0);
root_layout->addWidget(view_container, 1);
root_layout->addWidget(controls, 0);
main_widget.resize(EXAMPLE_WINDOW_WIDTH, EXAMPLE_WINDOW_HEIGHT);
main_widget.show();
bool capture_failed = false;
QObject::connect(&qt_app, &QCoreApplication::aboutToQuit, &qt_app, [&view_window]() {
view_window->release_surface();
});
if (smoke_ms > 0)
QTimer::singleShot(smoke_ms, &qt_app, &QCoreApplication::quit);
if (capture_png)
{
QTimer::singleShot(1000, &qt_app, [&main_widget, &capture_failed]() {
if (!_capture_window(&main_widget))
{
std::fprintf(stderr, "qt_hosting: failed to capture the Qt window\n");
capture_failed = true;
}
QCoreApplication::quit();
});
}
const int rc = qt_app.exec();
timer.stop();
view_window->release_surface();
qt_instance.destroy();
dvz_app_destroy(app);
dvz_scene_destroy(scene);
return capture_failed ? 1 : rc;
}
#!/usr/bin/env python3
"""Embed a Datoviz view in a normal PyQt6 Widgets application."""
from __future__ import annotations
import argparse
import ctypes
import random
import sys
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QMainWindow, QPushButton
from PyQt6.QtWidgets import QSlider, QVBoxLayout, QWidget
import datoviz.raw as dvz
from datoviz.qt import DatovizWidget
def _void_p(array: ctypes.Array) -> ctypes.c_void_p:
return ctypes.cast(array, ctypes.c_void_p)
class ExampleScene:
def __init__(self):
self.scene = dvz.dvz_scene()
if not self.scene:
raise RuntimeError('dvz_scene() failed')
self.figure = dvz.dvz_figure(self.scene, 800, 600, 0)
self.panel = dvz.dvz_panel_full(self.figure)
self.visual = dvz.dvz_point(self.scene, 0)
if not self.figure or not self.panel or not self.visual:
raise RuntimeError('Datoviz scene setup failed')
self.positions = (ctypes.c_float * 9)(
-0.55,
-0.45,
0.0,
+0.55,
-0.45,
0.0,
0.0,
+0.50,
0.0,
)
self.colors = (dvz.DvzColor * 3)(
dvz.DvzColor(255, 80, 80, 255),
dvz.DvzColor(80, 220, 120, 255),
dvz.DvzColor(90, 150, 255, 255),
)
self.diameters = (ctypes.c_float * 3)(24.0, 24.0, 24.0)
self._upload()
if dvz.dvz_panel_add_visual(self.panel, self.visual, None) != 0:
raise RuntimeError('dvz_panel_add_visual() failed')
dvz.dvz_panel_set_background_color(self.panel, dvz.DvzColor(13, 15, 20, 255))
controller = dvz.dvz_panzoom(self.scene, None)
if not controller:
raise RuntimeError('dvz_panzoom() failed')
if dvz.dvz_panel_bind_controller(
self.panel, controller, dvz.DvzDimMaskFlag.DVZ_DIM_MASK_XY
):
raise RuntimeError('dvz_panel_bind_controller() failed')
def destroy(self):
if self.scene:
dvz.dvz_scene_destroy(self.scene)
self.scene = None
def set_point_size(self, size: int):
for i in range(3):
self.diameters[i] = float(size)
if dvz.dvz_visual_set_data(self.visual, b'diameter_px', _void_p(self.diameters), 3) != 0:
raise RuntimeError('dvz_visual_set_data(diameter_px) failed')
def randomize_colors(self):
for i in range(3):
self.colors[i] = dvz.DvzColor(
random.randint(80, 255),
random.randint(80, 255),
random.randint(80, 255),
255,
)
if dvz.dvz_visual_set_data(self.visual, b'color', _void_p(self.colors), 3) != 0:
raise RuntimeError('dvz_visual_set_data(color) failed')
def _upload(self):
if dvz.dvz_visual_set_data(self.visual, b'position', _void_p(self.positions), 3) != 0:
raise RuntimeError('dvz_visual_set_data(position) failed')
if dvz.dvz_visual_set_data(self.visual, b'color', _void_p(self.colors), 3) != 0:
raise RuntimeError('dvz_visual_set_data(color) failed')
if dvz.dvz_visual_set_data(self.visual, b'diameter_px', _void_p(self.diameters), 3) != 0:
raise RuntimeError('dvz_visual_set_data(diameter_px) failed')
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Datoviz PyQt hosting')
self.example = ExampleScene()
self.datoviz = DatovizWidget(self.example.scene, self.example.figure, self)
point_size = QSlider(Qt.Orientation.Horizontal)
point_size.setRange(8, 64)
point_size.setValue(24)
point_size.valueChanged.connect(self._set_point_size)
randomize = QPushButton('Randomize colors')
randomize.clicked.connect(self._randomize_colors)
controls = QWidget()
controls_layout = QVBoxLayout(controls)
controls_layout.addWidget(QLabel('Point size'))
controls_layout.addWidget(point_size)
controls_layout.addWidget(randomize)
controls_layout.addStretch(1)
root = QWidget()
layout = QHBoxLayout(root)
layout.addWidget(self.datoviz, 1)
layout.addWidget(controls)
self.setCentralWidget(root)
self.resize(1000, 650)
def closeEvent(self, event):
self.datoviz.release()
self.example.destroy()
super().closeEvent(event)
def _set_point_size(self, value: int):
self.example.set_point_size(value)
self.datoviz.request_frame()
def _randomize_colors(self):
self.example.randomize_colors()
self.datoviz.request_frame()
def _parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'--smoke-ms',
type=int,
default=0,
help='quit automatically after this many milliseconds',
)
return parser.parse_known_args(argv[1:])
def main(argv: list[str] | None = None) -> int:
argv = sys.argv if argv is None else argv
args, qt_args = _parse_args(argv)
app = QApplication([argv[0], *qt_args])
window = MainWindow()
window.show()
if args.smoke_ms > 0:
QTimer.singleShot(args.smoke_ms, app.quit)
rc = int(app.exec())
window.close()
app.processEvents()
return rc
if __name__ == '__main__':
raise SystemExit(main())
Example details
- ID:
advanced_qt_hosting - Category:
advanced - Lane:
advanced - Status:
supported - Source:
examples/qt/qt_hosting.cpp - Approved adaptation starter:
no - PyQt source:
examples/python/qt/hosted_pyqt.py - Browser support: Native only
- Browser note: Qt and PyQt hosting use native toolkit windows and an optional bridge provider
- Browser capability tags:
native-view,external-surface,qt,optional-provider - Validation:
optional-native-smoke+screenshot
Tags
advanced, qt, pyqt, optional-provider, external-surface, host-integration
Data
| Field | Value |
|---|---|
kind |
synthetic |