qt 6.6.0 clean

This commit is contained in:
kleuter
2023-11-01 22:23:55 +01:00
parent 7b5ada15e7
commit 5d8194efa7
1449 changed files with 134276 additions and 31391 deletions

View File

@ -15,9 +15,10 @@ add_subdirectory(gestures)
add_subdirectory(highdpi)
add_subdirectory(inputmethodhints)
add_subdirectory(keypadnavigation)
#add_subdirectory(lance) # special case qgl.h missing
#add_subdirectory(lance) # qgl.h missing
add_subdirectory(qcursor)
add_subdirectory(qdesktopservices)
add_subdirectory(qdnslookup)
add_subdirectory(qgraphicsitem)
add_subdirectory(qgraphicsitemgroup)
add_subdirectory(qgraphicslayout/flicker)
@ -29,11 +30,9 @@ add_subdirectory(qmimedatabase)
add_subdirectory(qnetconmonitor)
add_subdirectory(qnetworkaccessmanager/qget)
add_subdirectory(qnetworkinformation)
#special case begin
if (QT_FEATURE_openssl AND UNIX)
add_subdirectory(qnetworkreply)
endif()
#special case end
if(QT_FEATURE_permissions)
add_subdirectory(permissions)
endif()
@ -77,10 +76,10 @@ if(QT_FEATURE_openssl)
add_subdirectory(qssloptions)
endif()
if(QT_FEATURE_opengl)
# add_subdirectory(qopengltextureblitter) special case broken in dev
# add_subdirectory(qopengltextureblitter) # TODO: broken in dev
endif()
if(QT_FEATURE_egl AND QT_FEATURE_opengl)
# add_subdirectory(qopenglcontext) # special case broken in dev
# add_subdirectory(qopenglcontext) # TODO: broken in dev
endif()
if(QT_FEATURE_vulkan)
add_subdirectory(qvulkaninstance)

View File

@ -0,0 +1,30 @@
#!/bin/sh
# Usage: foreachzone command [args...]
#
# The command is run with eval, so can include embedded shell
# metacharacters such as | and ||. It is run in a sub-shell, so can
# change environment or cd to a different directory without
# complicating later runs of the same command.
#
# It is run repeatedly, with the TZ environment variable set to each
# timezone name in turn, excluding the copies of zoneinfo/ under its
# posix/ and right/ sub-dirs. Symbolic links are included (as long as
# they point to valid zone data).
#
# For example, in the top level of a build tree,
# foreachzone ninja tst_qdate_check
# will run all the QDate tests in every time zone (this may take some
# time).
DIR=/usr/share/zoneinfo
[ -d "$DIR" ] || DIR=/usr/lib/zoneinfo
find $DIR -type d \( -name posix -o -name right \) -prune -o \( -type f -o -type l \) -print \
| while read f
do
# To filter out symlinks in zoneinfo/ itself, uncomment this line:
# echo "$f" | grep -wq 'zoneinfo/.*/' || [ ! -h "$f" ] || continue
# To skip all symlinks, omit the -L here:
file -L "$f" | grep -wq 'timezone data .*, version' || continue
( export TZ=${f#*/zoneinfo/}; eval "$@" )
done

View File

@ -0,0 +1,19 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(embeddedwindows
SOURCES
main.cpp
LIBRARIES
Qt::Gui
)
if(QT_FEATURE_xcb)
target_link_libraries(embeddedwindows PRIVATE XCB::XCB)
endif()
if(APPLE)
enable_language(OBJCXX)
set_source_files_properties(main.cpp PROPERTIES LANGUAGE OBJCXX)
set_property(TARGET embeddedwindows PROPERTY PROPERTY MACOSX_BUNDLE TRUE)
endif()

View File

@ -0,0 +1,110 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtGui>
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_OS_WIN) || QT_CONFIG(xcb)
#include "../../shared/nativewindow.h"
#define HAVE_NATIVE_WINDOW
#endif
#include <QDebug>
class TestWindow : public QRasterWindow
{
public:
using QRasterWindow::QRasterWindow;
TestWindow(const QBrush &brush) : m_brush(brush) {}
protected:
void mousePressEvent(QMouseEvent *) override
{
m_pressed = true;
update();
}
void mouseReleaseEvent(QMouseEvent *) override
{
m_pressed = false;
update();
}
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setCompositionMode(QPainter::CompositionMode_Source);
if (!mask().isNull())
painter.setClipRegion(mask());
painter.fillRect(QRect(0, 0, width(), height()),
m_pressed ? QGradient(QGradient::JuicyPeach) : m_brush);
}
private:
QBrush m_brush = QGradient(QGradient::DustyGrass);
bool m_pressed = false;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
TestWindow window{QGradient(QGradient::WinterNeva)};
window.resize(500, 500);
TestWindow *opaqueChildWindow = new TestWindow;
opaqueChildWindow->setParent(&window);
opaqueChildWindow->setGeometry(50, 50, 100, 100);
opaqueChildWindow->showNormal();
TestWindow *maskedChildWindow = new TestWindow;
maskedChildWindow->setParent(&window);
maskedChildWindow->setGeometry(200, 50, 100, 100);
maskedChildWindow->setMask(QRegion(0, 0, 100, 100, QRegion::Ellipse));
maskedChildWindow->showNormal();
static const QColor transparentGreen = QColor(0, 255, 0, 20);
TestWindow *transparentChildWindow = new TestWindow(transparentGreen);
// The default surface format of a platform may not include
// an alpha, so set it explicitly.
QSurfaceFormat format = transparentChildWindow->format();
format.setAlphaBufferSize(8);
transparentChildWindow->setFormat(format);
// FIXME: Windows requires this, even for child windows
transparentChildWindow->setFlag(Qt::FramelessWindowHint);
transparentChildWindow->setParent(&window);
transparentChildWindow->setGeometry(350, 50, 100, 100);
transparentChildWindow->showNormal();
#if defined(HAVE_NATIVE_WINDOW)
NativeWindow nativeWindow;
if (QWindow *foreignWindow = QWindow::fromWinId(nativeWindow)) {
foreignWindow->setParent(&window);
foreignWindow->setGeometry(50, 200, 100, 100);
foreignWindow->showNormal();
}
NativeWindow maskedNativeWindow;
if (QWindow *foreignWindow = QWindow::fromWinId(maskedNativeWindow)) {
foreignWindow->setParent(&window);
foreignWindow->setGeometry(200, 200, 100, 100);
foreignWindow->setMask(QRegion(0, 0, 100, 100, QRegion::Ellipse));
foreignWindow->showNormal();
}
NativeWindow nativeParentWindow;
if (QWindow *foreignWindow = QWindow::fromWinId(nativeParentWindow)) {
foreignWindow->setParent(&window);
foreignWindow->setGeometry(50, 350, 100, 100);
foreignWindow->showNormal();
TestWindow *maskedChildWindowOfNativeWindow = new TestWindow;
maskedChildWindowOfNativeWindow->setParent(foreignWindow);
maskedChildWindowOfNativeWindow->setGeometry(25, 25, 50, 50);
maskedChildWindowOfNativeWindow->showNormal();
}
#endif
window.show();
return app.exec();
}

View File

@ -17,6 +17,7 @@ qt_standard_project_setup()
qt_add_executable(permissions
MANUAL_FINALIZATION
main.cpp
android/AndroidManifest.xml
)
set_target_properties(permissions PROPERTIES

View File

@ -32,7 +32,7 @@ public:
QVector3D(0,0,0),
QVector3D(0,1,0));
m_timer.setInterval(16);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(update()));
connect(&m_timer, &QTimer::timeout, this, qOverload<>(&PaintedWindow::update));
m_timer.start();
}

View File

@ -0,0 +1,38 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(wiggly LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/widgets/wiggly")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(wiggly
dialog.cpp dialog.h
main.cpp
wigglywidget.cpp wigglywidget.h
)
set_target_properties(wiggly PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(wiggly PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS wiggly
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -0,0 +1,27 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "dialog.h"
#include "wigglywidget.h"
#include <QLineEdit>
#include <QVBoxLayout>
//! [0]
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
WigglyWidget *wigglyWidget = new WigglyWidget;
QLineEdit *lineEdit = new QLineEdit;
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(wigglyWidget);
layout->addWidget(lineEdit);
connect(lineEdit, &QLineEdit::textChanged, wigglyWidget, &WigglyWidget::setText);
lineEdit->setText(u8"🖖 " + tr("Hello world!"));
setWindowTitle(tr("Wiggly"));
resize(360, 145);
}
//! [0]

View File

@ -0,0 +1,19 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
//! [0]
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = nullptr);
};
//! [0]
#endif

View File

@ -0,0 +1,16 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Dialog dialog;
dialog.show();
return app.exec();
}

View File

@ -0,0 +1,11 @@
QT += widgets
HEADERS = wigglywidget.h \
dialog.h
SOURCES = wigglywidget.cpp \
dialog.cpp \
main.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/widgets/wiggly
INSTALLS += target

View File

@ -0,0 +1,65 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "wigglywidget.h"
#include <QFontMetrics>
#include <QPainter>
#include <QTimerEvent>
//! [0]
WigglyWidget::WigglyWidget(QWidget *parent)
: QWidget(parent), step(0)
{
setBackgroundRole(QPalette::Midlight);
setAutoFillBackground(true);
QFont newFont = font();
newFont.setPointSize(newFont.pointSize() + 20);
setFont(newFont);
timer.start(60, this);
}
//! [0]
//! [1]
void WigglyWidget::paintEvent(QPaintEvent * /* event */)
//! [1] //! [2]
{
static constexpr int sineTable[16] = {
0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38
};
QFontMetrics metrics(font());
int x = (width() - metrics.horizontalAdvance(text)) / 2;
int y = (height() + metrics.ascent() - metrics.descent()) / 2;
QColor color;
//! [2]
//! [3]
QPainter painter(this);
//! [3] //! [4]
int offset = 0;
for (char32_t codePoint : text.toUcs4()) {
int index = (step + offset++) % 16;
color.setHsv((15 - index) * 16, 255, 191);
painter.setPen(color);
QString symbol = QString::fromUcs4(&codePoint, 1);
painter.drawText(x, y - ((sineTable[index] * metrics.height()) / 400), symbol);
x += metrics.horizontalAdvance(symbol);
}
}
//! [4]
//! [5]
void WigglyWidget::timerEvent(QTimerEvent *event)
//! [5] //! [6]
{
if (event->timerId() == timer.timerId()) {
++step;
update();
} else {
QWidget::timerEvent(event);
}
//! [6]
}

View File

@ -0,0 +1,32 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef WIGGLYWIDGET_H
#define WIGGLYWIDGET_H
#include <QBasicTimer>
#include <QWidget>
//! [0]
class WigglyWidget : public QWidget
{
Q_OBJECT
public:
WigglyWidget(QWidget *parent = nullptr);
public slots:
void setText(const QString &newText) { text = newText; }
protected:
void paintEvent(QPaintEvent *event) override;
void timerEvent(QTimerEvent *event) override;
private:
QBasicTimer timer;
QString text;
int step;
};
//! [0]
#endif

View File

@ -0,0 +1,17 @@
TEMPLATE = app
TARGET = fontfeatures
INCLUDEPATH += .
QT += widgets
# You can make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# Please consult the documentation of the deprecated API in order to know
# how to port your code away from it.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x060000 # disables all APIs deprecated in Qt 6.0.0 and earlier
# Input
HEADERS += mainwindow.h
FORMS += mainwindow.ui
SOURCES += main.cpp \
mainwindow.cpp \

View File

@ -0,0 +1,14 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@ -0,0 +1,225 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setup();
updateSampleText();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateSampleText()
{
QFont font = ui->fontComboBox->currentFont();
font.setPixelSize(54);
for (int i = 0; i < ui->lwFeatures->count(); ++i) {
QListWidgetItem *it = ui->lwFeatures->item(i);
if (it->checkState() != Qt::PartiallyChecked) {
QByteArray ba = it->text().toLatin1();
font.setFeature(ba, !!it->checkState());
}
}
ui->lSampleDisplay->setFont(font);
ui->lSampleDisplay->setText(ui->leSampleText->text());
}
void MainWindow::enableAll()
{
for (int i = 0; i < ui->lwFeatures->count(); ++i) {
QListWidgetItem *it = ui->lwFeatures->item(i);
it->setCheckState(Qt::Checked);
}
}
void MainWindow::disableAll()
{
for (int i = 0; i < ui->lwFeatures->count(); ++i) {
QListWidgetItem *it = ui->lwFeatures->item(i);
it->setCheckState(Qt::Unchecked);
}
}
void MainWindow::reset()
{
for (int i = 0; i < ui->lwFeatures->count(); ++i) {
QListWidgetItem *it = ui->lwFeatures->item(i);
it->setCheckState(Qt::PartiallyChecked);
}
}
void MainWindow::setup()
{
connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, &MainWindow::updateSampleText);
connect(ui->leSampleText, &QLineEdit::textChanged, this, &MainWindow::updateSampleText);
connect(ui->lwFeatures, &QListWidget::itemChanged, this, &MainWindow::updateSampleText);
connect(ui->pbEnableAll, &QPushButton::clicked, this, &MainWindow::enableAll);
connect(ui->pbDisableAll, &QPushButton::clicked, this, &MainWindow::disableAll);
connect(ui->pbReset, &QPushButton::clicked, this, &MainWindow::reset);
QList<QByteArray> featureList =
{
"aalt",
"abvf",
"abvm",
"abvs",
"afrc",
"akhn",
"blwf",
"blwm",
"blws",
"calt",
"case",
"ccmp",
"cfar",
"chws",
"cjct",
"clig",
"cpct",
"cpsp",
"cswh",
"curs",
"cv01",
"c2pc",
"c2sc",
"dist",
"dlig",
"dnom",
"dtls",
"expt",
"falt",
"fin2",
"fin3",
"fina",
"flac",
"frac",
"fwid",
"half",
"haln",
"halt",
"hist",
"hkna",
"hlig",
"hngl",
"hojo",
"hwid",
"init",
"isol",
"ital",
"jalt",
"jp78",
"jp83",
"jp90",
"jp04",
"kern",
"lfbd",
"liga",
"ljmo",
"lnum",
"locl",
"ltra",
"ltrm",
"mark",
"med2",
"medi",
"mgrk",
"mkmk",
"mset",
"nalt",
"nlck",
"nukt",
"numr",
"onum",
"opbd",
"ordn",
"ornm",
"palt",
"pcap",
"pkna",
"pnum",
"pref",
"pres",
"pstf",
"psts",
"pwid",
"qwid",
"rand",
"rclt",
"rkrf",
"rlig",
"rphf",
"rtbd",
"rtla",
"rtlm",
"ruby",
"rvrn",
"salt",
"sinf",
"size",
"smcp",
"smpl",
"ss01",
"ss02",
"ss03",
"ss04",
"ss05",
"ss06",
"ss07",
"ss08",
"ss09",
"ss10",
"ss11",
"ss12",
"ss13",
"ss14",
"ss15",
"ss16",
"ss17",
"ss18",
"ss19",
"ss20",
"ssty",
"stch",
"subs",
"sups",
"swsh",
"titl",
"tjmo",
"tnam",
"tnum",
"trad",
"twid",
"unic",
"valt",
"vatu",
"vchw",
"vert",
"vhal",
"vjmo",
"vkna",
"vkrn",
"vpal",
"vrt2",
"vrtr",
"zero"
};
for (auto it = featureList.constBegin(); it != featureList.constEnd(); ++it) {
QListWidgetItem *item = new QListWidgetItem(*it);
item->setFlags(Qt::ItemIsUserTristate | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState(Qt::PartiallyChecked);
ui->lwFeatures->addItem(item);
}
}

View File

@ -0,0 +1,32 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void updateSampleText();
void enableAll();
void disableAll();
void reset();
private:
Ui::MainWindow *ui;
void setup();
};
#endif // MAINWINDOW_H

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFontComboBox" name="fontComboBox"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Sample text:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leSampleText">
<property name="text">
<string>Foobar</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="pbEnableAll">
<property name="text">
<string>Enable all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbDisableAll">
<property name="text">
<string>Disable all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbReset">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="lwFeatures">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="flow">
<enum>QListView::TopToBottom</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lSampleDisplay">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>LABEL</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,17 @@
project(inputdevices)
cmake_minimum_required(VERSION 3.19)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_add_executable(inputdevices
main.cpp
inputdevicemodel.h inputdevicemodel.cpp
)
set_target_properties(inputdevices PROPERTIES
AUTOMOC TRUE
)
target_link_libraries(inputdevices PUBLIC
Qt::Widgets
)

View File

@ -0,0 +1,264 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "inputdevicemodel.h"
#include <QEvent>
#include <QInputDevice>
#include <QLoggingCategory>
#include <QMetaEnum>
#include <QPointingDevice>
Q_LOGGING_CATEGORY(lcIPDM, "qt.inputdevicemodel")
static QString enumToString(const QObject *obj, const char* enumName, int enumValue)
{
const auto *metaobj = obj->metaObject();
Q_ASSERT(metaobj);
const int enumIdx = metaobj->indexOfEnumerator(enumName);
if (enumIdx < 0)
return {};
Q_ASSERT(metaobj->enumerator(enumIdx).isValid());
const char *ret = metaobj->enumerator(enumIdx).valueToKey(enumValue);
if (!ret)
return {};
return QString::fromUtf8(ret);
}
static QString capabilitiesString(const QInputDevice *dev)
{
QStringList ret;
const auto caps = dev->capabilities();
if (caps.testFlag(QInputDevice::Capability::Position))
ret << InputDeviceModel::tr("pos");
if (caps.testFlag(QInputDevice::Capability::Area))
ret << InputDeviceModel::tr("area");
if (caps.testFlag(QInputDevice::Capability::Pressure))
ret << InputDeviceModel::tr("press");
if (caps.testFlag(QInputDevice::Capability::Velocity))
ret << InputDeviceModel::tr("vel");
if (caps.testFlag(QInputDevice::Capability::NormalizedPosition))
ret << InputDeviceModel::tr("norm");
if (caps.testFlag(QInputDevice::Capability::MouseEmulation))
ret << InputDeviceModel::tr("m-emu");
if (caps.testFlag(QInputDevice::Capability::Scroll))
ret << InputDeviceModel::tr("scroll");
if (caps.testFlag(QInputDevice::Capability::PixelScroll))
ret << InputDeviceModel::tr("pxscroll");
if (caps.testFlag(QInputDevice::Capability::Hover))
ret << InputDeviceModel::tr("hover");
if (caps.testFlag(QInputDevice::Capability::Rotation))
ret << InputDeviceModel::tr("rot");
if (caps.testFlag(QInputDevice::Capability::XTilt))
ret << InputDeviceModel::tr("xtilt");
if (caps.testFlag(QInputDevice::Capability::YTilt))
ret << InputDeviceModel::tr("ytilt");
if (caps.testFlag(QInputDevice::Capability::TangentialPressure))
ret << InputDeviceModel::tr("tan");
if (caps.testFlag(QInputDevice::Capability::ZPosition))
ret << InputDeviceModel::tr("z");
return ret.join(u'|');
}
/*!
Returns true only if the given \a device is a master:
that is, if its parent is \e not another QInputDevice.
*/
static const auto masterDevicePred = [](const QInputDevice *device)
{
return !qobject_cast<QInputDevice*>(device->parent());
};
/*!
Returns the master device at index \a i:
that is, the i'th of the devices that satisfy masterDevicePred().
*/
static const QInputDevice *masterDevice(int i)
{
const auto devices = QInputDevice::devices();
auto it = std::find_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
it += i;
return (it == devices.constEnd() ? nullptr : *it);
}
/*!
Returns the index of the master \a device: that is, the index within the
subset of QInputDevice::devices() that satisfy masterDevicePred().
*/
static const int masterDeviceIndex(const QInputDevice *device)
{
Q_ASSERT(masterDevicePred(device)); // assume dev is not a slave
const auto devices = QInputDevice::devices();
auto it = std::find_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
for (int i = 0; it != devices.constEnd(); ++i, ++it)
if (*it == device)
return i;
return -1;
}
InputDeviceModel::InputDeviceModel(QObject *parent)
: QAbstractItemModel{parent}
{
connect(this, &InputDeviceModel::deviceAdded, this, &InputDeviceModel::onDeviceAdded, Qt::QueuedConnection);
}
// invariant: always call createIndex(row, column, QInputDevice*) or else nullptr for the last argument
QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const
{
const QInputDevice *par = static_cast<QInputDevice *>(parent.internalPointer());
const QInputDevice *ret = par ? qobject_cast<const QInputDevice *>(par->children().at(row)) : masterDevice(row);
qCDebug(lcIPDM) << row << column << "under parent" << par << ":" << ret;
return createIndex(row, column, ret);
}
QModelIndex InputDeviceModel::parent(const QModelIndex &index) const
{
if (!index.internalPointer())
return {};
const QInputDevice *par = qobject_cast<const QInputDevice *>(
static_cast<QInputDevice *>(index.internalPointer())->parent());
if (par)
return createIndex(masterDeviceIndex(par), index.column(), par);
return {};
}
int InputDeviceModel::rowCount(const QModelIndex &parent) const
{
int ret = 0;
const QInputDevice *par = qobject_cast<const QInputDevice *>(static_cast<QObject *>(parent.internalPointer()));
if (par) {
ret = par->children().count();
} else {
const auto devices = QInputDevice::devices();
ret = std::count_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
}
qCDebug(lcIPDM) << ret << "under parent" << parent << par;
return ret;
}
int InputDeviceModel::columnCount(const QModelIndex &) const
{
return NRoles;
}
QVariant InputDeviceModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section + Name) {
case Name:
return tr("Device Name");
case DeviceType:
return tr("Device Type");
case PointerType:
return tr("Pointer Type");
case Capabilities:
return tr("Capabilities");
case SystemId:
return tr("System ID");
case SeatName:
return tr("Seat Name");
case AvailableVirtualGeometry:
return tr("Available Virtual Geometry");
case MaximumPoints:
return tr("Maximum Points");
case ButtonCount:
return tr("Button Count");
case UniqueId:
return tr("Unique ID");
case NRoles:
break;
}
}
return {};
}
bool InputDeviceModel::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::ChildAdded)
// At this time, the child is not fully-constructed.
// Emit a signal which is connected to onDeviceAdded via queued connection, to delay row insertion.
emit deviceAdded(static_cast<QChildEvent *>(event)->child());
return false;
}
void InputDeviceModel::onDeviceAdded(const QObject *o)
{
const QInputDevice *child = qobject_cast<const QInputDevice *>(o);
const QInputDevice *parent = qobject_cast<const QInputDevice *>(child->parent());
const int idx = parent->children().indexOf(child);
qCDebug(lcIPDM) << parent << "has a baby!" << child << "@" << idx;
beginInsertRows(createIndex(masterDeviceIndex(parent), 0, parent), idx, idx);
endInsertRows();
}
void InputDeviceModel::watchDevice(const QInputDevice *dev) const
{
if (!m_known.contains(dev)) {
m_known << dev;
connect(dev, &QObject::destroyed, this, &InputDeviceModel::deviceDestroyed);
if (masterDevicePred(dev))
const_cast<QInputDevice *>(dev)->installEventFilter(const_cast<InputDeviceModel *>(this));
}
}
void InputDeviceModel::deviceDestroyed(QObject *o)
{
beginResetModel();
const QInputDevice *dev = static_cast<QInputDevice *>(o);
bool needsReset = true;
if (!masterDevicePred(dev)) {
const QInputDevice *parent = static_cast<const QInputDevice *>(dev->parent());
const int idx = parent->children().indexOf(dev);
Q_ASSERT(idx >= 0);
beginRemoveRows(createIndex(masterDeviceIndex(parent), 0, parent), idx, idx);
endRemoveRows();
needsReset = false;
}
m_known.removeOne(dev);
if (needsReset)
endResetModel();
}
QVariant InputDeviceModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
role = index.column() + Role::Name;
if (role >= NRoles)
return {};
const QInputDevice *dev = static_cast<QInputDevice *>(index.internalPointer());
watchDevice(dev);
if (role < Name)
qCDebug(lcIPDM) << index << Qt::ItemDataRole(role) << dev;
else
qCDebug(lcIPDM) << index << Role(role) << dev;
const QPointingDevice *pdev = qobject_cast<const QPointingDevice *>(dev);
switch (role) {
case Name:
return dev->name();
case DeviceType:
return enumToString(dev, "DeviceType", int(dev->type()));
case PointerType:
return pdev ? enumToString(pdev, "PointerType", int(pdev->pointerType()))
: QString();
case Capabilities:
return capabilitiesString(dev);
case SystemId:
return dev->systemId();
case SeatName:
return dev->seatName();
case AvailableVirtualGeometry: {
const auto rect = dev->availableVirtualGeometry();
return tr("%1 x %2 %3 %4").arg(rect.width()).arg(rect.height()).arg(rect.x()).arg(rect.y());
}
case MaximumPoints:
return pdev ? pdev->maximumPoints() : 0;
case ButtonCount:
return pdev ? pdev->buttonCount() : 0;
case UniqueId:
return pdev ? pdev->uniqueId().numericId() : 0;
}
return {};
}
#include "moc_inputdevicemodel.cpp"

View File

@ -0,0 +1,55 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef INPUTDEVICEMODEL_H
#define INPUTDEVICEMODEL_H
#include <QAbstractItemModel>
class QInputDevice;
class InputDeviceModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum Role {
Name = Qt::UserRole + 1,
DeviceType,
PointerType,
Capabilities,
SystemId,
SeatName,
AvailableVirtualGeometry,
MaximumPoints,
ButtonCount,
UniqueId,
NRoles
};
Q_ENUM(Role);
explicit InputDeviceModel(QObject *parent = nullptr);
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
signals:
void deviceAdded(const QObject *o);
private slots:
void onDeviceAdded(const QObject *o);
private:
void watchDevice(const QInputDevice *dev) const;
void deviceDestroyed(QObject *o);
mutable QList<const QInputDevice *> m_known;
};
#endif // INPUTDEVICEMODEL_H

View File

@ -0,0 +1,24 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include <QLoggingCategory>
#include <QTreeView>
#include "inputdevicemodel.h"
int main(int argc, char **argv)
{
QLoggingCategory::setFilterRules(QStringLiteral("qt.qpa.input.devices=true"));
QApplication app(argc, argv);
QTreeView view;
view.setModel(new InputDeviceModel(&view));
view.resize(1280, 600);
view.show();
view.resizeColumnToContents(0);
app.exec();
}

View File

@ -5,6 +5,7 @@ SUBDIRS = \
filetest \
embeddedintoforeignwindow \
foreignwindows \
fontfeatures \
gestures \
highdpi \
inputmethodhints \

View File

@ -0,0 +1,9 @@
# Copyright (C) 2023 Intel Corporation.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(qdnslookup
SOURCES
main.cpp
LIBRARIES
Qt::Network
)

View File

@ -0,0 +1,184 @@
// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QElapsedTimer>
#include <QtCore/QMetaEnum>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QDnsLookup>
#include <stdlib.h>
#include <stdio.h>
#include <chrono>
using namespace Qt::StringLiterals;
using namespace std::chrono;
using namespace std::chrono_literals;
static QDnsLookup::Type typeFromString(QString str)
{
// we can use the meta object
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
bool ok;
int value = me.keyToValue(str.toUpper().toLatin1(), &ok);
if (!ok)
return QDnsLookup::Type(0);
return QDnsLookup::Type(value);
}
static int showHelp(const char *argv0, int exitcode)
{
// like dig
printf("%s [@global-server] [domain] [query-type]\n", argv0);
return exitcode;
}
static auto parseServerAddress(QString server)
{
struct R {
QHostAddress address;
int port = -1;
} r;
// let's use QUrl to help us
QUrl url;
url.setAuthority(server);
if (!url.isValid() || !url.userInfo().isNull())
return r; // failed
r.port = url.port();
r.address.setAddress(url.host());
return r;
}
static void printAnswers(const QDnsLookup &lookup)
{
printf("\n;; ANSWER:\n");
static auto printRecordCommon = [](const auto &rr, const char *rrtype) {
printf("%-23s %6d IN %s\t", qPrintable(rr.name()), rr.timeToLive(), rrtype);
};
auto printNameRecords = [](const char *rrtype, const QList<QDnsDomainNameRecord> list) {
for (const QDnsDomainNameRecord &rr : list) {
printRecordCommon(rr, rrtype);
printf("%s\n", qPrintable(rr.value()));
}
};
for (const QDnsMailExchangeRecord &rr : lookup.mailExchangeRecords()) {
printRecordCommon(rr, "MX");
printf("%d %s\n", rr.preference(), qPrintable(rr.exchange()));
}
for (const QDnsServiceRecord &rr : lookup.serviceRecords()) {
printRecordCommon(rr, "SRV");
printf("%d %d %d %s\n", rr.priority(), rr.weight(), rr.port(),
qPrintable(rr.target()));
}
printNameRecords("NS", lookup.nameServerRecords());
printNameRecords("PTR", lookup.pointerRecords());
printNameRecords("CNAME", lookup.canonicalNameRecords());
for (const QDnsHostAddressRecord &rr : lookup.hostAddressRecords()) {
QHostAddress addr = rr.value();
printRecordCommon(rr, addr.protocol() == QHostAddress::IPv6Protocol ? "AAAA" : "A");
printf("%s\n", qPrintable(addr.toString()));
}
for (const QDnsTextRecord &rr : lookup.textRecords()) {
printRecordCommon(rr, "TXT");
for (const QByteArray &data : rr.values())
printf("%s ", qPrintable(QDebug::toString(data)));
puts("");
}
}
static void printResults(const QDnsLookup &lookup, QElapsedTimer::Duration duration)
{
if (QDnsLookup::Error error = lookup.error())
printf(";; status: %s (%s)\n", QMetaEnum::fromType<QDnsLookup::Error>().valueToKey(error),
qPrintable(lookup.errorString()));
else
printf(";; status: NoError\n");
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
printf(";; QUESTION:\n");
printf(";%-30s IN %s\n", qPrintable(lookup.name()),
me.valueToKey(lookup.type()));
if (lookup.error() == QDnsLookup::NoError)
printAnswers(lookup);
printf("\n;; Query time: %lld ms\n", qint64(duration_cast<milliseconds>(duration).count()));
if (QHostAddress server = lookup.nameserver(); !server.isNull())
printf(";; SERVER: %s#%d\n", qPrintable(server.toString()), lookup.nameserverPort());
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDnsLookup::Type type = {};
QString domain, server;
const QStringList args = QCoreApplication::arguments().sliced(1);
for (const QString &arg : args) {
if (arg.startsWith(u'@')) {
server = arg.mid(1);
continue;
}
if (arg == u"-h")
return showHelp(argv[0], EXIT_SUCCESS);
if (domain.isNull()) {
domain = arg;
continue;
}
if (type != QDnsLookup::Type{})
return showHelp(argv[0], EXIT_FAILURE);
type = typeFromString(arg);
if (type == QDnsLookup::Type{}) {
fprintf(stderr, "%s: unknown DNS record type '%s'. Valid types are:\n",
argv[0], qPrintable(arg));
QMetaEnum me = QMetaEnum::fromType<QDnsLookup::Type>();
for (int i = 0; i < me.keyCount(); ++i)
fprintf(stderr, " %s\n", me.key(i));
return EXIT_FAILURE;
}
}
if (domain.isEmpty())
domain = u"qt-project.org"_s;
if (type == QDnsLookup::Type{})
type = QDnsLookup::A;
QDnsLookup lookup(type, domain);
if (!server.isEmpty()) {
auto addr = parseServerAddress(server);
if (addr.address.isNull()) {
fprintf(stderr, "%s: could not parse name server address '%s'\n",
argv[0], qPrintable(server));
return EXIT_FAILURE;
}
lookup.setNameserver(addr.address);
if (addr.port > 0)
lookup.setNameserverPort(addr.port);
}
// execute the lookup
QObject::connect(&lookup, &QDnsLookup::finished, qApp, &QCoreApplication::quit);
QTimer::singleShot(15s, []() { qApp->exit(EXIT_FAILURE); });
QElapsedTimer timer;
timer.start();
lookup.lookup();
if (a.exec() == EXIT_FAILURE)
return EXIT_FAILURE;
printf("; <<>> QDnsLookup " QT_VERSION_STR " <<>> %s %s\n",
qPrintable(QCoreApplication::applicationName()), qPrintable(args.join(u' ')));
printResults(lookup, timer.durationElapsed());
return 0;
}

View File

@ -1,6 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#add_subdirectory(device_information) # special case no member named 'staticQtMetaObject'
#add_subdirectory(device_information) # TODO: no member named 'staticQtMetaObject'
add_subdirectory(event_compression)
add_subdirectory(regular_widgets)

View File

@ -30,6 +30,8 @@ add_subdirectory(geometryshader)
add_subdirectory(stenciloutline)
add_subdirectory(stereo)
add_subdirectory(tex1d)
add_subdirectory(displacement)
add_subdirectory(imguirenderer)
if(QT_FEATURE_widgets)
add_subdirectory(rhiwidget)
endif()

View File

@ -0,0 +1,26 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(displacement
GUI
SOURCES
displacement.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
qt_internal_add_resource(displacement "displacement"
PREFIX
"/"
FILES
"material.vert.qsb"
"material.tesc.qsb"
"material.tese.qsb"
"material.frag.qsb"
"heightmap.png"
)
set(imgui_base ../shared/imgui)
set(imgui_target displacement)
include(${imgui_base}/imgui.cmakeinc)

View File

@ -0,0 +1,7 @@
#!/bin/sh
qsb --glsl 320es,410 --hlsl 50 --msl 12 --msltess material.vert -o material.vert.qsb
qsb --glsl 320es,410 --hlsl 50 --msl 12 material.frag -o material.frag.qsb
qsb --glsl 320es,410 --msl 12 --tess-mode triangles material.tesc -o material.tesc.qsb
qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 material.tese -o material.tese.qsb
qsb -r hlsl,50,material_hull.hlsl material.tesc.qsb
qsb -r hlsl,50,material_domain.hlsl material.tese.qsb

View File

@ -0,0 +1,8 @@
qsb --glsl 320es,410 --hlsl 50 --msl 12 --msltess material.vert -o material.vert.qsb
qsb --glsl 320es,410 --hlsl 50 --msl 12 material.frag -o material.frag.qsb
qsb --glsl 320es,410 --msl 12 --tess-mode triangles material.tesc -o material.tesc.qsb
qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 material.tese -o material.tese.qsb
qsb -r hlsl,50,material_hull.hlsl material.tesc.qsb
qsb -r hlsl,50,material_domain.hlsl material.tese.qsb

View File

@ -0,0 +1,199 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#define EXAMPLEFW_IMGUI
#include "../shared/examplefw.h"
#include "../shared/cube.h"
// Another tessellation test. Compatible with Direct 3D via hand-written hull
// and domain shaders, but this already pushes the limits of what is sensible
// when it comes to injecting hand-written HLSL code to get tessellation
// functional (cbuffer layout, resource registers all need to be figured out
// manually and works only as long as the GLSL source is not changing, etc.).
// Note that the domain shader must use SampleLevel (textureLod), it won't
// compile for ds_5_0 otherwise.
static const quint32 UBUF_SIZE = 80;
struct {
QList<QRhiResource *> releasePool;
QRhiBuffer *vbuf;
QRhiBuffer *ubuf;
QRhiTexture *tex;
QRhiSampler *sampler;
QRhiShaderResourceBindings *srb;
QRhiGraphicsPipeline *psWire;
QRhiGraphicsPipeline *psSolid;
bool rotate = true;
float rotation = 0.0f;
float viewZ = 0.0f;
float displacementAmount = 0.0f;
int tessInner = 4;
int tessOuter = 4;
bool useTex = false;
bool wireframe = true;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
} d;
void Window::customInit()
{
if (!m_r->isFeatureSupported(QRhi::Tessellation))
qFatal("Tessellation is not supported");
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
d.vbuf->create();
d.releasePool << d.vbuf;
d.initialUpdates->uploadStaticBuffer(d.vbuf, cube);
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, m_r->ubufAligned(UBUF_SIZE));
d.ubuf->create();
d.releasePool << d.ubuf;
QImage image;
image.load(":/heightmap.png");
if (image.isNull())
qFatal("Failed to load displacement map");
d.tex = m_r->newTexture(QRhiTexture::RGBA8, image.size());
d.tex->create();
d.releasePool << d.tex;
d.initialUpdates->uploadTexture(d.tex, image);
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat);
d.releasePool << d.sampler;
d.sampler->create();
d.srb = m_r->newShaderResourceBindings();
d.releasePool << d.srb;
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0,
QRhiShaderResourceBinding::TessellationControlStage
| QRhiShaderResourceBinding::TessellationEvaluationStage,
d.ubuf),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::TessellationEvaluationStage, d.tex, d.sampler)
});
d.srb->create();
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 3 * sizeof(float) },
{ 2 * sizeof(float) },
{ 3 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
{ 1, 1, QRhiVertexInputAttribute::Float2, 0 },
{ 2, 2, QRhiVertexInputAttribute::Float3, 0 }
});
const QRhiShaderStage stages[] = {
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/material.vert.qsb")) },
{ QRhiShaderStage::TessellationControl, getShader(QLatin1String(":/material.tesc.qsb")) },
{ QRhiShaderStage::TessellationEvaluation, getShader(QLatin1String(":/material.tese.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/material.frag.qsb")) }
};
d.psWire = m_r->newGraphicsPipeline();
d.releasePool << d.psWire;
d.psWire->setTopology(QRhiGraphicsPipeline::Patches);
d.psWire->setPatchControlPointCount(3);
d.psWire->setShaderStages(stages, stages + 4);
d.psWire->setDepthTest(true);
d.psWire->setDepthWrite(true);
d.psWire->setCullMode(QRhiGraphicsPipeline::Back);
d.psWire->setPolygonMode(QRhiGraphicsPipeline::Line);
d.psWire->setVertexInputLayout(inputLayout);
d.psWire->setShaderResourceBindings(d.srb);
d.psWire->setRenderPassDescriptor(m_rp);
d.psWire->create();
d.psSolid = m_r->newGraphicsPipeline();
d.releasePool << d.psSolid;
d.psSolid->setTopology(QRhiGraphicsPipeline::Patches);
d.psSolid->setPatchControlPointCount(3);
d.psSolid->setShaderStages(stages, stages + 4);
d.psSolid->setDepthTest(true);
d.psSolid->setDepthWrite(true);
d.psSolid->setCullMode(QRhiGraphicsPipeline::Back);
d.psSolid->setVertexInputLayout(inputLayout);
d.psSolid->setShaderResourceBindings(d.srb);
d.psSolid->setRenderPassDescriptor(m_rp);
d.psSolid->create();
}
void Window::customRelease()
{
qDeleteAll(d.releasePool);
d.releasePool.clear();
}
void Window::customRender()
{
const QSize outputSizeInPixels = m_sc->currentPixelSize();
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
QRhiResourceUpdateBatch *u = nullptr;
if (d.initialUpdates) {
u = d.initialUpdates;
d.initialUpdates = nullptr;
}
char *p = d.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
QMatrix4x4 mvp = m_proj;
mvp.translate(0, 0, d.viewZ);
mvp.rotate(d.rotation, 1, 1, 0);
mvp.scale(0.5f);
memcpy(p, mvp.constData(), 64);
memcpy(p + 64, &d.displacementAmount, sizeof(float));
float tessInnerFloat = d.tessInner;
memcpy(p + 68, &tessInnerFloat, sizeof(float));
float tessOuterFloat = d.tessOuter;
memcpy(p + 72, &tessOuterFloat, sizeof(float));
qint32 useTex = d.useTex ? 1 : 0;
memcpy(p + 76, &useTex, sizeof(qint32));
d.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
const QRhiCommandBuffer::VertexInput vbufBinding[] = {
{ d.vbuf, 0 },
{ d.vbuf, quint32(36 * 3 * sizeof(float)) },
{ d.vbuf, quint32(36 * (3 + 2) * sizeof(float)) }
};
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u, QRhiCommandBuffer::DoNotTrackResourcesForCompute);
cb->setGraphicsPipeline(d.wireframe ? d.psWire : d.psSolid);
cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
cb->setShaderResources(d.srb);
cb->setVertexInput(0, 3, vbufBinding);
cb->draw(36);
m_imguiRenderer->render();
cb->endPass();
if (d.rotate)
d.rotation += 1;
}
void Window::customGui()
{
ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(500, 250), ImGuiCond_FirstUseEver);
ImGui::Begin("Test");
ImGui::SliderInt("Inner", &d.tessInner, 0, 20);
ImGui::SliderInt("Outer", &d.tessOuter, 0, 20);
ImGui::SliderFloat("Displacement", &d.displacementAmount, 0.0f, 4.0f);
ImGui::Checkbox("Use displacement texture", &d.useTex);
ImGui::SliderFloat("Z", &d.viewZ, -16.0f, 4.0f);
ImGui::Checkbox("Rotate", &d.rotate);
ImGui::Checkbox("Wireframe", &d.wireframe);
ImGui::End();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

View File

@ -0,0 +1,10 @@
#version 440
layout(location = 0) out vec4 fragColor;
layout(location = 1) in vec3 in_normal;
void main()
{
fragColor = vec4((normalize(in_normal) + 1.0) / 2.0, 1.0);
}

Binary file not shown.

View File

@ -0,0 +1,32 @@
#version 440
layout(vertices = 3) out;
layout(location = 0) in vec2 in_uv[];
layout(location = 1) in vec3 in_normal[];
layout(location = 0) out vec2 out_uv[];
layout(location = 1) out vec3 out_normal[];
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float displacementAmount;
float tessInner;
float tessOuter;
int useTex;
};
void main()
{
if (gl_InvocationID == 0) {
gl_TessLevelOuter[0] = tessOuter;
gl_TessLevelOuter[1] = tessOuter;
gl_TessLevelOuter[2] = tessOuter;
gl_TessLevelInner[0] = tessInner;
}
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
out_uv[gl_InvocationID] = in_uv[gl_InvocationID];
out_normal[gl_InvocationID] = in_normal[gl_InvocationID];
}

Binary file not shown.

View File

@ -0,0 +1,37 @@
#version 440
layout(triangles, fractional_odd_spacing, ccw) in;
layout(location = 0) in vec2 in_uv[];
layout(location = 1) in vec3 in_normal[];
//layout(location = 0) out vec2 out_uv;
layout(location = 1) out vec3 out_normal;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float displacementAmount;
float tessInner;
float tessOuter;
int useTex;
};
layout(binding = 1) uniform sampler2D displacementMap;
void main()
{
vec4 pos = (gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position);
vec2 uv = gl_TessCoord.x * in_uv[0].xy + gl_TessCoord.y * in_uv[1].xy;
vec3 normal = normalize(gl_TessCoord.x * in_normal[0] + gl_TessCoord.y * in_normal[1] + gl_TessCoord.z * in_normal[2]);
vec4 c = texture(displacementMap, uv);
const vec3 yCoeff_709 = vec3(0.2126, 0.7152, 0.0722);
float df = dot(c.rgb, yCoeff_709);
if (useTex == 0)
df = 1.0;
vec3 displacedPos = pos.xyz + normal * df * displacementAmount;
gl_Position = mvp * vec4(displacedPos, 1.0);
// out_uv = uv;
out_normal = normal;
}

Binary file not shown.

View File

@ -0,0 +1,15 @@
#version 440
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 uv;
layout(location = 2) in vec3 normal;
layout(location = 0) out vec2 out_uv;
layout(location = 1) out vec3 out_normal;
void main()
{
gl_Position = vec4(position, 1.0);
out_uv = uv;
out_normal = normal;
}

Binary file not shown.

View File

@ -0,0 +1,54 @@
struct Input
{
float edges[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
struct PatchInput
{
float3 position : POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
};
struct PixelInput
{
//float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float4 position : SV_POSITION;
};
cbuffer buf : register(b0)
{
row_major float4x4 mvp : packoffset(c0);
float displacementAmount : packoffset(c4);
float tessInner : packoffset(c4.y);
float tessOuter : packoffset(c4.z);
int useTex : packoffset(c4.w);
};
Texture2D<float4> tex : register(t1);
SamplerState texsampler : register(s1);
[domain("tri")]
PixelInput main(Input input, float3 uvwCoord : SV_DomainLocation, const OutputPatch<PatchInput, 3> patch)
{
PixelInput output;
float3 pos = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
float2 uv = uvwCoord.x * patch[0].uv + uvwCoord.y * patch[1].uv;
float3 normal = normalize(uvwCoord.x * patch[0].normal + uvwCoord.y * patch[1].normal + uvwCoord.z * patch[2].normal);
float4 c = tex.SampleLevel(texsampler, uv, 0.0);
const float3 yCoeff_709 = float3(0.2126, 0.7152, 0.0722);
float df = dot(c.rgb, yCoeff_709);
if (useTex == 0)
df = 1.0;
float3 displacedPos = pos + normal * df * displacementAmount;
output.position = mul(float4(displacedPos, 1.0), mvp);
//output.uv = uv;
output.normal = normal;
return output;
}

View File

@ -0,0 +1,52 @@
struct Input
{
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float4 position : SV_Position;
};
struct Output
{
float3 position : POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
};
struct ConstantData
{
float edges[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
cbuffer buf : register(b0)
{
row_major float4x4 mvp : packoffset(c0);
float displacementAmount : packoffset(c4);
float tessInner : packoffset(c4.y);
float tessOuter : packoffset(c4.z);
int useTex : packoffset(c4.w);
};
ConstantData patchConstFunc(InputPatch<Input, 3> ip, uint PatchID : SV_PrimitiveID )
{
ConstantData d;
d.edges[0] = tessOuter;
d.edges[1] = tessOuter;
d.edges[2] = tessOuter;
d.inside = tessInner;
return d;
}
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("patchConstFunc")]
Output main(InputPatch<Input, 3> patch, uint pointId : SV_OutputControlPointID, uint patchId : SV_PrimitiveID)
{
Output output;
output.position = patch[pointId].position;
output.uv = patch[pointId].uv;
output.normal = patch[pointId].normal;
return output;
}

View File

@ -3,7 +3,7 @@
#include "hellowindow.h"
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
static float vertexData[] = {
// Y up (note clipSpaceCorrMatrix in m_proj), CCW

View File

@ -20,6 +20,8 @@ QString graphicsApiName(QRhi::Implementation graphicsApi)
return QLatin1String("Vulkan");
case QRhi::D3D11:
return QLatin1String("Direct3D 11");
case QRhi::D3D12:
return QLatin1String("Direct3D 12");
case QRhi::Metal:
return QLatin1String("Metal");
default:
@ -51,8 +53,10 @@ int main(int argc, char **argv)
cmdLineParser.addOption(glOption);
QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
cmdLineParser.addOption(vkOption);
QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3dOption);
QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3d11Option);
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
cmdLineParser.addOption(d3d12Option);
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
cmdLineParser.addOption(mtlOption);
@ -63,8 +67,10 @@ int main(int argc, char **argv)
graphicsApi = QRhi::OpenGLES2;
if (cmdLineParser.isSet(vkOption))
graphicsApi = QRhi::Vulkan;
if (cmdLineParser.isSet(d3dOption))
if (cmdLineParser.isSet(d3d11Option))
graphicsApi = QRhi::D3D11;
if (cmdLineParser.isSet(d3d12Option))
graphicsApi = QRhi::D3D12;
if (cmdLineParser.isSet(mtlOption))
graphicsApi = QRhi::Metal;

View File

@ -16,6 +16,7 @@ Window::Window(QRhi::Implementation graphicsApi)
setSurfaceType(VulkanSurface);
break;
case QRhi::D3D11:
case QRhi::D3D12:
setSurfaceType(Direct3DSurface);
break;
case QRhi::Metal:
@ -81,7 +82,7 @@ bool Window::event(QEvent *e)
void Window::init()
{
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers | QRhi::EnableProfiling;
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
if (m_graphicsApi == QRhi::Null) {
QRhiNullInitParams params;
@ -112,6 +113,10 @@ void Window::init()
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
m_rhi.reset(QRhi::create(QRhi::D3D11, &params, rhiFlags));
} else if (m_graphicsApi == QRhi::D3D12) {
QRhiD3D12InitParams params;
params.enableDebugLayer = true;
m_rhi.reset(QRhi::create(QRhi::D3D12, &params, rhiFlags));
}
#endif

View File

@ -5,21 +5,8 @@
#define WINDOW_H
#include <QWindow>
#include <QtGui/private/qrhinull_p.h>
#if QT_CONFIG(opengl)
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#endif
#if QT_CONFIG(vulkan)
#include <QtGui/private/qrhivulkan_p.h>
#endif
#ifdef Q_OS_WIN
#include <QtGui/private/qrhid3d11_p.h>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include <QtGui/private/qrhimetal_p.h>
#endif
#include <rhi/qrhi.h>
class Window : public QWindow
{

View File

@ -0,0 +1,30 @@
qt_internal_add_manual_test(imguirenderer
GUI
SOURCES
imguirenderer.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
set_source_files_properties("../shared/texture.vert.qsb"
PROPERTIES QT_RESOURCE_ALIAS "texture.vert.qsb"
)
set_source_files_properties("../shared/texture.frag.qsb"
PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
)
set_source_files_properties("../shared/qt256.png"
PROPERTIES QT_RESOURCE_ALIAS "qt256.png"
)
qt_internal_add_resource(imguirenderer "imguirenderer"
PREFIX
"/"
FILES
"../shared/texture.vert.qsb"
"../shared/texture.frag.qsb"
"../shared/qt256.png"
)
set(imgui_base ../shared/imgui)
set(imgui_target imguirenderer)
include(${imgui_base}/imgui.cmakeinc)

View File

@ -0,0 +1,128 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#define EXAMPLEFW_IMGUI
#include "../shared/examplefw.h"
#include "../shared/cube.h"
struct {
QMatrix4x4 winProj;
QList<QRhiResource *> releasePool;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiTexture *tex = nullptr;
QRhiSampler *sampler = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
bool showDemoWindow = true;
float rotation = 0.0f;
} d;
void Window::customInit()
{
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
d.vbuf->create();
d.releasePool << d.vbuf;
d.initialUpdates->uploadStaticBuffer(d.vbuf, cube);
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
d.ubuf->create();
d.releasePool << d.ubuf;
float opacity = 1.0f;
d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &opacity);
QImage image = QImage(QLatin1String(":/qt256.png")).convertToFormat(QImage::Format_RGBA8888).mirrored();
d.tex = m_r->newTexture(QRhiTexture::RGBA8, QSize(image.width(), image.height()), 1, {});
d.releasePool << d.tex;
d.tex->create();
d.initialUpdates->uploadTexture(d.tex, image);
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.releasePool << d.sampler;
d.sampler->create();
d.srb = m_r->newShaderResourceBindings();
d.releasePool << d.srb;
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler)
});
d.srb->create();
d.ps = m_r->newGraphicsPipeline();
d.releasePool << d.ps;
d.ps->setCullMode(QRhiGraphicsPipeline::Back);
const QRhiShaderStage stages[] = {
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) }
};
d.ps->setShaderStages(stages, stages + 2);
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 3 * sizeof(float) },
{ 2 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
{ 1, 1, QRhiVertexInputAttribute::Float2, 0 }
});
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->create();
}
void Window::customRelease()
{
qDeleteAll(d.releasePool);
d.releasePool.clear();
}
void Window::customRender()
{
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch();
if (d.initialUpdates) {
u->merge(d.initialUpdates);
d.initialUpdates->release();
d.initialUpdates = nullptr;
}
QMatrix4x4 mvp = m_proj;
mvp.rotate(d.rotation, 0, 1, 0);
u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData());
const QSize outputSizeInPixels = m_sc->currentPixelSize();
cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.ps);
cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height()));
cb->setShaderResources();
const QRhiCommandBuffer::VertexInput vbufBindings[] = {
{ d.vbuf, 0 },
{ d.vbuf, quint32(36 * 3 * sizeof(float)) }
};
cb->setVertexInput(0, 2, vbufBindings);
cb->draw(36);
m_imguiRenderer->render();
cb->endPass();
}
void Window::customGui()
{
ImGui::ShowDemoWindow(&d.showDemoWindow);
ImGui::SetNextWindowPos(ImVec2(50, 120), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(400, 100), ImGuiCond_FirstUseEver);
ImGui::Begin("Test");
ImGui::SliderFloat("Rotation", &d.rotation, 0.0f, 360.0f);
ImGui::End();
}

View File

@ -13,33 +13,17 @@
#include <QWindow>
#include <QPlatformSurfaceEvent>
#include <QElapsedTimer>
#include <QtGui/private/qshader_p.h>
#include <QFile>
#ifndef QT_NO_OPENGL
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#endif
#if QT_CONFIG(vulkan)
#include <QLoggingCategory>
#include <QtGui/private/qrhivulkan_p.h>
#endif
#ifdef Q_OS_WIN
#include <QtGui/private/qrhid3d11_p.h>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include <QtGui/private/qrhimetal_p.h>
#endif
#include <QOffscreenSurface>
#include <rhi/qrhi.h>
enum GraphicsApi
{
OpenGL,
Vulkan,
D3D11,
D3D12,
Metal
};
@ -54,6 +38,8 @@ static QString graphicsApiName()
return QLatin1String("Vulkan");
case D3D11:
return QLatin1String("Direct3D 11");
case D3D12:
return QLatin1String("Direct3D 12");
case Metal:
return QLatin1String("Metal");
default:
@ -98,6 +84,10 @@ void createRhi()
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
r.r = QRhi::create(QRhi::D3D11, &params);
} else if (graphicsApi == D3D12) {
QRhiD3D12InitParams params;
params.enableDebugLayer = true;
r.r = QRhi::create(QRhi::D3D12, &params);
}
#endif
@ -281,6 +271,7 @@ Window::Window(const QString &title, const QColor &bgColor, int axis, bool noVSy
#endif
break;
case D3D11:
case D3D12:
setSurfaceType(Direct3DSurface);
break;
case Metal:
@ -494,6 +485,8 @@ int main(int argc, char **argv)
cmdLineParser.addOption(vkOption);
QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3dOption);
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
cmdLineParser.addOption(d3d12Option);
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
cmdLineParser.addOption(mtlOption);
cmdLineParser.process(app);
@ -503,6 +496,8 @@ int main(int argc, char **argv)
graphicsApi = Vulkan;
if (cmdLineParser.isSet(d3dOption))
graphicsApi = D3D11;
if (cmdLineParser.isSet(d3d12Option))
graphicsApi = D3D12;
if (cmdLineParser.isSet(mtlOption))
graphicsApi = Metal;
@ -517,6 +512,7 @@ int main(int argc, char **argv)
r.instance = new QVulkanInstance;
if (graphicsApi == Vulkan) {
r.instance->setLayers({ "VK_LAYER_KHRONOS_validation" });
r.instance->setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
if (!r.instance->create()) {
qWarning("Failed to create Vulkan instance, switching to OpenGL");
graphicsApi = OpenGL;

View File

@ -16,27 +16,10 @@
#include <QEvent>
#include <QCommandLineParser>
#include <QElapsedTimer>
#include <QtGui/private/qshader_p.h>
#include <QFile>
#ifndef QT_NO_OPENGL
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#endif
#if QT_CONFIG(vulkan)
#include <QLoggingCategory>
#include <QtGui/private/qrhivulkan_p.h>
#endif
#ifdef Q_OS_WIN
#include <QtGui/private/qrhid3d11_p.h>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include <QtGui/private/qrhimetal_p.h>
#endif
#include <QOffscreenSurface>
#include <rhi/qrhi.h>
#ifdef Q_OS_DARWIN
#include <QtCore/private/qcore_mac_p.h>
@ -66,6 +49,8 @@ static QString graphicsApiName()
return QLatin1String("Vulkan");
case D3D11:
return QLatin1String("Direct3D 11");
case D3D12:
return QLatin1String("Direct3D 12");
case Metal:
return QLatin1String("Metal");
default:
@ -329,6 +314,10 @@ void Renderer::createRhi()
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
r = QRhi::create(QRhi::D3D11, &params, rhiFlags);
} else if (graphicsApi == D3D12) {
QRhiD3D12InitParams params;
params.enableDebugLayer = true;
r = QRhi::create(QRhi::D3D12, &params, rhiFlags);
}
#endif
@ -681,6 +670,8 @@ int main(int argc, char **argv)
cmdLineParser.addOption(vkOption);
QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3dOption);
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
cmdLineParser.addOption(d3d12Option);
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
cmdLineParser.addOption(mtlOption);
cmdLineParser.process(app);
@ -690,6 +681,8 @@ int main(int argc, char **argv)
graphicsApi = Vulkan;
if (cmdLineParser.isSet(d3dOption))
graphicsApi = D3D11;
if (cmdLineParser.isSet(d3d12Option))
graphicsApi = D3D12;
if (cmdLineParser.isSet(mtlOption))
graphicsApi = Metal;

View File

@ -4,10 +4,6 @@
#include "window.h"
#include <QPlatformSurfaceEvent>
#ifndef QT_NO_OPENGL
#include <QtGui/private/qrhigles2_p.h>
#endif
#if QT_CONFIG(vulkan)
extern QVulkanInstance *instance;
#endif
@ -25,6 +21,7 @@ Window::Window(const QString &title, GraphicsApi api)
#endif
break;
case D3D11:
case D3D12:
setSurfaceType(Direct3DSurface);
break;
case Metal:

View File

@ -11,6 +11,7 @@ enum GraphicsApi
OpenGL,
Vulkan,
D3D11,
D3D12,
Metal
};

View File

@ -7,27 +7,10 @@
#include <QFile>
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <QtGui/private/qshader_p.h>
#include <QtGui/private/qrhinull_p.h>
#ifndef QT_NO_OPENGL
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#endif
#if QT_CONFIG(vulkan)
#include <QLoggingCategory>
#include <QtGui/private/qrhivulkan_p.h>
#endif
#include <QOffscreenSurface>
#ifdef Q_OS_WIN
#include <QtGui/private/qrhid3d11_p.h>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include <QtGui/private/qrhimetal_p.h>
#endif
#include <rhi/qrhi.h>
//#define TEST_FINISH
@ -51,6 +34,7 @@ enum GraphicsApi
OpenGL,
Vulkan,
D3D11,
D3D12,
Metal,
Null
};
@ -66,6 +50,8 @@ QString graphicsApiName()
return QLatin1String("Vulkan");
case D3D11:
return QLatin1String("Direct3D 11");
case D3D12:
return QLatin1String("Direct3D 12");
case Metal:
return QLatin1String("Metal");
case Null:
@ -96,8 +82,10 @@ int main(int argc, char **argv)
cmdLineParser.addOption(glOption);
QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
cmdLineParser.addOption(vkOption);
QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3dOption);
QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3d11Option);
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
cmdLineParser.addOption(d3d12Option);
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
cmdLineParser.addOption(mtlOption);
QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
@ -107,8 +95,10 @@ int main(int argc, char **argv)
graphicsApi = OpenGL;
if (cmdLineParser.isSet(vkOption))
graphicsApi = Vulkan;
if (cmdLineParser.isSet(d3dOption))
if (cmdLineParser.isSet(d3d11Option))
graphicsApi = D3D11;
if (cmdLineParser.isSet(d3d12Option))
graphicsApi = D3D12;
if (cmdLineParser.isSet(mtlOption))
graphicsApi = Metal;
if (cmdLineParser.isSet(nullOption))
@ -118,10 +108,11 @@ int main(int argc, char **argv)
qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText()));
QRhi *r = nullptr;
QRhi::Flags rhiFlags = QRhi::EnableTimestamps;
if (graphicsApi == Null) {
QRhiNullInitParams params;
r = QRhi::create(QRhi::Null, &params);
r = QRhi::create(QRhi::Null, &params, rhiFlags);
}
#if QT_CONFIG(vulkan)
@ -133,7 +124,7 @@ int main(int argc, char **argv)
if (inst.create()) {
QRhiVulkanInitParams params;
params.inst = &inst;
r = QRhi::create(QRhi::Vulkan, &params);
r = QRhi::create(QRhi::Vulkan, &params, rhiFlags);
} else {
qWarning("Failed to create Vulkan instance, switching to OpenGL");
graphicsApi = OpenGL;
@ -147,7 +138,7 @@ int main(int argc, char **argv)
offscreenSurface.reset(QRhiGles2InitParams::newFallbackSurface());
QRhiGles2InitParams params;
params.fallbackSurface = offscreenSurface.data();
r = QRhi::create(QRhi::OpenGLES2, &params);
r = QRhi::create(QRhi::OpenGLES2, &params, rhiFlags);
}
#endif
@ -155,14 +146,18 @@ int main(int argc, char **argv)
if (graphicsApi == D3D11) {
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
r = QRhi::create(QRhi::D3D11, &params);
r = QRhi::create(QRhi::D3D11, &params, rhiFlags);
} else if (graphicsApi == D3D12) {
QRhiD3D12InitParams params;
params.enableDebugLayer = true;
r = QRhi::create(QRhi::D3D12, &params, rhiFlags);
}
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
if (graphicsApi == Metal) {
QRhiMetalInitParams params;
r = QRhi::create(QRhi::Metal, &params);
r = QRhi::create(QRhi::Metal, &params, rhiFlags);
}
#endif
@ -289,6 +284,8 @@ int main(int argc, char **argv)
#ifdef TEST_FINISH
r->endOffscreenFrame();
#endif
if (r->isFeatureSupported(QRhi::Timestamps))
qDebug() << "GPU time:" << cb->lastCompletedGpuTime() << "seconds (may refer to a previous frame)";
}
delete ps;

View File

@ -5,7 +5,7 @@
#define EXAMPLEWIDGET_H
#include "rhiwidget.h"
#include <QtGui/private/qrhi_p.h>
#include <rhi/qrhi.h>
class ExampleRhiWidget : public QRhiWidget
{

View File

@ -5,7 +5,7 @@
#define RHIWIDGET_H
#include <QWidget>
#include <QtGui/private/qrhi_p.h>
#include <rhi/qrhi.h>
class QRhiWidgetPrivate;

View File

@ -25,6 +25,8 @@
* Author: Bill Hollings <bill.hollings@brenwill.com>
*/
// Normals added by Qt.
#ifndef CUBE_H
#define CUBE_H
@ -72,6 +74,7 @@ static const float cube[] = {
1.0f,-1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
// UVs
0.0f, 1.0f, // -X side
1.0f, 1.0f,
1.0f, 0.0f,
@ -113,6 +116,49 @@ static const float cube[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
// normals
-1.0, 0.0, 0.0, // -X side
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
0.0, 0.0, -1.0, // -Z side
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, -1.0, 0.0, // -Y side
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, 1.0, 0.0, // +Y side
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
1.0, 0.0, 0.0, // +X side
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 1.0, // +Z side
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0
};
// clang-format on

View File

@ -13,26 +13,13 @@
#include <QTimer>
#include <QLoggingCategory>
#include <QColorSpace>
#include <QtGui/private/qshader_p.h>
#include <QFile>
#include <QtGui/private/qrhinull_p.h>
#ifndef QT_NO_OPENGL
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#endif
#include <rhi/qrhi.h>
#if QT_CONFIG(vulkan)
#include <QtGui/private/qrhivulkan_p.h>
#endif
#ifdef Q_OS_WIN
#include <QtGui/private/qrhid3d11_p.h>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include <QtGui/private/qrhimetal_p.h>
#ifdef EXAMPLEFW_IMGUI
#include "qrhiimgui_p.h"
#include "imgui.h"
#endif
QShader getShader(const QString &name)
@ -44,12 +31,22 @@ QShader getShader(const QString &name)
return QShader();
}
QByteArray getResource(const QString &name)
{
QFile f(name);
if (f.open(QIODevice::ReadOnly))
return f.readAll();
return QByteArray();
}
enum GraphicsApi
{
Null,
OpenGL,
Vulkan,
D3D11,
D3D12,
Metal
};
@ -66,6 +63,8 @@ QString graphicsApiName()
return QLatin1String("Vulkan");
case D3D11:
return QLatin1String("Direct3D 11");
case D3D12:
return QLatin1String("Direct3D 12");
case Metal:
return QLatin1String("Metal");
default:
@ -74,12 +73,11 @@ QString graphicsApiName()
return QString();
}
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers | QRhi::EnableTimestamps;
int sampleCount = 1;
QRhiSwapChain::Flags scFlags;
QRhi::BeginFrameFlags beginFrameFlags;
QRhi::EndFrameFlags endFrameFlags;
int framesUntilTdr = -1;
bool transparentBackground = false;
bool debugLayer = true;
@ -99,6 +97,9 @@ protected:
void customInit();
void customRelease();
void customRender();
#ifdef EXAMPLEFW_IMGUI
void customGui();
#endif
void exposeEvent(QExposeEvent *) override;
bool event(QEvent *) override;
@ -127,6 +128,11 @@ protected:
QColor m_clearColor;
#ifdef EXAMPLEFW_IMGUI
QRhiImguiRenderer *m_imguiRenderer;
QRhiImgui m_imgui;
#endif
friend int main(int, char**);
};
@ -141,6 +147,7 @@ Window::Window()
setSurfaceType(VulkanSurface);
break;
case D3D11:
case D3D12:
setSurfaceType(Direct3DSurface);
break;
case Metal:
@ -200,6 +207,10 @@ bool Window::event(QEvent *e)
break;
default:
#ifdef EXAMPLEFW_IMGUI
if (m_imgui.processEvent(e))
return true;
#endif
break;
}
@ -238,11 +249,13 @@ void Window::init()
if (debugLayer)
qDebug("Enabling D3D11 debug layer");
params.enableDebugLayer = debugLayer;
if (framesUntilTdr > 0) {
params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
params.repeatDeviceKill = true;
}
m_r = QRhi::create(QRhi::D3D11, &params, rhiFlags);
} else if (graphicsApi == D3D12) {
QRhiD3D12InitParams params;
if (debugLayer)
qDebug("Enabling D3D12 debug layer");
params.enableDebugLayer = debugLayer;
m_r = QRhi::create(QRhi::D3D12, &params, rhiFlags);
}
#endif
@ -270,6 +283,21 @@ void Window::init()
m_rp = m_sc->newCompatibleRenderPassDescriptor();
m_sc->setRenderPassDescriptor(m_rp);
#ifdef EXAMPLEFW_IMGUI
ImGuiIO &io(ImGui::GetIO());
io.FontAllowUserScaling = true; // enable ctrl+wheel on windows
io.IniFilename = nullptr; // no imgui.ini
QByteArray font = getResource(QLatin1String(":/fonts/RobotoMono-Medium.ttf"));
ImFontConfig fontCfg;
fontCfg.FontDataOwnedByAtlas = false;
io.Fonts->Clear();
io.Fonts->AddFontFromMemoryTTF(font.data(), font.size(), 20.0f, &fontCfg);
m_imgui.rebuildFontAtlas();
m_imguiRenderer = new QRhiImguiRenderer;
#endif
customInit();
}
@ -286,6 +314,11 @@ void Window::releaseResources()
delete m_sc;
m_sc = nullptr;
#ifdef EXAMPLEFW_IMGUI
delete m_imguiRenderer;
m_imguiRenderer = nullptr;
#endif
delete m_r;
m_r = nullptr;
@ -354,6 +387,20 @@ void Window::render()
m_frameCount = 0;
}
#ifdef EXAMPLEFW_IMGUI
m_imgui.nextFrame(size(), devicePixelRatio(), QPointF(0, 0), std::bind(&Window::customGui, this));
m_imgui.syncRenderer(m_imguiRenderer);
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
QRhiRenderTarget *rt = m_sc->currentFrameRenderTarget();
const QSize outputSizeInPixels = m_sc->currentPixelSize();
const float dpr = devicePixelRatio();
QMatrix4x4 guiMvp = m_r->clipSpaceCorrMatrix();
guiMvp.ortho(0, outputSizeInPixels.width() / dpr, outputSizeInPixels.height() / dpr, 0, 1, -1);
m_imguiRenderer->prepare(m_r, rt, cb, guiMvp, 1.0f);
#endif
customRender();
m_r->endFrame(m_sc, endFrameFlags);
@ -390,8 +437,10 @@ int main(int argc, char **argv)
cmdLineParser.addOption(glOption);
QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
cmdLineParser.addOption(vkOption);
QCommandLineOption d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3dOption);
QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
cmdLineParser.addOption(d3d11Option);
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
cmdLineParser.addOption(d3d12Option);
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
cmdLineParser.addOption(mtlOption);
// Testing cleanup both with QWindow::close() (hitting X or Alt-F4) and
@ -401,12 +450,7 @@ int main(int argc, char **argv)
cmdLineParser.addOption(sdOption);
QCommandLineOption coreProfOption({ "c", "core" }, QLatin1String("Request a core profile context for OpenGL"));
cmdLineParser.addOption(coreProfOption);
// Attempt testing device lost situations on D3D at least.
QCommandLineOption tdrOption(QLatin1String("curse"), QLatin1String("Curse the graphics device. "
"(generate a device reset every <count> frames when on D3D11)"),
QLatin1String("count"));
cmdLineParser.addOption(tdrOption);
// Allow testing preferring the software adapter (D3D).
// Allow testing preferring the software adapter (D3D, Vulkan).
QCommandLineOption swOption(QLatin1String("software"), QLatin1String("Prefer a software renderer when choosing the adapter. "
"Only applicable with some APIs and platforms."));
cmdLineParser.addOption(swOption);
@ -421,8 +465,10 @@ int main(int argc, char **argv)
graphicsApi = OpenGL;
if (cmdLineParser.isSet(vkOption))
graphicsApi = Vulkan;
if (cmdLineParser.isSet(d3dOption))
if (cmdLineParser.isSet(d3d11Option))
graphicsApi = D3D11;
if (cmdLineParser.isSet(d3d12Option))
graphicsApi = D3D12;
if (cmdLineParser.isSet(mtlOption))
graphicsApi = Metal;
@ -471,11 +517,14 @@ int main(int argc, char **argv)
inst.setLayers({ "VK_LAYER_KHRONOS_validation" });
}
const QVersionNumber supportedVersion = inst.supportedApiVersion();
qDebug() << "Supported Vulkan API version:" << supportedVersion;
if (supportedVersion >= QVersionNumber(1, 1)) {
qDebug("Requesting Vulkan API 1.1 on the VkInstance");
if (supportedVersion >= QVersionNumber(1, 3))
inst.setApiVersion(QVersionNumber(1, 3));
else if (supportedVersion >= QVersionNumber(1, 2))
inst.setApiVersion(QVersionNumber(1, 2));
else if (supportedVersion >= QVersionNumber(1, 1))
inst.setApiVersion(QVersionNumber(1, 1));
}
qDebug() << "Requesting Vulkan API" << inst.apiVersion().toString();
qDebug() << "Instance-level version was reported as" << supportedVersion.toString();
inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
if (!inst.create()) {
qWarning("Failed to create Vulkan instance, switching to OpenGL");
@ -484,9 +533,6 @@ int main(int argc, char **argv)
}
#endif
if (cmdLineParser.isSet(tdrOption))
framesUntilTdr = cmdLineParser.value(tdrOption).toInt();
if (cmdLineParser.isSet(swOption))
rhiFlags |= QRhi::PreferSoftwareRenderer;

View File

@ -0,0 +1,2 @@
qsb --glsl "100 es,120,150" --hlsl 50 --msl 12 -c imgui.vert -o imgui.vert.qsb
qsb --glsl "100 es,120,150" --hlsl 50 --msl 12 -c imgui.frag -o imgui.frag.qsb

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,29 @@
set(imgui_sources
${imgui_base}/imgui/imgui.cpp
${imgui_base}/imgui/imgui_draw.cpp
${imgui_base}/imgui/imgui_tables.cpp
${imgui_base}/imgui/imgui_widgets.cpp
${imgui_base}/imgui/imgui_demo.cpp
${imgui_base}/qrhiimgui.cpp
${imgui_base}/qrhiimgui_p.h
)
target_sources(${imgui_target} PRIVATE
${imgui_sources}
)
target_include_directories(${imgui_target} PRIVATE
${imgui_base}
${imgui_base}/imgui
)
qt6_add_resources(${imgui_target} "imgui_resources"
PREFIX
"/"
BASE
${imgui_base}
FILES
${imgui_base}/imgui.vert.qsb
${imgui_base}/imgui.frag.qsb
${imgui_base}/fonts/RobotoMono-Medium.ttf
)

View File

@ -0,0 +1,21 @@
#version 440
layout(location = 0) in vec2 v_texcoord;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float opacity;
};
layout(binding = 1) uniform sampler2D tex;
void main()
{
vec4 c = v_color * texture(tex, v_texcoord);
c.a *= opacity;
c.rgb *= c.a;
fragColor = c;
}

Binary file not shown.

View File

@ -0,0 +1,20 @@
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
layout(location = 2) in vec4 color;
layout(location = 0) out vec2 v_texcoord;
layout(location = 1) out vec4 v_color;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float opacity;
};
void main()
{
v_texcoord = texcoord;
v_color = color;
gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
}

Binary file not shown.

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2022 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,125 @@
//-----------------------------------------------------------------------------
// COMPILE-TIME OPTIONS FOR DEAR IMGUI
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
//-----------------------------------------------------------------------------
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec( dllexport )
//#define IMGUI_API __declspec( dllimport )
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88).
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Include imgui_user.h at the end of imgui.h as a convenience
//#define IMGUI_INCLUDE_IMGUI_USER_H
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
//#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
/*
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \
operator MyVec2() const { return MyVec2(x,y); }
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
#define ImDrawIdx unsigned int
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
//struct ImDrawList;
//struct ImDrawCmd;
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(),
// (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.)
// This adds a small runtime cost which is why it is not enabled by default.
//#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files.
/*
namespace ImGui
{
void MyFunction(const char* name, const MyMatrix44& v);
}
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,627 @@
// [DEAR IMGUI]
// This is a slightly modified version of stb_rect_pack.h 1.01.
// Grep for [DEAR IMGUI] to find the changes.
//
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= 'width'
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
//
// If you don't do either of the above things, widths will be quantized to multiples
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
//
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
// may run out of temporary storage and be unable to pack some rectangles.
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
// Optionally call this function after init but before doing any packing to
// change the handling of the out-of-temp-memory scenario, described above.
// If you call init again, this will be reset to the default (false).
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
// Optionally select which packing heuristic the library should use. Different
// heuristics will produce better/worse results for different data sets.
// If you call init again, this will be reset to the default.
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
//////////////////////////////////////////////////////////////////////////////
//
// the details of the following structures don't matter to you, but they must
// be visible so you can handle the memory allocations for them
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
};
#ifdef __cplusplus
}
#endif
#endif
//////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION SECTION
//
#ifdef STB_RECT_PACK_IMPLEMENTATION
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
// if it's ok to run out of memory, then don't bother aligning them;
// this gives better packing, but may fail due to OOM (even though
// the rectangles easily fit). @TODO a smarter approach would be to only
// quantize once we've hit OOM, then we could get rid of this parameter.
context->align = 1;
else {
// if it's not ok to run out of memory, then quantize the widths
// so that num_nodes is always enough nodes.
//
// I.e. num_nodes * align >= width
// align >= width / num_nodes
// align = ceil(width/num_nodes)
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
context->extra[1].y = (1<<30);
context->extra[1].next = NULL;
}
// find minimum y position if it starts at x1
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#if 0
// skip in case we're past the node
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
// add waste area
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
// align to multiple of c->align
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
// bottom left
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
// best-fit
if (y + height <= c->height) {
// can only use it if it first vertically
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
// if doing best-fit (BF), we also have to try aligning right edge to each node position
//
// e.g, if fitting
//
// ____________________
// |____________________|
//
// into
//
// | |
// | ____________|
// |____________|
//
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
//
// This makes BF take about 2x the time
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
// find first node that's admissible
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
// find the left position that matches this
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height <= c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
//STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
// find best position according to heuristic
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
// bail if:
// 1. it failed
// 2. the best node doesn't fit (we don't always check this)
// 3. we're out of memory
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
// on success, create new node
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
cur = *res.prev_link;
if (cur->x < res.x) {
// preserve the existing one, so start testing with the next one
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
// from here, traverse cur and free the nodes, until we get to one
// that shouldn't be freed
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
// move the current node to the free list
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
// stitch the list back in
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
}
// unsort
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,606 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "qrhiimgui_p.h"
#include <QtCore/qfile.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qevent.h>
#include <QtGui/qclipboard.h>
#include <QtGui/qimage.h>
#include "imgui.h"
// the imgui default
static_assert(sizeof(ImDrawVert) == 20);
// switched to uint in imconfig.h to avoid trouble with 4 byte offset alignment reqs
static_assert(sizeof(ImDrawIdx) == 4);
QT_BEGIN_NAMESPACE
static QShader getShader(const QString &name)
{
QFile f(name);
if (f.open(QIODevice::ReadOnly))
return QShader::fromSerialized(f.readAll());
return QShader();
}
QRhiImguiRenderer::~QRhiImguiRenderer()
{
releaseResources();
}
void QRhiImguiRenderer::releaseResources()
{
for (Texture &t : m_textures) {
delete t.tex;
delete t.srb;
}
m_textures.clear();
m_vbuf.reset();
m_ibuf.reset();
m_ubuf.reset();
m_ps.reset();
m_sampler.reset();
m_rhi = nullptr;
}
void QRhiImguiRenderer::prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuffer *cb, const QMatrix4x4 &mvp, float opacity)
{
if (!m_rhi) {
m_rhi = rhi;
} else if (m_rhi != rhi) {
releaseResources();
m_rhi = rhi;
}
if (!m_rhi || f.draw.isEmpty())
return;
m_rt = rt;
m_cb = cb;
if (!m_vbuf) {
m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, f.totalVbufSize));
m_vbuf->setName(QByteArrayLiteral("imgui vertex buffer"));
if (!m_vbuf->create())
return;
} else {
if (f.totalVbufSize > m_vbuf->size()) {
m_vbuf->setSize(f.totalVbufSize);
if (!m_vbuf->create())
return;
}
}
if (!m_ibuf) {
m_ibuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::IndexBuffer, f.totalIbufSize));
m_ibuf->setName(QByteArrayLiteral("imgui index buffer"));
if (!m_ibuf->create())
return;
} else {
if (f.totalIbufSize > m_ibuf->size()) {
m_ibuf->setSize(f.totalIbufSize);
if (!m_ibuf->create())
return;
}
}
if (!m_ubuf) {
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4));
m_ubuf->setName(QByteArrayLiteral("imgui uniform buffer"));
if (!m_ubuf->create())
return;
}
if (!m_sampler) {
m_sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::Repeat, QRhiSampler::Repeat));
m_sampler->setName(QByteArrayLiteral("imgui sampler"));
if (!m_sampler->create())
return;
}
if (m_textures.isEmpty()) {
Texture fontTex;
fontTex.image = sf.fontTextureData;
m_textures.append(fontTex);
} else if (!sf.fontTextureData.isNull()) {
Texture fontTex;
fontTex.image = sf.fontTextureData;
delete m_textures[0].tex;
delete m_textures[0].srb;
m_textures[0] = fontTex;
}
QVarLengthArray<int, 8> texturesNeedUpdate;
for (int i = 0; i < m_textures.count(); ++i) {
Texture &t(m_textures[i]);
if (!t.tex) {
t.tex = m_rhi->newTexture(QRhiTexture::RGBA8, t.image.size());
t.tex->setName(QByteArrayLiteral("imgui texture ") + QByteArray::number(i));
if (!t.tex->create())
return;
texturesNeedUpdate.append(i);
}
if (!t.srb) {
t.srb = m_rhi->newShaderResourceBindings();
t.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf.get()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, t.tex, m_sampler.get())
});
if (!t.srb->create())
return;
}
}
// If layer.enabled is toggled on the item or an ancestor, the render
// target is then suddenly different and may not be compatible.
if (m_ps && m_rt->renderPassDescriptor()->serializedFormat() != m_renderPassFormat)
m_ps.reset();
if (!m_ps) {
QShader vs = getShader(QLatin1String(":/imgui.vert.qsb"));
QShader fs = getShader(QLatin1String(":/imgui.frag.qsb"));
if (!vs.isValid() || !fs.isValid()) {
qWarning("Failed to load imgui shaders");
return;
}
m_ps.reset(m_rhi->newGraphicsPipeline());
QRhiGraphicsPipeline::TargetBlend blend;
blend.enable = true;
// Premultiplied alpha (matches imgui.frag). Would not be needed if we
// only cared about outputting to the window (the common case), but
// once going through a texture (Item layer, ShaderEffect) which is
// then sampled by Quick, the result wouldn't be correct otherwise.
blend.srcColor = QRhiGraphicsPipeline::One;
blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
blend.srcAlpha = QRhiGraphicsPipeline::One;
blend.dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
m_ps->setTargetBlends({ blend });
m_ps->setCullMode(QRhiGraphicsPipeline::None);
m_ps->setDepthTest(true);
m_ps->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
m_ps->setDepthWrite(false);
m_ps->setFlags(QRhiGraphicsPipeline::UsesScissor);
m_ps->setShaderStages({
{ QRhiShaderStage::Vertex, vs },
{ QRhiShaderStage::Fragment, fs }
});
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 4 * sizeof(float) + sizeof(quint32) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) },
{ 0, 2, QRhiVertexInputAttribute::UNormByte4, 4 * sizeof(float) }
});
m_ps->setVertexInputLayout(inputLayout);
m_ps->setShaderResourceBindings(m_textures[0].srb);
m_ps->setRenderPassDescriptor(m_rt->renderPassDescriptor());
m_renderPassFormat = m_rt->renderPassDescriptor()->serializedFormat();
if (!m_ps->create())
return;
}
QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch();
for (const CmdListBuffer &b : f.vbuf)
u->updateDynamicBuffer(m_vbuf.get(), b.offset, b.data.size(), b.data.constData());
for (const CmdListBuffer &b : f.ibuf)
u->updateDynamicBuffer(m_ibuf.get(), b.offset, b.data.size(), b.data.constData());
u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData());
u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &opacity);
for (int i = 0; i < texturesNeedUpdate.count(); ++i) {
Texture &t(m_textures[texturesNeedUpdate[i]]);
u->uploadTexture(t.tex, t.image);
t.image = QImage();
}
m_cb->resourceUpdate(u);
}
void QRhiImguiRenderer::render()
{
if (!m_rhi || f.draw.isEmpty() || !m_ps)
return;
m_cb->setGraphicsPipeline(m_ps.get());
const QSize viewportSize = m_rt->pixelSize();
bool needsViewport = true;
for (const DrawCmd &c : f.draw) {
QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), f.vbuf[c.cmdListBufferIdx].offset);
if (needsViewport) {
needsViewport = false;
m_cb->setViewport({ 0, 0, float(viewportSize.width()), float(viewportSize.height()) });
}
const float sx1 = c.clipRect.x() + c.itemPixelOffset.x();
const float sy1 = c.clipRect.y() + c.itemPixelOffset.y();
const float sx2 = c.clipRect.z() + c.itemPixelOffset.x();
const float sy2 = c.clipRect.w() + c.itemPixelOffset.y();
QPoint scissorPos = QPointF(sx1, viewportSize.height() - sy2).toPoint();
QSize scissorSize = QSizeF(sx2 - sx1, sy2 - sy1).toSize();
scissorPos.setX(qMax(0, scissorPos.x()));
scissorPos.setY(qMax(0, scissorPos.y()));
scissorSize.setWidth(qMin(viewportSize.width(), scissorSize.width()));
scissorSize.setHeight(qMin(viewportSize.height(), scissorSize.height()));
m_cb->setScissor({ scissorPos.x(), scissorPos.y(), scissorSize.width(), scissorSize.height() });
m_cb->setShaderResources(m_textures[c.textureIndex].srb);
m_cb->setVertexInput(0, 1, &vbufBinding, m_ibuf.get(), c.indexOffset, QRhiCommandBuffer::IndexUInt32);
m_cb->drawIndexed(c.elemCount);
}
}
static const char *getClipboardText(void *)
{
static QByteArray contents;
contents = QGuiApplication::clipboard()->text().toUtf8();
return contents.constData();
}
static void setClipboardText(void *, const char *text)
{
QGuiApplication::clipboard()->setText(QString::fromUtf8(text));
}
QRhiImgui::QRhiImgui()
{
ImGui::CreateContext();
rebuildFontAtlas();
ImGuiIO &io(ImGui::GetIO());
io.GetClipboardTextFn = getClipboardText;
io.SetClipboardTextFn = setClipboardText;
}
QRhiImgui::~QRhiImgui()
{
ImGui::DestroyContext();
}
void QRhiImgui::rebuildFontAtlas()
{
unsigned char *pixels;
int w, h;
ImGuiIO &io(ImGui::GetIO());
io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h);
const QImage wrapperImg(const_cast<const uchar *>(pixels), w, h, QImage::Format_RGBA8888);
sf.fontTextureData = wrapperImg.copy();
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(quintptr(0)));
}
void QRhiImgui::nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPointF &logicalOffset, FrameFunc frameFunc)
{
ImGuiIO &io(ImGui::GetIO());
const QPointF itemPixelOffset = logicalOffset * dpr;
f.outputPixelSize = (logicalOutputSize * dpr).toSize();
io.DisplaySize.x = logicalOutputSize.width();
io.DisplaySize.y = logicalOutputSize.height();
io.DisplayFramebufferScale = ImVec2(dpr, dpr);
ImGui::NewFrame();
if (frameFunc)
frameFunc();
ImGui::Render();
ImDrawData *draw = ImGui::GetDrawData();
draw->ScaleClipRects(ImVec2(dpr, dpr));
f.vbuf.resize(draw->CmdListsCount);
f.ibuf.resize(draw->CmdListsCount);
f.totalVbufSize = 0;
f.totalIbufSize = 0;
for (int n = 0; n < draw->CmdListsCount; ++n) {
const ImDrawList *cmdList = draw->CmdLists[n];
const int vbufSize = cmdList->VtxBuffer.Size * sizeof(ImDrawVert);
f.vbuf[n].offset = f.totalVbufSize;
f.totalVbufSize += vbufSize;
const int ibufSize = cmdList->IdxBuffer.Size * sizeof(ImDrawIdx);
f.ibuf[n].offset = f.totalIbufSize;
f.totalIbufSize += ibufSize;
}
f.draw.clear();
for (int n = 0; n < draw->CmdListsCount; ++n) {
const ImDrawList *cmdList = draw->CmdLists[n];
f.vbuf[n].data = QByteArray(reinterpret_cast<const char *>(cmdList->VtxBuffer.Data),
cmdList->VtxBuffer.Size * sizeof(ImDrawVert));
f.ibuf[n].data = QByteArray(reinterpret_cast<const char *>(cmdList->IdxBuffer.Data),
cmdList->IdxBuffer.Size * sizeof(ImDrawIdx));
const ImDrawIdx *indexBufOffset = nullptr;
for (int i = 0; i < cmdList->CmdBuffer.Size; ++i) {
const ImDrawCmd *cmd = &cmdList->CmdBuffer[i];
const quint32 indexOffset = f.ibuf[n].offset + quintptr(indexBufOffset);
if (!cmd->UserCallback) {
QRhiImguiRenderer::DrawCmd dc;
dc.cmdListBufferIdx = n;
dc.textureIndex = int(reinterpret_cast<qintptr>(cmd->TextureId));
dc.indexOffset = indexOffset;
dc.elemCount = cmd->ElemCount;
dc.itemPixelOffset = itemPixelOffset;
dc.clipRect = QVector4D(cmd->ClipRect.x, cmd->ClipRect.y, cmd->ClipRect.z, cmd->ClipRect.w);
f.draw.append(dc);
} else {
cmd->UserCallback(cmdList, cmd);
}
indexBufOffset += cmd->ElemCount;
}
}
}
void QRhiImgui::syncRenderer(QRhiImguiRenderer *renderer)
{
renderer->sf = sf;
sf.fontTextureData = QImage();
renderer->f = std::move(f);
}
static void updateKeyboardModifiers(Qt::KeyboardModifiers modifiers)
{
ImGuiIO &io(ImGui::GetIO());
io.AddKeyEvent(ImGuiKey_ModCtrl, modifiers.testFlag(Qt::ControlModifier));
io.AddKeyEvent(ImGuiKey_ModShift, modifiers.testFlag(Qt::ShiftModifier));
io.AddKeyEvent(ImGuiKey_ModAlt, modifiers.testFlag(Qt::AltModifier));
io.AddKeyEvent(ImGuiKey_ModSuper, modifiers.testFlag(Qt::MetaModifier));
}
static ImGuiKey mapKey(int k)
{
switch (k) {
case Qt::Key_Space:
return ImGuiKey_Space;
case Qt::Key_Apostrophe:
return ImGuiKey_Apostrophe;
case Qt::Key_Comma:
return ImGuiKey_Comma;
case Qt::Key_Minus:
return ImGuiKey_Minus;
case Qt::Key_Period:
return ImGuiKey_Period;
case Qt::Key_Slash:
return ImGuiKey_Slash;
case Qt::Key_0:
return ImGuiKey_0;
case Qt::Key_1:
return ImGuiKey_1;
case Qt::Key_2:
return ImGuiKey_2;
case Qt::Key_3:
return ImGuiKey_3;
case Qt::Key_4:
return ImGuiKey_4;
case Qt::Key_5:
return ImGuiKey_5;
case Qt::Key_6:
return ImGuiKey_6;
case Qt::Key_7:
return ImGuiKey_8;
case Qt::Key_8:
return ImGuiKey_8;
case Qt::Key_9:
return ImGuiKey_9;
case Qt::Key_Semicolon:
return ImGuiKey_Semicolon;
case Qt::Key_Equal:
return ImGuiKey_Equal;
case Qt::Key_A:
return ImGuiKey_A;
case Qt::Key_B:
return ImGuiKey_B;
case Qt::Key_C:
return ImGuiKey_C;
case Qt::Key_D:
return ImGuiKey_D;
case Qt::Key_E:
return ImGuiKey_E;
case Qt::Key_F:
return ImGuiKey_F;
case Qt::Key_G:
return ImGuiKey_G;
case Qt::Key_H:
return ImGuiKey_H;
case Qt::Key_I:
return ImGuiKey_I;
case Qt::Key_J:
return ImGuiKey_J;
case Qt::Key_K:
return ImGuiKey_K;
case Qt::Key_L:
return ImGuiKey_L;
case Qt::Key_M:
return ImGuiKey_M;
case Qt::Key_N:
return ImGuiKey_N;
case Qt::Key_O:
return ImGuiKey_O;
case Qt::Key_P:
return ImGuiKey_P;
case Qt::Key_Q:
return ImGuiKey_Q;
case Qt::Key_R:
return ImGuiKey_R;
case Qt::Key_S:
return ImGuiKey_S;
case Qt::Key_T:
return ImGuiKey_T;
case Qt::Key_U:
return ImGuiKey_U;
case Qt::Key_V:
return ImGuiKey_V;
case Qt::Key_W:
return ImGuiKey_W;
case Qt::Key_X:
return ImGuiKey_X;
case Qt::Key_Y:
return ImGuiKey_Y;
case Qt::Key_Z:
return ImGuiKey_Z;
case Qt::Key_BracketLeft:
return ImGuiKey_LeftBracket;
case Qt::Key_Backslash:
return ImGuiKey_Backslash;
case Qt::Key_BracketRight:
return ImGuiKey_RightBracket;
case Qt::Key_QuoteLeft:
return ImGuiKey_GraveAccent;
case Qt::Key_Escape:
return ImGuiKey_Escape;
case Qt::Key_Tab:
return ImGuiKey_Tab;
case Qt::Key_Backspace:
return ImGuiKey_Backspace;
case Qt::Key_Return:
case Qt::Key_Enter:
return ImGuiKey_Enter;
case Qt::Key_Insert:
return ImGuiKey_Insert;
case Qt::Key_Delete:
return ImGuiKey_Delete;
case Qt::Key_Pause:
return ImGuiKey_Pause;
case Qt::Key_Print:
return ImGuiKey_PrintScreen;
case Qt::Key_Home:
return ImGuiKey_Home;
case Qt::Key_End:
return ImGuiKey_End;
case Qt::Key_Left:
return ImGuiKey_LeftArrow;
case Qt::Key_Up:
return ImGuiKey_UpArrow;
case Qt::Key_Right:
return ImGuiKey_RightArrow;
case Qt::Key_Down:
return ImGuiKey_DownArrow;
case Qt::Key_PageUp:
return ImGuiKey_PageUp;
case Qt::Key_PageDown:
return ImGuiKey_PageDown;
case Qt::Key_Shift:
return ImGuiKey_LeftShift;
case Qt::Key_Control:
return ImGuiKey_LeftCtrl;
case Qt::Key_Meta:
return ImGuiKey_LeftSuper;
case Qt::Key_Alt:
return ImGuiKey_LeftAlt;
case Qt::Key_CapsLock:
return ImGuiKey_CapsLock;
case Qt::Key_NumLock:
return ImGuiKey_NumLock;
case Qt::Key_ScrollLock:
return ImGuiKey_ScrollLock;
case Qt::Key_F1:
return ImGuiKey_F1;
case Qt::Key_F2:
return ImGuiKey_F2;
case Qt::Key_F3:
return ImGuiKey_F3;
case Qt::Key_F4:
return ImGuiKey_F4;
case Qt::Key_F5:
return ImGuiKey_F5;
case Qt::Key_F6:
return ImGuiKey_F6;
case Qt::Key_F7:
return ImGuiKey_F7;
case Qt::Key_F8:
return ImGuiKey_F8;
case Qt::Key_F9:
return ImGuiKey_F9;
case Qt::Key_F10:
return ImGuiKey_F10;
case Qt::Key_F11:
return ImGuiKey_F11;
case Qt::Key_F12:
return ImGuiKey_F12;
default:
break;
}
return ImGuiKey_None;
}
bool QRhiImgui::processEvent(QEvent *event)
{
ImGuiIO &io(ImGui::GetIO());
switch (event->type()) {
case QEvent::MouseButtonPress:
{
QMouseEvent *me = static_cast<QMouseEvent *>(event);
updateKeyboardModifiers(me->modifiers());
Qt::MouseButtons buttons = me->buttons();
if (buttons.testFlag(Qt::LeftButton) && !pressedMouseButtons.testFlag(Qt::LeftButton))
io.AddMouseButtonEvent(0, true);
if (buttons.testFlag(Qt::RightButton) && !pressedMouseButtons.testFlag(Qt::RightButton))
io.AddMouseButtonEvent(1, true);
if (buttons.testFlag(Qt::MiddleButton) && !pressedMouseButtons.testFlag(Qt::MiddleButton))
io.AddMouseButtonEvent(2, true);
pressedMouseButtons = buttons;
}
return true;
case QEvent::MouseButtonRelease:
{
QMouseEvent *me = static_cast<QMouseEvent *>(event);
Qt::MouseButtons buttons = me->buttons();
if (!buttons.testFlag(Qt::LeftButton) && pressedMouseButtons.testFlag(Qt::LeftButton))
io.AddMouseButtonEvent(0, false);
if (!buttons.testFlag(Qt::RightButton) && pressedMouseButtons.testFlag(Qt::RightButton))
io.AddMouseButtonEvent(1, false);
if (!buttons.testFlag(Qt::MiddleButton) && pressedMouseButtons.testFlag(Qt::MiddleButton))
io.AddMouseButtonEvent(2, false);
pressedMouseButtons = buttons;
}
return true;
case QEvent::MouseMove:
{
QMouseEvent *me = static_cast<QMouseEvent *>(event);
const QPointF pos = me->position();
io.AddMousePosEvent(pos.x(), pos.y());
}
return true;
case QEvent::Wheel:
{
QWheelEvent *we = static_cast<QWheelEvent *>(event);
QPointF wheel(we->angleDelta().x() / 120.0f, we->angleDelta().y() / 120.0f);
io.AddMouseWheelEvent(wheel.x(), wheel.y());
}
return true;
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
const bool down = event->type() == QEvent::KeyPress;
updateKeyboardModifiers(ke->modifiers());
io.AddKeyEvent(mapKey(ke->key()), down);
if (down && !ke->text().isEmpty()) {
const QByteArray text = ke->text().toUtf8();
io.AddInputCharactersUTF8(text.constData());
}
}
return true;
default:
break;
}
return false;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,93 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef QRHIIMGUI_P_H
#define QRHIIMGUI_P_H
#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
class QEvent;
class QRhiImguiRenderer
{
public:
~QRhiImguiRenderer();
struct CmdListBuffer {
quint32 offset;
QByteArray data;
};
struct DrawCmd {
int cmdListBufferIdx;
int textureIndex;
quint32 indexOffset;
quint32 elemCount;
QPointF itemPixelOffset;
QVector4D clipRect;
};
struct StaticRenderData {
QImage fontTextureData;
};
struct FrameRenderData {
quint32 totalVbufSize = 0;
quint32 totalIbufSize = 0;
QVarLengthArray<CmdListBuffer, 4> vbuf;
QVarLengthArray<CmdListBuffer, 4> ibuf;
QVarLengthArray<DrawCmd, 4> draw;
QSize outputPixelSize;
};
StaticRenderData sf;
FrameRenderData f;
void prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuffer *cb, const QMatrix4x4 &mvp, float opacity);
void render();
void releaseResources();
private:
QRhi *m_rhi = nullptr;
QRhiRenderTarget *m_rt = nullptr;
QRhiCommandBuffer *m_cb = nullptr;
std::unique_ptr<QRhiBuffer> m_vbuf;
std::unique_ptr<QRhiBuffer> m_ibuf;
std::unique_ptr<QRhiBuffer> m_ubuf;
std::unique_ptr<QRhiGraphicsPipeline> m_ps;
QVector<quint32> m_renderPassFormat;
std::unique_ptr<QRhiSampler> m_sampler;
struct Texture {
QImage image;
QRhiTexture *tex = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
};
QVector<Texture> m_textures;
};
class QRhiImgui
{
public:
QRhiImgui();
~QRhiImgui();
using FrameFunc = std::function<void()>;
void nextFrame(const QSizeF &logicalOutputSize, float dpr, const QPointF &logicalOffset, FrameFunc frameFunc);
void syncRenderer(QRhiImguiRenderer *renderer);
bool processEvent(QEvent *e);
void rebuildFontAtlas();
private:
QRhiImguiRenderer::StaticRenderData sf;
QRhiImguiRenderer::FrameRenderData f;
Qt::MouseButtons pressedMouseButtons;
};
QT_END_NAMESPACE
#endif

View File

@ -5,7 +5,7 @@
#include <QPlatformSurfaceEvent>
#include <QTimer>
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
#include "../shared/cube.h"
Window::Window()
@ -55,7 +55,7 @@ bool Window::event(QEvent *e)
void Window::init()
{
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers | QRhi::EnableProfiling;
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
QRhiGles2InitParams params;

View File

@ -5,8 +5,8 @@
#define WINDOW_H
#include <QWindow>
#include <QtGui/private/qrhigles2_p.h>
#include <QOffscreenSurface>
#include <rhi/qrhi.h>
class Window : public QWindow
{

View File

@ -43,7 +43,9 @@ void Window::customInit()
if (!m_r->isFeatureSupported(QRhi::TextureArrays))
qFatal("Texture array objects are not supported by this backend");
d.texArr = m_r->newTextureArray(QRhiTexture::RGBA8, ARRAY_SIZE, QSize(512, 512));
d.texArr = m_r->newTextureArray(QRhiTexture::RGBA8, ARRAY_SIZE, QSize(512, 512), 1,
// mipmaps will be generated, to exercise that too
QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
d.releasePool << d.texArr;
d.texArr->create();
@ -59,7 +61,9 @@ void Window::customInit()
img.fill(Qt::yellow);
d.initialUpdates->uploadTexture(d.texArr, QRhiTextureUploadDescription(QRhiTextureUploadEntry(3, 0, QRhiTextureSubresourceUploadDescription(img))));
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
d.initialUpdates->generateMips(d.texArr);
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::Linear,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.releasePool << d.sampler;
d.sampler->create();

View File

@ -3,7 +3,7 @@
#include "quadrenderer.h"
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
// Renders a quad using indexed drawing. No QRhiGraphicsPipeline is created, it
// expects to reuse the one created by TriangleRenderer. A separate

View File

@ -4,7 +4,7 @@
#ifndef QUADRENDERER_H
#define QUADRENDERER_H
#include <QtGui/private/qrhi_p.h>
#include <rhi/qrhi.h>
class QuadRenderer
{

View File

@ -3,7 +3,7 @@
#include "texturedcuberenderer.h"
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
#include "../shared/cube.h"

View File

@ -4,7 +4,7 @@
#ifndef TEXTUREDCUBERENDERER_H
#define TEXTUREDCUBERENDERER_H
#include <QtGui/private/qrhi_p.h>
#include <rhi/qrhi.h>
class TexturedCubeRenderer
{

View File

@ -3,7 +3,7 @@
#include "triangleoncuberenderer.h"
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
// toggle to test the preserved content (no clear) path
const bool IMAGE_UNDER_OFFSCREEN_RENDERING = false;

View File

@ -3,7 +3,7 @@
#include "trianglerenderer.h"
#include <QFile>
#include <QtGui/private/qshader_p.h>
#include <rhi/qshader.h>
//#define VBUF_IS_DYNAMIC

View File

@ -4,7 +4,7 @@
#ifndef TRIANGLERENDERER_H
#define TRIANGLERENDERER_H
#include <QtGui/private/qrhi_p.h>
#include <rhi/qrhi.h>
class TriangleRenderer
{

View File

@ -35,7 +35,6 @@ struct {
QSize lastOutputSize;
int frameCount = 0;
QFile profOut;
QVarLengthArray<float, 64> gpuFrameTimes;
QElapsedTimer gpuFrameTimePrintTimer;
} d;
@ -136,20 +135,8 @@ void Window::customInit()
// With Vulkan at least we should see some details from the memory allocator.
qDebug() << m_r->statistics();
// Every two seconds try printing an average of the gpu frame times.
// Every two seconds try printing last known gpu frame time.
d.gpuFrameTimePrintTimer.start();
m_r->addGpuFrameTimeCallback([](float elapsedMs) {
d.gpuFrameTimes.append(elapsedMs);
if (d.gpuFrameTimePrintTimer.elapsed() > 2000) {
float at = 0.0f;
for (float t : d.gpuFrameTimes)
at += t;
at /= d.gpuFrameTimes.count();
qDebug() << "Average GPU frame time" << at;
d.gpuFrameTimes.clear();
d.gpuFrameTimePrintTimer.restart();
}
});
}
void Window::customRelease()
@ -170,6 +157,11 @@ void Window::customRender()
const QSize outputSize = m_sc->currentPixelSize();
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
if (d.gpuFrameTimePrintTimer.elapsed() > 2000) {
qDebug() << "Last completed GPU frame time" << cb->lastCompletedGpuTime() << "seconds";
d.gpuFrameTimePrintTimer.restart();
}
if (outputSize != d.lastOutputSize) {
d.triRenderer.resize(outputSize);
if (!d.triangleOnly) {

View File

@ -116,7 +116,7 @@
QPlainTextEdit gives the possibility to have more than one
selection at the same time. we can set the character format
(QTextCharFormat) of these selections. We clear the cursors
selection before setting the new new
selection before setting the new
QPlainTextEdit::ExtraSelection, else several lines would get
highlighted when the user selects multiple lines with the mouse.
\omit ask someone how this works \endomit

View File

@ -320,8 +320,8 @@ void FilesTest::selectOneFileWithFileDialog()
QWASMSUCCESS();
});
QWasmLocalFileAccess::openFile(
{QStringLiteral("*")}, fileSelectedCallback->get(), acceptFileCallback->get(), fileDataReadyCallback->get());
QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
fileDataReadyCallback->get());
}
void FilesTest::selectMultipleFilesWithFileDialog()
@ -377,9 +377,9 @@ void FilesTest::selectMultipleFilesWithFileDialog()
}
});
QWasmLocalFileAccess::openFiles(
{QStringLiteral("*")}, QWasmLocalFileAccess::FileSelectMode::MultipleFiles,
fileSelectedCallback->get(), acceptFileCallback->get(), fileDataReadyCallback->get());
QWasmLocalFileAccess::openFiles("*", QWasmLocalFileAccess::FileSelectMode::MultipleFiles,
fileSelectedCallback->get(), acceptFileCallback->get(),
fileDataReadyCallback->get());
}
void FilesTest::cancelFileDialog()
@ -398,8 +398,8 @@ void FilesTest::cancelFileDialog()
auto* acceptFileCallback = Own(new MockCallback<char*, uint64_t, const std::string&>());
auto* fileDataReadyCallback = Own(new MockCallback<void>());
QWasmLocalFileAccess::openFile(
{QStringLiteral("*")}, fileSelectedCallback->get(), acceptFileCallback->get(), fileDataReadyCallback->get());
QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
fileDataReadyCallback->get());
}
void FilesTest::rejectFile()
@ -430,8 +430,8 @@ void FilesTest::rejectFile()
return nullptr;
});
QWasmLocalFileAccess::openFile(
{QStringLiteral("*")}, fileSelectedCallback->get(), acceptFileCallback->get(), fileDataReadyCallback->get());
QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
fileDataReadyCallback->get());
}
void FilesTest::saveFileWithFileDialog()

View File

@ -7,7 +7,7 @@
#include <QtGui/qguiapplication.h>
#include <QtGui/qoffscreensurface.h>
#include <QtGui/qpa/qwindowsysteminterface.h>
#include <QtGui/private/qrhigles2_p.h>
#include <QtGui/rhi/qrhi.h>
#include <qtwasmtestlib.h>
@ -66,7 +66,7 @@ void Window::keyPressEvent(QKeyEvent *)
void Window::init()
{
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers | QRhi::EnableProfiling;
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
QRhiGles2InitParams params;
@ -94,10 +94,8 @@ public:
QWasmCompositorTest() : m_window(val::global("window")), m_testSupport(val::object())
{
m_window.set("testSupport", m_testSupport);
m_testSupport.set("qtAddContainerElement",
emscripten::val::module_property("qtAddContainerElement"));
m_testSupport.set("qtRemoveContainerElement",
emscripten::val::module_property("qtRemoveContainerElement"));
m_testSupport.set("qtSetContainerElements",
emscripten::val::module_property("qtSetContainerElements"));
}
~QWasmCompositorTest() noexcept
@ -118,12 +116,12 @@ private:
});
m_cleanup.emplace_back([]() mutable {
EM_ASM({
testSupport.qtRemoveContainerElement(testSupport.screenElement);
testSupport.qtSetContainerElements([]);
testSupport.screenElement.parentElement.removeChild(testSupport.screenElement);
});
});
EM_ASM({ testSupport.qtAddContainerElement(testSupport.screenElement); });
EM_ASM({ testSupport.qtSetContainerElements([testSupport.screenElement]); });
}
template<class T>

View File

@ -0,0 +1,45 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(tst_qtloader_integration
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
Qt::GuiPrivate
Qt::Widgets
)
set_target_properties(tst_qtloader_integration PROPERTIES QT_WASM_EXTRA_EXPORTED_METHODS "ENV")
add_custom_command(
TARGET tst_qtloader_integration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/tst_qtloader_integration.html
${CMAKE_CURRENT_BINARY_DIR}/tst_qtloader_integration.html)
add_custom_command(
TARGET tst_qtloader_integration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/plugins/platforms/wasm/qtloader.js
${CMAKE_CURRENT_BINARY_DIR}/qtloader.js)
add_custom_command(
TARGET tst_qtloader_integration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../shared/testrunner.js
${CMAKE_CURRENT_BINARY_DIR}/testrunner.js)
add_custom_command(
TARGET tst_qtloader_integration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/test_body.js
${CMAKE_CURRENT_BINARY_DIR}/test_body.js)
add_custom_command(
TARGET tst_qtloader_integration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/preload.json
${CMAKE_CURRENT_BINARY_DIR}/preload.json)

View File

@ -0,0 +1,171 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets/QtWidgets>
#include <iostream>
#include <sstream>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <emscripten.h>
#include <QtGui/qpa/qplatformscreen.h>
namespace {
constexpr int ExitValueImmediateReturn = 42;
constexpr int ExitValueFromExitApp = 22;
std::string screenInformation()
{
auto screens = qGuiApp->screens();
std::ostringstream out;
out << "[";
const char *separator = "";
for (const auto &screen : screens) {
out << separator;
out << "[" << std::to_string(screen->geometry().x()) << ","
<< std::to_string(screen->geometry().y()) << ","
<< std::to_string(screen->geometry().width()) << ","
<< std::to_string(screen->geometry().height()) << "]";
separator = ",";
}
out << "]";
return out.str();
}
std::string logicalDpi()
{
auto screens = qGuiApp->screens();
std::ostringstream out;
out << "[";
const char *separator = "";
for (const auto &screen : screens) {
out << separator;
out << "[" << std::to_string(screen->handle()->logicalDpi().first) << ", "
<< std::to_string(screen->handle()->logicalDpi().second) << "]";
separator = ",";
}
out << "]";
return out.str();
}
std::string preloadedFiles()
{
QStringList files = QDir("/preload").entryList(QDir::Files);
std::ostringstream out;
out << "[";
const char *separator = "";
for (const auto &file : files) {
out << separator;
out << file.toStdString();
separator = ",";
}
out << "]";
return out.str();
}
void crash()
{
std::abort();
}
void exitApp()
{
emscripten_force_exit(ExitValueFromExitApp);
}
void produceOutput()
{
fprintf(stdout, "Sample output!\n");
}
std::string retrieveArguments()
{
auto arguments = QApplication::arguments();
std::ostringstream out;
out << "[";
const char *separator = "";
for (const auto &argument : arguments) {
out << separator;
out << "'" << argument.toStdString() << "'";
separator = ",";
}
out << "]";
return out.str();
}
std::string getEnvironmentVariable(std::string name) {
return QString::fromLatin1(qgetenv(name.c_str())).toStdString();
}
} // namespace
class AppWindow : public QObject
{
Q_OBJECT
public:
AppWindow() : m_layout(new QVBoxLayout(&m_ui))
{
addWidget<QLabel>("Qt Loader integration tests");
m_ui.setLayout(m_layout);
}
void show() { m_ui.show(); }
~AppWindow() = default;
private:
template<class T, class... Args>
T *addWidget(Args... args)
{
T *widget = new T(std::forward<Args>(args)..., &m_ui);
m_layout->addWidget(widget);
return widget;
}
QWidget m_ui;
QVBoxLayout *m_layout;
};
int main(int argc, char **argv)
{
QApplication application(argc, argv);
const auto arguments = application.arguments();
const bool exitImmediately =
std::find(arguments.begin(), arguments.end(), QStringLiteral("--exit-immediately"))
!= arguments.end();
if (exitImmediately)
emscripten_force_exit(ExitValueImmediateReturn);
const bool crashImmediately =
std::find(arguments.begin(), arguments.end(), QStringLiteral("--crash-immediately"))
!= arguments.end();
if (crashImmediately)
crash();
const bool noGui = std::find(arguments.begin(), arguments.end(), QStringLiteral("--no-gui"))
!= arguments.end();
if (!noGui) {
AppWindow window;
window.show();
return application.exec();
}
return application.exec();
}
EMSCRIPTEN_BINDINGS(qtLoaderIntegrationTest)
{
emscripten::constant("EXIT_VALUE_IMMEDIATE_RETURN", ExitValueImmediateReturn);
emscripten::constant("EXIT_VALUE_FROM_EXIT_APP", ExitValueFromExitApp);
emscripten::function("screenInformation", &screenInformation);
emscripten::function("logicalDpi", &logicalDpi);
emscripten::function("preloadedFiles", &preloadedFiles);
emscripten::function("crash", &crash);
emscripten::function("exitApp", &exitApp);
emscripten::function("produceOutput", &produceOutput);
emscripten::function("retrieveArguments", &retrieveArguments);
emscripten::function("getEnvironmentVariable", &getEnvironmentVariable);
}
#include "main.moc"

View File

@ -0,0 +1,10 @@
[
{
"source": "qtloader.js",
"destination": "/preload/qtloader.js"
},
{
"source": "$QTDIR/qtlogo.svg",
"destination": "/preload/qtlogo.svg"
}
]

View File

@ -0,0 +1,469 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDXLicenseIdentifier: LicenseRefQtCommercial OR GPL3.0only
import { Mock, assert, TestRunner } from './testrunner.js';
export class QtLoaderIntegrationTests
{
#testScreenContainers = []
async beforeEach()
{
this.#addScreenContainer('screen-container-0', { width: '200px', height: '300px' });
}
async afterEach()
{
this.#testScreenContainers.forEach(screenContainer =>
{
document.body.removeChild(screenContainer);
});
this.#testScreenContainers = [];
}
async missingConfig()
{
let caughtException;
try {
await qtLoad();
} catch (e) {
caughtException = e;
}
assert.isNotUndefined(caughtException);
assert.equal('config is required, expected an object', caughtException.message);
}
async missingQtSection()
{
let caughtException;
try {
await qtLoad({});
} catch (e) {
caughtException = e;
}
assert.isNotUndefined(caughtException);
assert.equal(
'config.qt is required, expected an object', caughtException.message);
}
async useDefaultOnMissingEntryFunction()
{
const instance = await qtLoad({ arguments: ['--no-gui'], qt: {}});
assert.isNotUndefined(instance);
}
async environmentVariables()
{
const instance = await qtLoad({
qt: {
environment: {
variable1: 'value1',
variable2: 'value2'
},
entryFunction: createQtAppInstance,
containerElements: [this.#testScreenContainers[0]]
}
});
assert.isTrue(instance.getEnvironmentVariable('variable1') === 'value1');
assert.isTrue(instance.getEnvironmentVariable('variable2') === 'value2');
}
async screenContainerManipulations()
{
// ... (do other things), then call addContainerElement() to add a new container/screen.
// This can happen either before or after load() is called - loader will route the
// call to instance when it's ready.
this.#addScreenContainer('appcontainer1', { width: '100px', height: '100px' })
const instance = await qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: this.#testScreenContainers
}
});
{
const screenInformation = this.#getScreenInformation(instance);
assert.equal(2, screenInformation.length);
assert.equal(200, screenInformation[0].width);
assert.equal(300, screenInformation[0].height);
assert.equal(100, screenInformation[1].width);
assert.equal(100, screenInformation[1].height);
}
this.#addScreenContainer('appcontainer2', { width: '234px', height: '99px' })
instance.qtSetContainerElements(this.#testScreenContainers);
{
const screenInformation = this.#getScreenInformation(instance);
assert.equal(3, screenInformation.length);
assert.equal(200, screenInformation[0].width);
assert.equal(300, screenInformation[0].height);
assert.equal(100, screenInformation[1].width);
assert.equal(100, screenInformation[1].height);
assert.equal(234, screenInformation[2].width);
assert.equal(99, screenInformation[2].height);
}
document.body.removeChild(this.#testScreenContainers.splice(2, 1)[0]);
instance.qtSetContainerElements(this.#testScreenContainers);
{
const screenInformation = this.#getScreenInformation(instance);
assert.equal(2, screenInformation.length);
assert.equal(200, screenInformation[0].width);
assert.equal(300, screenInformation[0].height);
assert.equal(100, screenInformation[1].width);
assert.equal(100, screenInformation[1].height);
}
}
async primaryScreenIsAlwaysFirst()
{
const instance = await qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: this.#testScreenContainers,
}
});
this.#addScreenContainer(
'appcontainer3', { width: '12px', height: '24px' },
container => this.#testScreenContainers.splice(0, 0, container));
this.#addScreenContainer(
'appcontainer4', { width: '34px', height: '68px' },
container => this.#testScreenContainers.splice(1, 0, container));
instance.qtSetContainerElements(this.#testScreenContainers);
{
const screenInformation = this.#getScreenInformation(instance);
assert.equal(3, screenInformation.length);
// The primary screen (at position 0) is always at 0
assert.equal(12, screenInformation[0].width);
assert.equal(24, screenInformation[0].height);
// Other screens are pushed at the back
assert.equal(200, screenInformation[1].width);
assert.equal(300, screenInformation[1].height);
assert.equal(34, screenInformation[2].width);
assert.equal(68, screenInformation[2].height);
}
this.#testScreenContainers.forEach(screenContainer =>
{
document.body.removeChild(screenContainer);
});
this.#testScreenContainers = [
this.#addScreenContainer('appcontainer5', { width: '11px', height: '12px' }),
this.#addScreenContainer('appcontainer6', { width: '13px', height: '14px' }),
];
instance.qtSetContainerElements(this.#testScreenContainers);
{
const screenInformation = this.#getScreenInformation(instance);
assert.equal(2, screenInformation.length);
assert.equal(11, screenInformation[0].width);
assert.equal(12, screenInformation[0].height);
assert.equal(13, screenInformation[1].width);
assert.equal(14, screenInformation[1].height);
}
}
async multipleInstances()
{
// Fetch/Compile the module once; reuse for each instance. This is also if the page wants to
// initiate the .wasm file download fetch as early as possible, before the browser has
// finished fetching and parsing testapp.js and qtloader.js
const module = WebAssembly.compileStreaming(fetch('tst_qtloader_integration.wasm'));
const instances = await Promise.all([1, 2, 3].map(i => qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: [this.#addScreenContainer(`screen-container-${i}`, {
width: `${i * 10}px`,
height: `${i * 10}px`,
})],
module,
}
})));
// Confirm the identity of instances by querying their screen widths and heights
{
const screenInformation = this.#getScreenInformation(instances[0]);
console.log();
assert.equal(1, screenInformation.length);
assert.equal(10, screenInformation[0].width);
assert.equal(10, screenInformation[0].height);
}
{
const screenInformation = this.#getScreenInformation(instances[1]);
assert.equal(1, screenInformation.length);
assert.equal(20, screenInformation[0].width);
assert.equal(20, screenInformation[0].height);
}
{
const screenInformation = this.#getScreenInformation(instances[2]);
assert.equal(1, screenInformation.length);
assert.equal(30, screenInformation[0].width);
assert.equal(30, screenInformation[0].height);
}
}
async consoleMode()
{
// 'Console mode' for autotesting type scenarios
let accumulatedStdout = '';
const instance = await qtLoad({
arguments: ['--no-gui'],
print: output =>
{
accumulatedStdout += output;
},
qt: {
entryFunction: createQtAppInstance,
}
});
this.#callTestInstanceApi(instance, 'produceOutput');
assert.equal('Sample output!', accumulatedStdout);
}
async modulePromiseProvided()
{
await qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: [this.#testScreenContainers[0]],
module: WebAssembly.compileStreaming(
fetch('tst_qtloader_integration.wasm'))
}
});
}
async moduleProvided()
{
await qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: [this.#testScreenContainers[0]],
module: await WebAssembly.compileStreaming(
fetch('tst_qtloader_integration.wasm'))
}
});
}
async arguments()
{
const instance = await qtLoad({
arguments: ['--no-gui', 'arg1', 'other', 'yetanotherarg'],
qt: {
entryFunction: createQtAppInstance,
}
});
const args = this.#callTestInstanceApi(instance, 'retrieveArguments');
assert.equal(5, args.length);
assert.isTrue('arg1' === args[2]);
assert.equal('other', args[3]);
assert.equal('yetanotherarg', args[4]);
}
async moduleProvided_exceptionThrownInFactory()
{
let caughtException;
try {
await qtLoad({
qt: {
entryFunction: createQtAppInstance,
containerElements: [this.#testScreenContainers[0]],
module: Promise.reject(new Error('Failed to load')),
}
});
} catch (e) {
caughtException = e;
}
assert.isTrue(caughtException !== undefined);
assert.equal('Failed to load', caughtException.message);
}
async abort()
{
const onExitMock = new Mock();
const instance = await qtLoad({
arguments: ['--no-gui'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
}
});
try {
instance.crash();
} catch { }
assert.equal(1, onExitMock.calls.length);
const exitStatus = onExitMock.calls[0][0];
assert.isTrue(exitStatus.crashed);
assert.isUndefined(exitStatus.code);
assert.isNotUndefined(exitStatus.text);
}
async abortImmediately()
{
const onExitMock = new Mock();
let caughtException;
try {
await qtLoad({
arguments: ['--no-gui', '--crash-immediately'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
}
});
} catch (e) {
caughtException = e;
}
assert.isUndefined(caughtException);
assert.equal(1, onExitMock.calls.length);
const exitStatus = onExitMock.calls[0][0];
assert.isTrue(exitStatus.crashed);
assert.isUndefined(exitStatus.code);
assert.isNotUndefined(exitStatus.text);
}
async userAbortCallbackCalled()
{
const onAbortMock = new Mock();
let instance = await qtLoad({
arguments: ['--no-gui'],
onAbort: onAbortMock,
qt: {
entryFunction: createQtAppInstance,
}
});
try {
instance.crash();
} catch (e) {
// emscripten throws an 'Aborted' error here, which we ignore for the sake of the test
}
assert.equal(1, onAbortMock.calls.length);
}
async exit()
{
const onExitMock = new Mock();
let instance = await qtLoad({
arguments: ['--no-gui'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
}
});
// The module is running. onExit should not have been called.
assert.equal(0, onExitMock.calls.length);
try {
instance.exitApp();
} catch (e) {
// emscripten throws a 'Runtime error: unreachable' error here. We ignore it for the
// sake of the test.
}
assert.equal(1, onExitMock.calls.length);
const exitStatus = onExitMock.calls[0][0];
assert.isFalse(exitStatus.crashed);
assert.equal(instance.EXIT_VALUE_FROM_EXIT_APP, exitStatus.code);
assert.isUndefined(exitStatus.text);
}
async exitImmediately()
{
const onExitMock = new Mock();
const instance = await qtLoad({
arguments: ['--no-gui', '--exit-immediately'],
qt: {
onExit: onExitMock,
entryFunction: createQtAppInstance,
}
});
assert.equal(1, onExitMock.calls.length);
const exitStatusFromOnExit = onExitMock.calls[0][0];
assert.isFalse(exitStatusFromOnExit.crashed);
assert.equal(instance.EXIT_VALUE_IMMEDIATE_RETURN, exitStatusFromOnExit.code);
assert.isUndefined(exitStatusFromOnExit.text);
}
async userQuitCallbackCalled()
{
const quitMock = new Mock();
let instance = await qtLoad({
arguments: ['--no-gui'],
quit: quitMock,
qt: {
entryFunction: createQtAppInstance,
}
});
try {
instance.exitApp();
} catch (e) {
// emscripten throws a 'Runtime error: unreachable' error here. We ignore it for the
// sake of the test.
}
assert.equal(1, quitMock.calls.length);
const [exitCode, exception] = quitMock.calls[0];
assert.equal(instance.EXIT_VALUE_FROM_EXIT_APP, exitCode);
assert.equal('ExitStatus', exception.name);
}
async preloadFiles()
{
const instance = await qtLoad({
arguments: ["--no-gui"],
qt: {
preload: ['preload.json'],
qtdir: '.',
}
});
const preloadedFiles = instance.preloadedFiles();
// Verify that preloaded file list matches files specified in preload.json
assert.equal("[qtloader.js,qtlogo.svg]", preloadedFiles);
}
#callTestInstanceApi(instance, apiName)
{
return eval(instance[apiName]());
}
#getScreenInformation(instance)
{
return this.#callTestInstanceApi(instance, 'screenInformation').map(elem => ({
x: elem[0],
y: elem[1],
width: elem[2],
height: elem[3],
}));
}
#addScreenContainer(id, style, inserter)
{
const container = (() =>
{
const container = document.createElement('div');
container.id = id;
container.style.width = style.width;
container.style.height = style.height;
document.body.appendChild(container);
return container;
})();
inserter ? inserter(container) : this.#testScreenContainers.push(container);
return container;
}
}
(async () =>
{
const runner = new TestRunner(new QtLoaderIntegrationTests(), {
timeoutSeconds: 10
});
await runner.runAll();
})();

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en-us">
<head>
<title>tst_qtloader_integration</title>
<script src='tst_qtloader_integration.js'></script>
<script src="qtloader.js" defer></script>
<script type="module" src="test_body.js" defer></script>
</head>
<body></body>
</html>

View File

@ -0,0 +1,36 @@
qt_internal_add_manual_test(qwasmwindow_harness
SOURCES
qwasmwindow_harness.cpp
LIBRARIES
Qt::Core
Qt::CorePrivate
Qt::GuiPrivate
)
add_custom_command(
TARGET qwasmwindow_harness POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/qwasmwindow_harness.html
${CMAKE_CURRENT_BINARY_DIR}/qwasmwindow_harness.html
)
add_custom_command(
TARGET qwasmwindow_harness POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/qwasmwindow.py
${CMAKE_CURRENT_BINARY_DIR}/qwasmwindow.py
)
add_custom_command(
TARGET qwasmwindow_harness POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/run.sh
${CMAKE_CURRENT_BINARY_DIR}/run.sh
)
add_custom_command(
TARGET qwasmwindow_harness POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../../../../util/wasm/qtwasmserver/qtwasmserver.py
${CMAKE_CURRENT_BINARY_DIR}/qtwasmserver.py
)

View File

@ -0,0 +1,445 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from selenium.webdriver import Chrome
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_actions import PointerActions
from selenium.webdriver.common.actions.interaction import POINTER_TOUCH
from selenium.webdriver.common.actions.pointer_input import PointerInput
from selenium.webdriver.common.by import By
from selenium.webdriver.support.expected_conditions import presence_of_element_located
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
import unittest
from enum import Enum, auto
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self._driver = Chrome()
self._driver.get(
'http://localhost:8001/qwasmwindow_harness.html')
self._test_sandbox_element = WebDriverWait(self._driver, 30).until(
presence_of_element_located((By.ID, 'test-sandbox'))
)
self.addTypeEqualityFunc(Rect, assert_rects_equal)
def test_window_resizing(self):
defaultWindowMinSize = 100
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=600)
window = Window(screen, x=100, y=100, width=200, height=200)
self.assertEqual(window.rect, Rect(x=100, y=100, width=200, height=200))
window.drag(Handle.TOP_LEFT, direction=UP(10) + LEFT(10))
self.assertEqual(window.rect, Rect(x=90, y=90, width=210, height=210))
window.drag(Handle.TOP, direction=DOWN(10) + LEFT(100))
self.assertEqual(window.rect, Rect(x=90, y=100, width=210, height=200))
window.drag(Handle.TOP_RIGHT, direction=UP(5) + LEFT(5))
self.assertEqual(window.rect, Rect(x=90, y=95, width=205, height=205))
window.drag(Handle.RIGHT, direction=DOWN(100) + RIGHT(5))
self.assertEqual(window.rect, Rect(x=90, y=95, width=210, height=205))
window.drag(Handle.BOTTOM_RIGHT, direction=UP(5) + LEFT(10))
self.assertEqual(window.rect, Rect(x=90, y=95, width=200, height=200))
window.drag(Handle.BOTTOM, direction=DOWN(20) + LEFT(100))
self.assertEqual(window.rect, Rect(x=90, y=95, width=200, height=220))
window.drag(Handle.BOTTOM_LEFT, direction=DOWN(10) + LEFT(10))
self.assertEqual(window.rect, Rect(x=80, y=95, width=210, height=230))
window.drag(Handle.LEFT, direction=DOWN(343) + LEFT(5))
self.assertEqual(window.rect, Rect(x=75, y=95, width=215, height=230))
window.drag(Handle.BOTTOM_RIGHT, direction=UP(150) + LEFT(150))
self.assertEqual(window.rect, Rect(x=75, y=95, width=defaultWindowMinSize, height=defaultWindowMinSize))
def test_cannot_resize_over_screen_top_edge(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=200, y=200, width=300, height=300)
window = Window(screen, x=300, y=300, width=100, height=100)
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
frame_rect_before_resize = window.frame_rect
window.drag(Handle.TOP, direction=UP(200))
self.assertEqual(window.rect.x, 300)
self.assertEqual(window.frame_rect.y, screen.rect.y)
self.assertEqual(window.rect.width, 100)
self.assertEqual(window.frame_rect.y + window.frame_rect.height,
frame_rect_before_resize.y + frame_rect_before_resize.height)
def test_window_move(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=200, y=200, width=300, height=300)
window = Window(screen, x=300, y=300, width=100, height=100)
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=UP(30))
self.assertEqual(window.rect, Rect(x=300, y=270, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=RIGHT(50))
self.assertEqual(window.rect, Rect(x=350, y=270, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=DOWN(30) + LEFT(70))
self.assertEqual(window.rect, Rect(x=280, y=300, width=100, height=100))
def test_screen_limits_window_moves(self):
screen = Screen(self._driver, ScreenPosition.RELATIVE,
x=200, y=200, width=300, height=300)
window = Window(screen, x=300, y=300, width=100, height=100)
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
self.assertEqual(window.frame_rect.x, screen.rect.x - window.frame_rect.width / 2)
def test_screen_in_scroll_container_limits_window_moves(self):
screen = Screen(self._driver, ScreenPosition.IN_SCROLL_CONTAINER,
x=200, y=2000, width=300, height=300,
container_width=500, container_height=7000)
screen.scroll_to()
window = Window(screen, x=300, y=2100, width=100, height=100)
self.assertEqual(window.rect, Rect(x=300, y=2100, width=100, height=100))
window.drag(Handle.TOP_WINDOW_BAR, direction=LEFT(300))
self.assertEqual(window.frame_rect.x, screen.rect.x - window.frame_rect.width / 2)
def test_maximize(self):
screen = Screen(self._driver, ScreenPosition.RELATIVE,
x=200, y=200, width=300, height=300)
window = Window(screen, x=300, y=300, width=100, height=100, title='Maximize')
self.assertEqual(window.rect, Rect(x=300, y=300, width=100, height=100))
window.maximize()
self.assertEqual(window.frame_rect, Rect(x=200, y=200, width=300, height=300))
def test_multitouch_window_move(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=800, height=800)
windows = [Window(screen, x=50, y=50, width=100, height=100, title='First'),
Window(screen, x=400, y=400, width=100, height=100, title='Second'),
Window(screen, x=50, y=400, width=100, height=100, title='Third')]
self.assertEqual(windows[0].rect, Rect(x=50, y=50, width=100, height=100))
self.assertEqual(windows[1].rect, Rect(x=400, y=400, width=100, height=100))
self.assertEqual(windows[2].rect, Rect(x=50, y=400, width=100, height=100))
actions = [TouchDragAction(origin=windows[0].at(Handle.TOP_WINDOW_BAR), direction=DOWN(20) + RIGHT(20)),
TouchDragAction(origin=windows[1].at(Handle.TOP_WINDOW_BAR), direction=DOWN(20) + LEFT(20)),
TouchDragAction(origin=windows[2].at(Handle.TOP_WINDOW_BAR), direction=UP(20) + RIGHT(20))]
perform_touch_drag_actions(actions)
self.assertEqual(windows[0].rect, Rect(x=70, y=70, width=100, height=100))
self.assertEqual(windows[1].rect, Rect(x=380, y=420, width=100, height=100))
self.assertEqual(windows[2].rect, Rect(x=70, y=380, width=100, height=100))
def test_multitouch_window_resize(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=800, height=800)
windows = [Window(screen, x=50, y=50, width=150, height=150, title='First'),
Window(screen, x=400, y=400, width=150, height=150, title='Second'),
Window(screen, x=50, y=400, width=150, height=150, title='Third')]
self.assertEqual(windows[0].rect, Rect(x=50, y=50, width=150, height=150))
self.assertEqual(windows[1].rect, Rect(x=400, y=400, width=150, height=150))
self.assertEqual(windows[2].rect, Rect(x=50, y=400, width=150, height=150))
actions = [TouchDragAction(origin=windows[0].at(Handle.TOP_LEFT), direction=DOWN(20) + RIGHT(20)),
TouchDragAction(origin=windows[1].at(Handle.TOP), direction=DOWN(20) + LEFT(20)),
TouchDragAction(origin=windows[2].at(Handle.BOTTOM_RIGHT), direction=UP(20) + RIGHT(20))]
perform_touch_drag_actions(actions)
self.assertEqual(windows[0].rect, Rect(x=70, y=70, width=130, height=130))
self.assertEqual(windows[1].rect, Rect(x=400, y=420, width=150, height=130))
self.assertEqual(windows[2].rect, Rect(x=50, y=400, width=170, height=130))
def tearDown(self):
self._driver.quit()
class ScreenPosition(Enum):
FIXED = auto()
RELATIVE = auto()
IN_SCROLL_CONTAINER = auto()
class Screen:
def __init__(self, driver, positioning, x, y, width, height, container_width=0, container_height=0):
self.driver = driver
self.x = x
self.y = y
self.width = width
self.height = height
if positioning == ScreenPosition.FIXED:
command = f'initializeScreenWithFixedPosition({self.x}, {self.y}, {self.width}, {self.height})'
elif positioning == ScreenPosition.RELATIVE:
command = f'initializeScreenWithRelativePosition({self.x}, {self.y}, {self.width}, {self.height})'
elif positioning == ScreenPosition.IN_SCROLL_CONTAINER:
command = f'initializeScreenInScrollContainer({container_width}, {container_height}, {self.x}, {self.y}, {self.width}, {self.height})'
self.element = self.driver.execute_script(
f'''
return testSupport.{command};
'''
)
if positioning == ScreenPosition.IN_SCROLL_CONTAINER:
self.element = self.element[1]
screen_information = call_instance_function(
self.driver, 'screenInformation')
if len(screen_information) != 1:
raise AssertionError('Expecting exactly one screen_information!')
self.screen_info = screen_information[0]
@property
def rect(self):
self.screen_info = call_instance_function(
self.driver, 'screenInformation')[0]
geo = self.screen_info['geometry']
return Rect(geo['x'], geo['y'], geo['width'], geo['height'])
def scroll_to(self):
ActionChains(self.driver).scroll_to_element(self.element).perform()
class Window:
def __init__(self, screen, x, y, width, height, title='title'):
self.driver = screen.driver
self.title = title
self.driver.execute_script(
f'''
instance.createWindow({x}, {y}, {width}, {height}, '{screen.screen_info["name"]}', '{title}');
'''
)
self._window_id = self.__window_information()['id']
self.element = screen.element.shadow_root.find_element(
By.CSS_SELECTOR, f'#qt-window-{self._window_id}')
def __window_information(self):
information = call_instance_function(self.driver, 'windowInformation')
return next(filter(lambda e: e['title'] == self.title, information))
@property
def rect(self):
geo = self.__window_information()["geometry"]
return Rect(geo['x'], geo['y'], geo['width'], geo['height'])
@property
def frame_rect(self):
geo = self.__window_information()["frameGeometry"]
return Rect(geo['x'], geo['y'], geo['width'], geo['height'])
def drag(self, handle, direction):
ActionChains(self.driver) \
.move_to_element_with_offset(self.element, *self.at(handle)['offset']) \
.click_and_hold() \
.move_by_offset(*translate_direction_to_offset(direction)) \
.release().perform()
def maximize(self):
maximize_button = self.element.find_element(
By.CSS_SELECTOR, f'.title-bar :nth-child(6)')
maximize_button.click()
def at(self, handle):
""" Returns (window, offset) for given handle on window"""
width = self.frame_rect.width
height = self.frame_rect.height
if handle == Handle.TOP_LEFT:
offset = (-width/2, -height/2)
elif handle == Handle.TOP:
offset = (0, -height/2)
elif handle == Handle.TOP_RIGHT:
offset = (width/2, -height/2)
elif handle == Handle.LEFT:
offset = (-width/2, 0)
elif handle == Handle.RIGHT:
offset = (width/2, 0)
elif handle == Handle.BOTTOM_LEFT:
offset = (-width/2, height/2)
elif handle == Handle.BOTTOM:
offset = (0, height/2)
elif handle == Handle.BOTTOM_RIGHT:
offset = (width/2, height/2)
elif handle == Handle.TOP_WINDOW_BAR:
frame_top = self.frame_rect.y
client_area_top = self.rect.y
top_frame_bar_width = client_area_top - frame_top
offset = (0, -height/2 + top_frame_bar_width/2)
return {'window': self, 'offset': offset}
class TouchDragAction:
def __init__(self, origin, direction):
self.origin = origin
self.direction = direction
self.step = 2
def perform_touch_drag_actions(actions):
driver = actions[0].origin['window'].driver
touch_action_builder = ActionBuilder(driver)
pointers = [PointerActions(source=touch_action_builder.add_pointer_input(
POINTER_TOUCH, f'touch_input_{i}')) for i in range(len(actions))]
for action, pointer in zip(actions, pointers):
pointer.move_to(
action.origin['window'].element, *action.origin['offset'])
pointer.pointer_down(width=10, height=10, pressure=1)
moves = [translate_direction_to_offset(a.direction) for a in actions]
def movement_finished():
for move in moves:
if move != (0, 0):
return False
return True
def sign(num):
if num > 0:
return 1
elif num < 0:
return -1
return 0
while not movement_finished():
for i in range(len(actions)):
pointer = pointers[i]
move = moves[i]
step = actions[i].step
current_move = (
min(abs(move[0]), step) * sign(move[0]), min(abs(move[1]), step) * sign(move[1]))
moves[i] = (move[0] - current_move[0], move[1] - current_move[1])
pointer.move_by(current_move[0],
current_move[1], width=10, height=10)
for pointer in pointers:
pointer.pointer_up()
touch_action_builder.perform()
class TouchDragAction:
def __init__(self, origin, direction):
self.origin = origin
self.direction = direction
self.step = 2
def perform_touch_drag_actions(actions):
driver = actions[0].origin['window'].driver
touch_action_builder = ActionBuilder(driver)
pointers = [PointerActions(source=touch_action_builder.add_pointer_input(
POINTER_TOUCH, f'touch_input_{i}')) for i in range(len(actions))]
for action, pointer in zip(actions, pointers):
pointer.move_to(
action.origin['window'].element, *action.origin['offset'])
pointer.pointer_down(width=10, height=10, pressure=1)
moves = [translate_direction_to_offset(a.direction) for a in actions]
def movement_finished():
for move in moves:
if move != (0, 0):
return False
return True
def sign(num):
if num > 0:
return 1
elif num < 0:
return -1
return 0
while not movement_finished():
for i in range(len(actions)):
pointer = pointers[i]
move = moves[i]
step = actions[i].step
current_move = (
min(abs(move[0]), step) * sign(move[0]), min(abs(move[1]), step) * sign(move[1]))
moves[i] = (move[0] - current_move[0], move[1] - current_move[1])
pointer.move_by(current_move[0],
current_move[1], width=10, height=10)
for pointer in pointers:
pointer.pointer_up()
touch_action_builder.perform()
def translate_direction_to_offset(direction):
return (direction.val[1] - direction.val[3], direction.val[2] - direction.val[0])
def call_instance_function(driver, name):
return driver.execute_script(
f'''let result;
window.{name}Callback = data => result = data;
instance.{name}();
return eval(result);''')
class Direction:
def __init__(self):
self.val = (0, 0, 0, 0)
def __init__(self, north, east, south, west):
self.val = (north, east, south, west)
def __add__(self, other):
return Direction(self.val[0] + other.val[0],
self.val[1] + other.val[1],
self.val[2] + other.val[2],
self.val[3] + other.val[3])
class UP(Direction):
def __init__(self, step=1):
self.val = (step, 0, 0, 0)
class RIGHT(Direction):
def __init__(self, step=1):
self.val = (0, step, 0, 0)
class DOWN(Direction):
def __init__(self, step=1):
self.val = (0, 0, step, 0)
class LEFT(Direction):
def __init__(self, step=1):
self.val = (0, 0, 0, step)
class Handle(Enum):
TOP_LEFT = auto()
TOP = auto()
TOP_RIGHT = auto()
LEFT = auto()
RIGHT = auto()
BOTTOM_LEFT = auto()
BOTTOM = auto()
BOTTOM_RIGHT = auto()
TOP_WINDOW_BAR = auto()
class Rect:
def __init__(self, x, y, width, height) -> None:
self.x = x
self.y = y
self.width = width
self.height = height
def __str__(self):
return f'(x: {self.x}, y: {self.y}, width: {self.width}, height: {self.height})'
def assert_rects_equal(geo1, geo2, msg=None):
if geo1.x != geo2.x or geo1.y != geo2.y or geo1.width != geo2.width or geo1.height != geo2.height:
raise AssertionError(f'Rectangles not equal: \n{geo1} \nvs \n{geo2}')
unittest.main()

Some files were not shown because too many files have changed in this diff Show More