summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan-Marek Glogowski <glogow@fbihome.de>2020-12-22 12:47:03 +0100
committerThorsten Behrens <thorsten.behrens@allotropia.de>2021-04-16 15:31:49 +0200
commit9506fbb812a0cea885ff390554b83ce4c2f6d35c (patch)
treed2f983cbab832e4accd882b2fd1c7ad01c942911
parent38f54a3f162798841832074fd59098d9419f4c55 (diff)
WASM build mandelbrot Qt example via gbuild
All the flags are copied directly from the qmake example. The emscripten HTML doesn't work, but copying the Qt HTML and the qtloader.js correctly runs the example in the browser. This is all a bit messy, but it is mainly a know working app to verify any WASM development at this point, so we know the Gbuild configuration can build a WASM application. Change-Id: I1e5fcc64f452d524192f4ea6d975aafafbb9f5bb
-rw-r--r--README.wasm7
-rw-r--r--Repository.mk1
-rw-r--r--RepositoryModule_host.mk1
-rw-r--r--configure.ac7
-rw-r--r--solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk18
-rw-r--r--wasm/CustomTarget_wasm-qt5-mandelbrot_moc.mk37
-rw-r--r--wasm/Executable_wasm-qt5-mandelbrot.mk32
-rw-r--r--wasm/Makefile13
-rw-r--r--wasm/Module_wasm.mk20
-rw-r--r--wasm/README1
-rw-r--r--wasm/source/qt5-mandelbrot/main.cxx66
-rw-r--r--wasm/source/qt5-mandelbrot/mandelbrotwidget.cxx228
-rw-r--r--wasm/source/qt5-mandelbrot/mandelbrotwidget.h91
-rw-r--r--wasm/source/qt5-mandelbrot/renderthread.cxx232
-rw-r--r--wasm/source/qt5-mandelbrot/renderthread.h97
15 files changed, 842 insertions, 9 deletions
diff --git a/README.wasm b/README.wasm
index 012d8c981539..3e201737bd24 100644
--- a/README.wasm
+++ b/README.wasm
@@ -5,6 +5,13 @@ $ emrun --serve_after_close instdir/program/ui-previewer.html
The ui-previewer "binary" will "crash" with memory alignment problems.
+You can run the WASM mandelbrot Qt example, if you copy it's HTML
+and the qtloader.js from the Qt's example folder after build with:
+
+$ emrun --serve_after_close workdir/LinkTarget/Executable/mandelbrot.html
+
+REMINDER: always start new tabs in the browser, reload might fail / cache!
+
= Setup for the LO WASM build (with Qt) =
diff --git a/Repository.mk b/Repository.mk
index 0c8c91f2becc..74b3967ca05b 100644
--- a/Repository.mk
+++ b/Repository.mk
@@ -73,6 +73,7 @@ $(eval $(call gb_Helper_register_executables,NONE, \
mtfdemo \
visualbackendtest \
$(if $(and $(ENABLE_GTK3), $(filter LINUX %BSD SOLARIS,$(OS))), gtktiledviewer) \
+ $(if $(filter EMSCRIPTEN,$(OS)),wasm-qt5-mandelbrot) \
))
$(eval $(call gb_Helper_register_executables_for_install,SDK,sdk, \
diff --git a/RepositoryModule_host.mk b/RepositoryModule_host.mk
index 94a986ea2db7..20fdf05bc117 100644
--- a/RepositoryModule_host.mk
+++ b/RepositoryModule_host.mk
@@ -148,6 +148,7 @@ $(eval $(call gb_Module_add_moduledirs,libreoffice,\
uui \
vbahelper \
vcl \
+ wasm \
winaccessibility \
wizards \
writerfilter \
diff --git a/configure.ac b/configure.ac
index c2ba067a6b8e..55879ba296d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12523,6 +12523,7 @@ then
qt5_incdirs="`$QMAKE5 -query QT_INSTALL_HEADERS` $qt5_incdirs"
qt5_libdirs="`$QMAKE5 -query QT_INSTALL_LIBS` $qt5_libdirs"
+ qt5_platformsdir="`$QMAKE5 -query QT_INSTALL_PLUGINS`/platforms"
AC_MSG_CHECKING([for Qt5 headers])
qt5_incdir="no"
@@ -12558,11 +12559,15 @@ then
AC_MSG_ERROR([Qt5 libraries not found. Please specify the root of your Qt5 installation by exporting QT5DIR before running "configure".])
fi
+ if test "$_os" = "Emscripten" -a ! -f "${qt5_platformsdir}"/libqwasm.a; then
+ AC_MSG_ERROR([No Qt5 WASM QPA plugin found in ${qt5_platformsdir}])
+ fi
+
QT5_CFLAGS="-I$qt5_incdir -DQT_CLEAN_NAMESPACE -DQT_THREAD_SUPPORT -DQT_NO_VERSION_TAGGING"
QT5_CFLAGS=$(printf '%s' "$QT5_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
QT5_LIBS="-L$qt5_libdir -lQt5Core -lQt5Gui -lQt5Widgets -lQt5Network"
if test "$_os" = "Emscripten"; then
- QT5_LIBS="$QT5_LIBS -lqtpcre2"
+ QT5_LIBS="$QT5_LIBS -lqtpcre2 -lQt5EventDispatcherSupport -lQt5FontDatabaseSupport -L${qt5_platformsdir} -lqwasm"
fi
if test "$USING_X11" = TRUE; then
diff --git a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
index 1c0456c646ec..941081614291 100644
--- a/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
+++ b/solenv/gbuild/platform/EMSCRIPTEN_INTEL_GCC.mk
@@ -17,12 +17,19 @@ include $(GBUILDDIR)/platform/unxgcc.mk
gb_RUN_CONFIGURE := $(SRCDIR)/solenv/bin/run-configure
gb_EMSCRIPTEN_CPPFLAGS := -pthread -s TOTAL_MEMORY=1GB -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 -s SAFE_HEAP=1
+gb_EMSCRIPTEN_LDFLAGS := $(gb_EMSCRIPTEN_CPPFLAGS) --bind -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s FETCH=1 -s ASSERTIONS=1 -s EXIT_RUNTIME=1 -s EXTRA_EXPORTED_RUNTIME_METHODS=["UTF16ToString","stringToUTF16"]
+gb_EMSCRIPTEN_QTDEFS := -DQT_NO_LINKED_LIST -DQT_NO_JAVA_STYLE_ITERATORS -DQT_NO_EXCEPTIONS -D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB
gb_Executable_EXT := .html
+gb_EMSCRIPTEN_EXCEPT := -s DISABLE_EXCEPTION_CATCHING=0
-gb_CFLAGS += $(gb_EMSCRIPTEN_CPPFLAGS)
-gb_CXXFLAGS += $(gb_EMSCRIPTEN_CPPFLAGS) -s DISABLE_EXCEPTION_CATCHING=0
-gb_LinkTarget_LDFLAGS += $(gb_EMSCRIPTEN_CPPFLAGS) --bind
+gb_LinkTarget_CFLAGS += $(gb_EMSCRIPTEN_CPPFLAGS) $(gb_EMSCRIPTEN_QTDEFS)
+gb_LinkTarget_CXXFLAGS += $(gb_EMSCRIPTEN_CPPFLAGS) $(gb_EMSCRIPTEN_QTDEFS) $(gb_EMSCRIPTEN_EXCEPT)
+
+# WASM is also optimized at link time, but ignores linker flags, so wants $(gb_COMPILEROPTFLAGS)
+gb_LINKEROPTFLAGS :=
+gb_LINKERSTRIPDEBUGFLAGS :=
+gb_LinkTarget_LDFLAGS += $(gb_EMSCRIPTEN_LDFLAGS) $(gb_EMSCRIPTEN_CPPFLAGS) $(gb_EMSCRIPTEN_EXCEPT) $(gb_COMPILEROPTFLAGS)
define gb_Library_get_rpath
endef
@@ -30,9 +37,4 @@ endef
define gb_Executable_get_rpath
endef
-gb_LINKEROPTFLAGS :=
-gb_LINKERSTRIPDEBUGFLAGS :=
-
-#gb_CXX_LINKFLAGS += -pthread -s TOTAL_MEMORY=1GB -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4
-
# vim: set noet sw=4 ts=4
diff --git a/wasm/CustomTarget_wasm-qt5-mandelbrot_moc.mk b/wasm/CustomTarget_wasm-qt5-mandelbrot_moc.mk
new file mode 100644
index 000000000000..52b7447d22e9
--- /dev/null
+++ b/wasm/CustomTarget_wasm-qt5-mandelbrot_moc.mk
@@ -0,0 +1,37 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,wasm/qt5-mandelbrot))
+
+$(call gb_CustomTarget_get_target,wasm/qt5-mandelbrot) : \
+ $(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/renderthread.moc \
+ $(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/mandelbrotwidget.moc \
+
+qt5_mandelbrot_MOCDEFS_H := $(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/moc_predefs.h
+qt5_mandelbrot_MOCDEFS_CXX := $(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/moc_dummy.cxx
+qt5_mandelbrot_WORKDIR := $(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/.dir
+
+$(qt5_mandelbrot_MOCDEFS_CXX): | $(qt5_mandelbrot_WORKDIR)
+ touch $@
+
+$(qt5_mandelbrot_MOCDEFS_H): $(qt5_mandelbrot_MOCDEFS_CXX) | $(qt5_mandelbrot_WORKDIR)
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),CXX,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),CXX)
+ $(CXX) -pipe -O2 -std=gnu++11 -fno-exceptions $(gb_EMSCRIPTEN_CPPFLAGS) -dM -E -o $@ $<
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+$(call gb_CustomTarget_get_workdir,wasm/qt5-mandelbrot)/%.moc : \
+ $(SRCDIR)/wasm/source/qt5-mandelbrot/%.h \
+ $(qt5_mandelbrot_MOCDEFS_H) | $(qt5_mandelbrot_WORKDIR)
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),MOC,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),MOC)
+ $(MOC5) --include $(qt5_mandelbrot_MOCDEFS_H) $(gb_EMSCRIPTEN_QTDEFS) $< -o $@
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),MOC)
+
+# vim: set noet sw=4:
diff --git a/wasm/Executable_wasm-qt5-mandelbrot.mk b/wasm/Executable_wasm-qt5-mandelbrot.mk
new file mode 100644
index 000000000000..7afaa81c0dc4
--- /dev/null
+++ b/wasm/Executable_wasm-qt5-mandelbrot.mk
@@ -0,0 +1,32 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,wasm-qt5-mandelbrot))
+
+$(eval $(call gb_Executable_use_custom_headers,wasm-qt5-mandelbrot,wasm/qt5-mandelbrot))
+
+$(eval $(call gb_Executable_use_externals,wasm-qt5-mandelbrot,\
+ graphite \
+ freetype \
+ harfbuzz \
+ libpng \
+ qt5 \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,wasm-qt5-mandelbrot,\
+ wasm/source/qt5-mandelbrot/main \
+ wasm/source/qt5-mandelbrot/mandelbrotwidget \
+ wasm/source/qt5-mandelbrot/renderthread \
+))
+
+$(eval $(call gb_Executable_add_defs,wasm-qt5-mandelbrot,\
+ -DVCL_INTERNALS \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/wasm/Makefile b/wasm/Makefile
new file mode 100644
index 000000000000..0c6f47b1790f
--- /dev/null
+++ b/wasm/Makefile
@@ -0,0 +1,13 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+
+include $(module_directory)/../solenv/gbuild/partial_build.mk
+
+# vim: set noet sw=4 ts=4:
diff --git a/wasm/Module_wasm.mk b/wasm/Module_wasm.mk
new file mode 100644
index 000000000000..0ef8c4c6aaf4
--- /dev/null
+++ b/wasm/Module_wasm.mk
@@ -0,0 +1,20 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_Module_Module,wasm))
+
+ifeq ($(OS),EMSCRIPTEN)
+
+$(eval $(call gb_Module_add_targets,wasm,\
+ CustomTarget_wasm-qt5-mandelbrot_moc \
+ Executable_wasm-qt5-mandelbrot \
+))
+
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/wasm/README b/wasm/README
new file mode 100644
index 000000000000..a23bd6a45ea9
--- /dev/null
+++ b/wasm/README
@@ -0,0 +1 @@
+See /README.wasm
diff --git a/wasm/source/qt5-mandelbrot/main.cxx b/wasm/source/qt5-mandelbrot/main.cxx
new file mode 100644
index 000000000000..037c63153cf2
--- /dev/null
+++ b/wasm/source/qt5-mandelbrot/main.cxx
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mandelbrotwidget.h"
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QtPlugin>
+
+Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
+
+int main(int argc, char* argv[])
+{
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+ QApplication app(argc, argv);
+ MandelbrotWidget widget;
+ widget.show();
+ return app.exec();
+}
diff --git a/wasm/source/qt5-mandelbrot/mandelbrotwidget.cxx b/wasm/source/qt5-mandelbrot/mandelbrotwidget.cxx
new file mode 100644
index 000000000000..5886dd2664c0
--- /dev/null
+++ b/wasm/source/qt5-mandelbrot/mandelbrotwidget.cxx
@@ -0,0 +1,228 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mandelbrotwidget.h"
+#include "mandelbrotwidget.moc"
+
+#include <QtGui/QPainter>
+#include <QtGui/QKeyEvent>
+
+#include <math.h>
+
+const double DefaultCenterX = -0.637011;
+const double DefaultCenterY = -0.0395159;
+const double DefaultScale = 0.00403897;
+
+const double ZoomInFactor = 0.8;
+const double ZoomOutFactor = 1 / ZoomInFactor;
+const int ScrollStep = 20;
+
+MandelbrotWidget::MandelbrotWidget(QWidget* parent)
+ : QWidget(parent)
+ , centerX(DefaultCenterX)
+ , centerY(DefaultCenterY)
+ , pixmapScale(DefaultScale)
+ , curScale(DefaultScale)
+{
+ connect(&thread, &RenderThread::renderedImage, this, &MandelbrotWidget::updatePixmap);
+
+ setWindowTitle(tr("Mandelbrot"));
+#if QT_CONFIG(cursor)
+ setCursor(Qt::CrossCursor);
+#endif
+ resize(550, 400);
+}
+
+void MandelbrotWidget::paintEvent(QPaintEvent* /* event */)
+{
+ QPainter painter(this);
+ painter.fillRect(rect(), Qt::black);
+
+ if (pixmap.isNull())
+ {
+ painter.setPen(Qt::white);
+ painter.drawText(rect(), Qt::AlignCenter, tr("Rendering initial image, please wait..."));
+ return;
+ }
+
+ if (qFuzzyCompare(curScale, pixmapScale))
+ {
+ painter.drawPixmap(pixmapOffset, pixmap);
+ }
+ else
+ {
+ auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatioF(), qreal(1))
+ ? pixmap
+ : pixmap.scaled(pixmap.size() / pixmap.devicePixelRatioF(),
+ Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ double scaleFactor = pixmapScale / curScale;
+ int newWidth = int(previewPixmap.width() * scaleFactor);
+ int newHeight = int(previewPixmap.height() * scaleFactor);
+ int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2;
+ int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2;
+
+ painter.save();
+ painter.translate(newX, newY);
+ painter.scale(scaleFactor, scaleFactor);
+
+ QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
+ painter.drawPixmap(exposed, previewPixmap, exposed);
+ painter.restore();
+ }
+
+ QString text = tr("Use mouse wheel or the '+' and '-' keys to zoom. "
+ "Press and hold left mouse button to scroll.");
+ QFontMetrics metrics = painter.fontMetrics();
+ int textWidth = metrics.horizontalAdvance(text);
+
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(QColor(0, 0, 0, 127));
+ painter.drawRect((width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5);
+ painter.setPen(Qt::white);
+ painter.drawText((width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text);
+}
+
+void MandelbrotWidget::resizeEvent(QResizeEvent* /* event */)
+{
+ thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
+}
+
+void MandelbrotWidget::keyPressEvent(QKeyEvent* event)
+{
+ switch (event->key())
+ {
+ case Qt::Key_Plus:
+ zoom(ZoomInFactor);
+ break;
+ case Qt::Key_Minus:
+ zoom(ZoomOutFactor);
+ break;
+ case Qt::Key_Left:
+ scroll(-ScrollStep, 0);
+ break;
+ case Qt::Key_Right:
+ scroll(+ScrollStep, 0);
+ break;
+ case Qt::Key_Down:
+ scroll(0, -ScrollStep);
+ break;
+ case Qt::Key_Up:
+ scroll(0, +ScrollStep);
+ break;
+ default:
+ QWidget::keyPressEvent(event);
+ }
+}
+
+#if QT_CONFIG(wheelevent)
+void MandelbrotWidget::wheelEvent(QWheelEvent* event)
+{
+ const int numDegrees = event->angleDelta().y() / 8;
+ const double numSteps = numDegrees / double(15);
+ zoom(pow(ZoomInFactor, numSteps));
+}
+#endif
+
+void MandelbrotWidget::mousePressEvent(QMouseEvent* event)
+{
+ if (event->button() == Qt::LeftButton)
+ lastDragPos = event->pos();
+}
+
+void MandelbrotWidget::mouseMoveEvent(QMouseEvent* event)
+{
+ if (event->buttons() & Qt::LeftButton)
+ {
+ pixmapOffset += event->pos() - lastDragPos;
+ lastDragPos = event->pos();
+ update();
+ }
+}
+
+void MandelbrotWidget::mouseReleaseEvent(QMouseEvent* event)
+{
+ if (event->button() == Qt::LeftButton)
+ {
+ pixmapOffset += event->pos() - lastDragPos;
+ lastDragPos = QPoint();
+
+ const auto pixmapSize = pixmap.size() / pixmap.devicePixelRatioF();
+ int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x();
+ int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y();
+ scroll(deltaX, deltaY);
+ }
+}
+
+void MandelbrotWidget::updatePixmap(const QImage& image, double scaleFactor)
+{
+ if (!lastDragPos.isNull())
+ return;
+
+ pixmap = QPixmap::fromImage(image);
+ pixmapOffset = QPoint();
+ lastDragPos = QPoint();
+ pixmapScale = scaleFactor;
+ update();
+}
+
+void MandelbrotWidget::zoom(double zoomFactor)
+{
+ curScale *= zoomFactor;
+ update();
+ thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
+}
+
+void MandelbrotWidget::scroll(int deltaX, int deltaY)
+{
+ centerX += deltaX * curScale;
+ centerY += deltaY * curScale;
+ update();
+ thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
+}
diff --git a/wasm/source/qt5-mandelbrot/mandelbrotwidget.h b/wasm/source/qt5-mandelbrot/mandelbrotwidget.h
new file mode 100644
index 000000000000..72e95568b522
--- /dev/null
+++ b/wasm/source/qt5-mandelbrot/mandelbrotwidget.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGui/QPixmap>
+#include <QtWidgets/QWidget>
+
+#include "renderthread.h"
+
+class MandelbrotWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ MandelbrotWidget(QWidget* parent = nullptr);
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+ void resizeEvent(QResizeEvent* event) override;
+ void keyPressEvent(QKeyEvent* event) override;
+#if QT_CONFIG(wheelevent)
+ void wheelEvent(QWheelEvent* event) override;
+#endif
+ void mousePressEvent(QMouseEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mouseReleaseEvent(QMouseEvent* event) override;
+
+private slots:
+ void updatePixmap(const QImage& image, double scaleFactor);
+ void zoom(double zoomFactor);
+
+private:
+ void scroll(int deltaX, int deltaY);
+
+ RenderThread thread;
+ QPixmap pixmap;
+ QPoint pixmapOffset;
+ QPoint lastDragPos;
+ double centerX;
+ double centerY;
+ double pixmapScale;
+ double curScale;
+};
diff --git a/wasm/source/qt5-mandelbrot/renderthread.cxx b/wasm/source/qt5-mandelbrot/renderthread.cxx
new file mode 100644
index 000000000000..398e43ded15d
--- /dev/null
+++ b/wasm/source/qt5-mandelbrot/renderthread.cxx
@@ -0,0 +1,232 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "renderthread.h"
+#include "renderthread.moc"
+
+#include <QtGui/QImage>
+#include <cmath>
+
+RenderThread::RenderThread(QObject* parent)
+ : QThread(parent)
+{
+ for (int i = 0; i < ColormapSize; ++i)
+ m_colormap[i] = rgbFromWaveLength(380.0 + (i * 400.0 / ColormapSize));
+}
+
+RenderThread::~RenderThread()
+{
+ m_mutex.lock();
+ m_abort = true;
+ m_condition.wakeOne();
+ m_mutex.unlock();
+
+ wait();
+}
+
+void RenderThread::render(double centerX, double centerY, double scaleFactor, QSize resultSize,
+ double devicePixelRatio)
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_centerX = centerX;
+ m_centerY = centerY;
+ m_scaleFactor = scaleFactor;
+ m_devicePixelRatio = devicePixelRatio;
+ m_resultSize = resultSize;
+
+ if (!isRunning())
+ {
+ start(LowPriority);
+ }
+ else
+ {
+ m_restart = true;
+ m_condition.wakeOne();
+ }
+}
+
+void RenderThread::run()
+{
+ forever
+ {
+ m_mutex.lock();
+ const double devicePixelRatio = m_devicePixelRatio;
+ const QSize resultSize = m_resultSize * devicePixelRatio;
+ const double requestedScaleFactor = m_scaleFactor;
+ const double scaleFactor = requestedScaleFactor / devicePixelRatio;
+ const double centerX = m_centerX;
+ const double centerY = m_centerY;
+ m_mutex.unlock();
+
+ int halfWidth = resultSize.width() / 2;
+ int halfHeight = resultSize.height() / 2;
+ QImage image(resultSize, QImage::Format_RGB32);
+ image.setDevicePixelRatio(devicePixelRatio);
+
+ const int NumPasses = 8;
+ int pass = 0;
+ while (pass < NumPasses)
+ {
+ const int MaxIterations = (1 << (2 * pass + 6)) + 32;
+ const int Limit = 4;
+ bool allBlack = true;
+
+ for (int y = -halfHeight; y < halfHeight; ++y)
+ {
+ if (m_restart)
+ break;
+ if (m_abort)
+ return;
+
+ auto scanLine = reinterpret_cast<uint*>(image.scanLine(y + halfHeight));
+ const double ay = centerY + (y * scaleFactor);
+
+ for (int x = -halfWidth; x < halfWidth; ++x)
+ {
+ const double ax = centerX + (x * scaleFactor);
+ double a1 = ax;
+ double b1 = ay;
+ int numIterations = 0;
+
+ do
+ {
+ ++numIterations;
+ const double a2 = (a1 * a1) - (b1 * b1) + ax;
+ const double b2 = (2 * a1 * b1) + ay;
+ if ((a2 * a2) + (b2 * b2) > Limit)
+ break;
+
+ ++numIterations;
+ a1 = (a2 * a2) - (b2 * b2) + ax;
+ b1 = (2 * a2 * b2) + ay;
+ if ((a1 * a1) + (b1 * b1) > Limit)
+ break;
+ } while (numIterations < MaxIterations);
+
+ if (numIterations < MaxIterations)
+ {
+ *scanLine++ = m_colormap[numIterations % ColormapSize];
+ allBlack = false;
+ }
+ else
+ {
+ *scanLine++ = qRgb(0, 0, 0);
+ }
+ }
+ }
+
+ if (allBlack && pass == 0)
+ {
+ pass = 4;
+ }
+ else
+ {
+ if (!m_restart)
+ emit renderedImage(image, requestedScaleFactor);
+ ++pass;
+ }
+ }
+
+ m_mutex.lock();
+ if (!m_restart)
+ m_condition.wait(&m_mutex);
+ m_restart = false;
+ m_mutex.unlock();
+ }
+}
+
+uint RenderThread::rgbFromWaveLength(double wave)
+{
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ if (wave >= 380.0 && wave <= 440.0)
+ {
+ r = -1.0 * (wave - 440.0) / (440.0 - 380.0);
+ b = 1.0;
+ }
+ else if (wave >= 440.0 && wave <= 490.0)
+ {
+ g = (wave - 440.0) / (490.0 - 440.0);
+ b = 1.0;
+ }
+ else if (wave >= 490.0 && wave <= 510.0)
+ {
+ g = 1.0;
+ b = -1.0 * (wave - 510.0) / (510.0 - 490.0);
+ }
+ else if (wave >= 510.0 && wave <= 580.0)
+ {
+ r = (wave - 510.0) / (580.0 - 510.0);
+ g = 1.0;
+ }
+ else if (wave >= 580.0 && wave <= 645.0)
+ {
+ r = 1.0;
+ g = -1.0 * (wave - 645.0) / (645.0 - 580.0);
+ }
+ else if (wave >= 645.0 && wave <= 780.0)
+ {
+ r = 1.0;
+ }
+
+ double s = 1.0;
+ if (wave > 700.0)
+ s = 0.3 + 0.7 * (780.0 - wave) / (780.0 - 700.0);
+ else if (wave < 420.0)
+ s = 0.3 + 0.7 * (wave - 380.0) / (420.0 - 380.0);
+
+ r = std::pow(r * s, 0.8);
+ g = std::pow(g * s, 0.8);
+ b = std::pow(b * s, 0.8);
+ return qRgb(int(r * 255), int(g * 255), int(b * 255));
+}
diff --git a/wasm/source/qt5-mandelbrot/renderthread.h b/wasm/source/qt5-mandelbrot/renderthread.h
new file mode 100644
index 000000000000..16c0d86fd46c
--- /dev/null
+++ b/wasm/source/qt5-mandelbrot/renderthread.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtCore/QMutex>
+#include <QtCore/QSize>
+#include <QtCore/QThread>
+#include <QtCore/QWaitCondition>
+
+QT_BEGIN_NAMESPACE
+class QImage;
+QT_END_NAMESPACE
+
+class RenderThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ RenderThread(QObject* parent = nullptr);
+ ~RenderThread();
+
+ void render(double centerX, double centerY, double scaleFactor, QSize resultSize,
+ double devicePixelRatio);
+
+signals:
+ void renderedImage(const QImage& image, double scaleFactor);
+
+protected:
+ void run() override;
+
+private:
+ static uint rgbFromWaveLength(double wave);
+
+ QMutex m_mutex;
+ QWaitCondition m_condition;
+ double m_centerX;
+ double m_centerY;
+ double m_scaleFactor;
+ double m_devicePixelRatio;
+ QSize m_resultSize;
+ bool m_restart = false;
+ bool m_abort = false;
+
+ enum
+ {
+ ColormapSize = 512
+ };
+ uint m_colormap[ColormapSize];
+};