/**************************************************************************** ** ** Copyright (C) 2013 - 2016 NVIDIA Corporation. ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL$ ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "viewer.h" #include #include #include #include #include #include #include #include #include #include #include #include static QSurfaceFormat findIdealGLVersion() { QSurfaceFormat fmt; fmt.setProfile(QSurfaceFormat::CoreProfile); // Advanced: Try 4.3 core (so we get compute shaders for instance) fmt.setVersion(4, 3); QOpenGLContext ctx; ctx.setFormat(fmt); if (ctx.create() && ctx.format().version() >= qMakePair(4, 3)) return fmt; // Basic: Stick with 3.3 for now to keep less fortunate, // Mesa-based systems happy fmt.setVersion(3, 3); ctx.setFormat(fmt); if (ctx.create()) return fmt; // We tried... return QSurfaceFormat::defaultFormat(); } static QSurfaceFormat findIdealGLESVersion() { QSurfaceFormat fmt; // Advanced: Try 3.1 (so we get compute shaders for instance) fmt.setVersion(3, 1); QOpenGLContext ctx; ctx.setFormat(fmt); if (ctx.create()) return fmt; // Basic: OpenGL ES 3.0 is a hard requirement at the moment since we can // only generate 300 es shaders, uniform buffers are mandatory. fmt.setVersion(3, 0); ctx.setFormat(fmt); if (ctx.create()) return fmt; // We tried... return QSurfaceFormat::defaultFormat(); } int main(int argc, char *argv[]) { #if defined(Q_OS_MACOS) QSurfaceFormat openGLFormat; openGLFormat.setRenderableType(QSurfaceFormat::OpenGL); openGLFormat.setProfile(QSurfaceFormat::CoreProfile); openGLFormat.setMajorVersion(4); openGLFormat.setMinorVersion(1); openGLFormat.setStencilBufferSize(8); QSurfaceFormat::setDefaultFormat(openGLFormat); #endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName("The Qt Company"); QCoreApplication::setOrganizationDomain("qt.io"); QCoreApplication::setApplicationName("Qt 3D Viewer"); QGuiApplication a(argc, argv); #if !defined(Q_OS_MACOS) QSurfaceFormat fmt; if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) fmt = findIdealGLVersion(); else fmt = findIdealGLESVersion(); fmt.setDepthBufferSize(24); fmt.setStencilBufferSize(8); QSurfaceFormat::setDefaultFormat(fmt); #endif QCommandLineParser parser; parser.addHelpOption(); parser.addPositionalArgument( "file", QCoreApplication::translate("main", "The presentation file to open."), QCoreApplication::translate("main", "[file]")); parser.addOption({"sequence", QCoreApplication::translate("main", "Generates an image sequence.\n" "The file argument must be specified.\n""" "Specifying any of the seq-* arguments\n" "implies setting this option.")}); parser.addOption({"seq-start", QCoreApplication::translate("main", "Start time of the sequence in\n" "milliseconds.\n" "The default value is 0."), QCoreApplication::translate("main", "ms"), QString::number(0)}); parser.addOption({"seq-end", QCoreApplication::translate("main", "End time of the sequence in\n" "milliseconds.\n" "The default value is 1000."), QCoreApplication::translate("main", "ms"), QString::number(1000)}); parser.addOption({"seq-fps", QCoreApplication::translate("main", "Frames per second for the sequence.\n" "The default value is 60."), QCoreApplication::translate("main", "fps"), QString::number(60)}); parser.addOption({"seq-interval", QCoreApplication::translate("main", "Time interval between frames in\n" "the sequence in milliseconds. The seq-fps argument is ignored" "if this argument is used."), QCoreApplication::translate("main", "ms"), QString::number(0)}); parser.addOption({"seq-width", QCoreApplication::translate("main", "Width of the image sequence.\n" "The default value is 1920."), QCoreApplication::translate("main", "pixels"), QString::number(1920)}); parser.addOption({"seq-height", QCoreApplication::translate("main", "Height of the image sequence.\n" "The default value is 1080."), QCoreApplication::translate("main", "pixels"), QString::number(1080)}); parser.addOption({"seq-outpath", QCoreApplication::translate("main", "Output path of the image sequence.\n" "The default value is the current directory."), QCoreApplication::translate("main", "path"), QStringLiteral(".")}); parser.addOption({"seq-outfile", QCoreApplication::translate("main", "Output filename base for the image\n" "sequence.\n" "The default value is derived from the presentation filename."), QCoreApplication::translate("main", "file"), QString()}); parser.addOption({"connect", QCoreApplication::translate("main", "If this parameter is specified, the viewer\n" "is started in connection mode.\n" "The default value is 36000."), QCoreApplication::translate("main", "port"), QString::number(36000)}); parser.addOption({"fullscreen", QCoreApplication::translate("main", "Starts the viewer in fullscreen mode.\n")}); parser.addOption({"maximized", QCoreApplication::translate("main", "Starts the viewer in maximized mode.")}); parser.addOption({"windowgeometry", QCoreApplication::translate("main", "Specifies the initial\n" "window geometry using the X11-syntax.\n" "For example: 1000x800+50+50"), QCoreApplication::translate("main", "geometry"), QString()}); parser.addOption({"mattecolor", QCoreApplication::translate("main", "Specifies custom matte color\n" "using #000000 syntax.\n" "For example, white matte: #ffffff"), QCoreApplication::translate("main", "color"), QStringLiteral("#333333")}); parser.addOption({"showstats", QCoreApplication::translate("main", "Show render statistics on screen.")}); parser.addOption({"scalemode", QCoreApplication::translate("main", "Specifies scaling mode.\n" "The default value is 'center'."), QCoreApplication::translate("main", "center|fit|fill"), QStringLiteral("center")}); parser.addOption({"stereomode", QCoreApplication::translate("main", "Specifies stereo mode.\n" "The default value is 'mono'."), QCoreApplication::translate("main", "mono|topbottom|leftright"), QStringLiteral("mono")}); parser.addOption({"stereoeyeseparation", QCoreApplication::translate("main", "Specifies stereo eye separation.\n" "The default value is 0.4"), QCoreApplication::translate("main", "separation"), QString::number(0.4)}); parser.addOption({"convert-shader-cache", QCoreApplication::translate("main", "Convert base64 dump to shader cache file."), QCoreApplication::translate("main", "fileName"), QString()}); QCommandLineOption variantListOption({QStringLiteral("v"), QStringLiteral("variants")}, QObject::tr("Gives list of variant groups and variants\n" "to be loaded from the presentation.\n" "For example VarGroupA:var1,VarGroupB:var4"), QStringLiteral("variants")); parser.addOption(variantListOption); parser.process(a); const QStringList files = parser.positionalArguments(); if (files.count() > 1) { qWarning() << "Only one presentation file can be given."; parser.showHelp(-1); } bool generateSequence = parser.isSet("sequence") || parser.isSet("seq-start") || parser.isSet("seq-end") || parser.isSet("seq-fps") || parser.isSet("seq-interval") || parser.isSet("seq-width") || parser.isSet("seq-height") || parser.isSet("seq-outpath") || parser.isSet("seq-outfile"); QStringList variantList; if (parser.isSet(variantListOption)) { QString variantOption = parser.value(variantListOption); variantList = variantOption.split(QLatin1Char(','), QString::SkipEmptyParts); } #ifndef Q_OS_ANDROID Q3DSImageSequenceGenerator *generator = nullptr; #endif Viewer viewer(generateSequence); // Figure out control size multiplier for devices using touch screens to ensure all controls // have minimum usable size. qreal sizeMultiplier = 1.0; const auto touchDevices = QTouchDevice::devices(); if (touchDevices.size() > 0) { // Find out the actual screen logical pixel size. Typically touch devices we care about // only have a single screen, so we just check primary screen. const auto screens = QGuiApplication::screens(); if (screens.size() > 0) { QScreen *screen = screens.at(0); qreal dpi = screen->physicalDotsPerInch() / screen->devicePixelRatio(); sizeMultiplier = dpi / 40.0; // divider chosen empirically } } QQmlApplicationEngine engine; // Set import paths so that standalone installation works QString extraImportPath1(QStringLiteral("%1/qml")); engine.addImportPath(extraImportPath1.arg(QGuiApplication::applicationDirPath())); #ifdef Q_OS_MACOS QString extraImportPath2(QStringLiteral("%1/../../../../qml")); engine.addImportPath(extraImportPath2.arg(QGuiApplication::applicationDirPath())); #endif // Add import path for running viewer without having to install it during development QString extraImportPath3(QStringLiteral("%1/../qml")); engine.addImportPath(extraImportPath3.arg(QGuiApplication::applicationDirPath())); QQmlContext *ctx = engine.rootContext(); ctx->setContextProperty(QStringLiteral("_menuBackgroundColor"), QColor("#404244")); ctx->setContextProperty(QStringLiteral("_menuSelectionColor"), QColor("#46a2da")); ctx->setContextProperty(QStringLiteral("_menuBorderColor"), QColor("#727476")); ctx->setContextProperty(QStringLiteral("_dialogBorderColor"), QColor("#404244")); ctx->setContextProperty(QStringLiteral("_dialogBackgroundColor"), QColor("#2e2f30")); ctx->setContextProperty(QStringLiteral("_dialogFieldColor"), QColor("#404244")); ctx->setContextProperty(QStringLiteral("_dialogFieldBorderColor"), QColor("#262829")); ctx->setContextProperty(QStringLiteral("_textColor"), QColor("#ffffff")); ctx->setContextProperty(QStringLiteral("_disabledColor"), QColor("#727476")); ctx->setContextProperty(QStringLiteral("_fontSize"), int(12 * sizeMultiplier)); ctx->setContextProperty(QStringLiteral("_controlBaseHeight"), int(24 * sizeMultiplier)); ctx->setContextProperty(QStringLiteral("_controlBaseWidth"), int(80 * sizeMultiplier)); ctx->setContextProperty(QStringLiteral("_controlPadding"), int(12 * sizeMultiplier)); ctx->setContextProperty(QStringLiteral("_viewerHelper"), &viewer); qmlRegisterUncreatableType( "Qt3DStudioViewer", 1, 0, "ViewerHelper", QCoreApplication::translate("main", "Creation of ViewerHelper not allowed from QML")); engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); Q_ASSERT(engine.rootObjects().size() > 0); QWindow *appWindow = qobject_cast(engine.rootObjects().at(0)); Q_ASSERT(appWindow); viewer.setQmlRootObject(appWindow); if (parser.isSet(QStringLiteral("windowgeometry"))) { int width = 1280; int height = 768; int x = 50; int y = 50; QString geometryStr = parser.value(QStringLiteral("windowgeometry")); const QStringList splitPlus = geometryStr.split(QLatin1Char('+')); if (splitPlus.size() > 0) { const QStringList splitX = splitPlus[0].split(QLatin1Char('x')); if (splitX.size() >= 2) { width = splitX[0].toInt(); height = splitX[1].toInt(); } if (splitPlus.size() >= 3) { x = splitPlus[1].toInt(); y = splitPlus[2].toInt(); } } appWindow->setGeometry(x, y, width, height); } if (parser.isSet(QStringLiteral("fullscreen"))) appWindow->setVisibility(QWindow::FullScreen); else if (parser.isSet(QStringLiteral("maximized"))) appWindow->setVisibility(QWindow::Maximized); if (parser.isSet(QStringLiteral("mattecolor"))) { QColor matteColor(parser.value("mattecolor")); if (matteColor != Qt::black) { appWindow->setProperty("showMatteColor", QVariant::fromValue(matteColor)); appWindow->setProperty("matteColor", QVariant::fromValue(matteColor)); } } if (parser.isSet(QStringLiteral("showstats"))) appWindow->setProperty("showRenderStats", true); if (parser.isSet(QStringLiteral("scalemode"))) { QString scaleStr(parser.value("scalemode")); if (scaleStr == QStringLiteral("fit")) appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeFit); else if (scaleStr == QStringLiteral("fill")) appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeFill); else appWindow->setProperty("scaleMode", Q3DSViewerSettings::ScaleModeCenter); } if (parser.isSet(QStringLiteral("convert-shader-cache"))) { QString fileName(parser.value(QStringLiteral("convert-shader-cache"))); QString cacheFileName = fileName + QStringLiteral(".shadercache"); QFile base64File(fileName); bool success = false; if (base64File.open(QIODevice::ReadOnly)) { QByteArray shaderCache = QByteArray::fromBase64(base64File.readAll()); QFile cacheFile(cacheFileName); if (cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { cacheFile.write(shaderCache); success = true; } } if (success) qInfo() << "Saved shader cache to file:" << cacheFileName; else qWarning() << "Failed to convert" << base64File << "from base64 to a shader cache file"; } if (parser.isSet(QStringLiteral("stereomode"))) { QString stereoStr(parser.value("stereomode")); if (stereoStr == QStringLiteral("topbottom")) appWindow->setProperty("stereoMode", Q3DSViewerSettings::StereoModeTopBottom); else if (stereoStr == QStringLiteral("leftright")) appWindow->setProperty("stereoMode", Q3DSViewerSettings::StereoModeLeftRight); else if (stereoStr == QStringLiteral("anaglyphredcyan")) appWindow->setProperty("stereoMode", Q3DSViewerSettings::StereoModeAnaglyphRedCyan); else if (stereoStr == QStringLiteral("anaglyphgreenmagenta")) appWindow->setProperty("stereoMode", Q3DSViewerSettings::StereoModeAnaglyphGreenMagenta); else appWindow->setProperty("stereoMode", Q3DSViewerSettings::StereoModeMono); } if (parser.isSet(QStringLiteral("stereoeyeseparation"))) { QString separationStr(parser.value("stereoeyeseparation")); bool ok; double separation = separationStr.toDouble(&ok); if (ok) appWindow->setProperty("stereoEyeSeparation", separation); } viewer.setVariantList(variantList); #ifndef Q_OS_ANDROID if (generateSequence) { if (files.count() != 1) { qWarning() << "Presentation file is required for generating an image sequence."; parser.showHelp(-1); } generator = new Q3DSImageSequenceGenerator; QObject::connect(generator, &Q3DSImageSequenceGenerator::progress, &viewer, &Viewer::generatorProgress); QObject::connect(generator, &Q3DSImageSequenceGenerator::finished, &viewer, &Viewer::generatorFinished); viewer.setGeneratorDetails(files.first()); generator->generateImageSequence( files.first(), parser.value("seq-start").toDouble(), parser.value("seq-end").toDouble(), parser.value("seq-fps").toDouble(), parser.value("seq-interval").toDouble(), parser.value("seq-width").toInt(), parser.value("seq-height").toInt(), parser.value("seq-outpath"), parser.value("seq-outfile")); } else #endif if (!files.isEmpty()) { // Load the presentation after window has been exposed to give QtQuick time to construct // the application window properly QTimer *exposeTimer = new QTimer(appWindow); QObject::connect(exposeTimer, &QTimer::timeout, [&](){ if (appWindow->isExposed()) { exposeTimer->stop(); exposeTimer->deleteLater(); viewer.loadFile(files.first()); } }); exposeTimer->start(0); } else { viewer.setContentView(Viewer::ConnectView); if (parser.isSet(QStringLiteral("connect"))) viewer.setConnectPort(parser.value(QStringLiteral("connect")).toInt()); viewer.connectRemote(); } return a.exec(); }