diff --git a/interface/resources/config/render.json b/interface/resources/config/render.json new file mode 100644 index 0000000000..092530d864 --- /dev/null +++ b/interface/resources/config/render.json @@ -0,0 +1,19 @@ +{ + "RenderShadowTask": { + "Enabled": { + "enabled": true + } + }, + "RenderDeferredTask": { + "AmbientOcclusion": { + "Enabled": { + "enabled": true + } + }, + "Antialiasing": { + "Enabled": { + "enabled": true + } + } + } +} diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 637cc6c02c..790d1d82ea 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -49,7 +49,7 @@ Preference { Button { id: button anchors { right: parent.right; verticalCenter: dataTextField.verticalCenter } - text: "Browse" + text: preference.browseLabel onClicked: { var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) }); browser.selectedFile.connect(function(fileUrl){ diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml new file mode 100644 index 0000000000..9f323ace63 --- /dev/null +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -0,0 +1,37 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Preference { + id: root + property real spacing: 8 + height: labelText.height + dataComboBox.height + spacing + + Component.onCompleted: { + dataComboBox.currentIndex = dataComboBox.find(preference.value); + } + + function save() { + preference.value = dataComboBox.currentText; + preference.save(); + } + + Text { + id: labelText + color: enabled ? "black" : "gray" + text: root.label + } + + ComboBox { + id: dataComboBox + model: preference.items + style: ComboBoxStyle { renderType: Text.QtRendering } + anchors { + top: labelText.bottom + left: parent.left + right: parent.right + topMargin: root.spacing + rightMargin: root.spacing + } + } +} diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index b1e59430a9..63aa19651a 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -81,6 +81,7 @@ Preference { property var sliderBuilder: Component { SliderPreference { } } property var avatarBuilder: Component { AvatarPreference { } } property var buttonBuilder: Component { ButtonPreference { } } + property var comboBoxBuilder: Component { ComboBoxPreference { } } property var preferences: [] function buildPreferences() { @@ -123,7 +124,11 @@ Preference { case Preference.Button: builder = buttonBuilder; - break + break; + + case Preference.ComboBox: + builder = comboBoxBuilder; + break; }; if (builder) { diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index c876ac6abb..81e4924204 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -7,7 +7,7 @@ PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" title: "General Preferences" - showCategories: ["Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Graphics"] + showCategories: ["Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers"] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml new file mode 100644 index 0000000000..dca3a8694c --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml @@ -0,0 +1,19 @@ +import QtQuick 2.5 +import Qt.labs.settings 1.0 + +import "../../dialogs" + +PreferencesDialog { + id: root + objectName: "GraphicsPreferencesDialog" + title: "Graphics Preferences" + showCategories: ["Graphics"] + property var settings: Settings { + category: root.objectName + property alias x: root.x + property alias y: root.y + property alias width: root.width + property alias height: root.height + } +} + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 749bf73b0d..ed18171a48 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1156,6 +1156,7 @@ void Application::initializeGL() { render::CullFunctor cullFunctor = LODManager::shouldRender; _renderEngine->addJob("RenderShadowTask", cullFunctor); _renderEngine->addJob("RenderDeferredTask", cullFunctor); + _renderEngine->load(); _renderEngine->registerScene(_main3DScene); // TODO: Load a cached config file diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e5f6de3ae1..964e660dd0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -312,6 +312,12 @@ Menu::Menu() { DependencyManager::get()->toggle(QString("hifi/dialogs/AudioPreferencesDialog.qml"), "AudioPreferencesDialog"); }); + // Settings > Graphics... + action = addActionToQMenuAndActionHash(settingsMenu, "Graphics..."); + connect(action, &QAction::triggered, [] { + DependencyManager::get()->toggle(QString("hifi/dialogs/GraphicsPreferencesDialog.qml"), "GraphicsPreferencesDialog"); + }); + // Settings > LOD...-- FIXME: needs implementation action = addActionToQMenuAndActionHash(settingsMenu, "LOD..."); connect(action, &QAction::triggered, [] { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index fd4647e429..b1812b7bd3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -336,24 +336,31 @@ void setupPreferences() { { static const QString RENDER("Graphics"); auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + + auto ambientOcclusionConfig = renderConfig->getConfig(); { - auto getter = [renderConfig]()->bool { return renderConfig->isJobEnabled(); }; - auto setter = [renderConfig](bool enable) { renderConfig->setJobEnabled(enable); }; - auto preference = new CheckPreference(RENDER, "Ambient Occlusion", getter, setter); + auto getter = [ambientOcclusionConfig]()->QString { return ambientOcclusionConfig->getPreset(); }; + auto setter = [ambientOcclusionConfig](QString preset) { ambientOcclusionConfig->setPreset(preset); }; + auto preference = new ComboBoxPreference(RENDER, "Ambient Occlusion", getter, setter); + preference->setItems(ambientOcclusionConfig->getPresetList()); preferences->addPreference(preference); } + auto antialiasingConfig = renderConfig->getConfig(); { - auto getter = [renderConfig]()->bool { return renderConfig->isJobEnabled(); }; - auto setter = [renderConfig](bool enable) { renderConfig->setJobEnabled(enable); }; - auto preference = new CheckPreference(RENDER, "Antialiasing", getter, setter); + auto getter = [antialiasingConfig]()->QString { return antialiasingConfig->getPreset(); }; + auto setter = [antialiasingConfig](QString preset) { antialiasingConfig->setPreset(preset); }; + auto preference = new ComboBoxPreference(RENDER, "Antialiasing", getter, setter); + preference->setItems(antialiasingConfig->getPresetList()); preferences->addPreference(preference); } + auto shadowConfig = renderConfig->getConfig(); { - auto getter = [renderConfig]()->bool { return renderConfig->isJobEnabled(); }; - auto setter = [renderConfig](bool enable) { renderConfig->setJobEnabled(enable); }; - auto preference = new CheckPreference(RENDER, "Shadows", getter, setter); + auto getter = [shadowConfig]()->QString { return shadowConfig->getPreset(); }; + auto setter = [shadowConfig](QString preset) { shadowConfig->setPreset(preset); }; + auto preference = new ComboBoxPreference(RENDER, "Shadows", getter, setter); + preference->setItems(shadowConfig->getPresetList()); preferences->addPreference(preference); } } diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp index 4634c05a06..10d80e5799 100644 --- a/libraries/octree/src/OctreeElementBag.cpp +++ b/libraries/octree/src/OctreeElementBag.cpp @@ -16,26 +16,25 @@ void OctreeElementBag::deleteAll() { _bagElements = Bag(); } +/// does the bag contain elements? +/// if all of the contained elements are expired, they will not report as empty, and +/// a single last item will be returned by extract as a null pointer bool OctreeElementBag::isEmpty() { - // Pop all expired front elements - while (!_bagElements.empty() && _bagElements.front().expired()) { - _bagElements.pop(); - } - return _bagElements.empty(); } void OctreeElementBag::insert(OctreeElementPointer element) { - _bagElements.push(element); + _bagElements[element.get()] = element; } OctreeElementPointer OctreeElementBag::extract() { OctreeElementPointer result; // Find the first element still alive - while (!result && !_bagElements.empty()) { - result = _bagElements.front().lock(); // Grab head's shared_ptr - _bagElements.pop(); + Bag::iterator it = _bagElements.begin(); + while (it != _bagElements.end() && !result) { + result = it->second.lock(); + it = _bagElements.erase(it); } return result; } diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index 02423b640c..97ff019513 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -16,17 +16,22 @@ #ifndef hifi_OctreeElementBag_h #define hifi_OctreeElementBag_h -#include +#include #include "OctreeElement.h" class OctreeElementBag { - using Bag = std::queue; + using Bag = std::unordered_map; public: void insert(OctreeElementPointer element); // put a element into the bag - OctreeElementPointer extract(); // pull a element out of the bag (could come in any order) - bool isEmpty(); + + OctreeElementPointer extract(); /// pull a element out of the bag (could come in any order) and if all of the + /// elements have expired, a single null pointer will be returned + + bool isEmpty(); /// does the bag contain elements, + /// if all of the contained elements are expired, they will not report as empty, and + /// a single last item will be returned by extract as a null pointer void deleteAll(); diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index c040e31188..717c9dc4fc 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -16,7 +16,7 @@ #include "render/DrawTask.h" -class AmbientOcclusionEffectConfig : public render::Job::Config { +class AmbientOcclusionEffectConfig : public render::Job::Config::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty) @@ -32,7 +32,7 @@ class AmbientOcclusionEffectConfig : public render::Job::Config { Q_PROPERTY(int blurRadius MEMBER blurRadius WRITE setBlurRadius) Q_PROPERTY(double gpuTime READ getGpuTime) public: - AmbientOcclusionEffectConfig() : render::Job::Config(false) {} + AmbientOcclusionEffectConfig() : render::Job::Config::Persistent("Ambient Occlusion", false) {} const int MAX_RESOLUTION_LEVEL = 4; const int MAX_BLUR_RADIUS = 6; diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index f0a32c68ff..6185ed07dc 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -16,11 +16,11 @@ #include "render/DrawTask.h" -class AntiAliasingConfig : public render::Job::Config { +class AntiAliasingConfig : public render::Job::Config::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) public: - AntiAliasingConfig() : render::Job::Config(false) {} + AntiAliasingConfig() : render::Job::Config::Persistent("Antialiasing", false) {} }; class Antialiasing { diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index e1bf983b79..679302b69f 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -31,11 +31,11 @@ protected: render::ShapePlumberPointer _shapePlumber; }; -class RenderShadowTaskConfig : public render::Task::Config { +class RenderShadowTaskConfig : public render::Task::Config::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) public: - RenderShadowTaskConfig() : render::Task::Config(false) {} + RenderShadowTaskConfig() : render::Task::Config::Persistent("Shadows", false) {} signals: void dirty(); diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index be0481f97c..806c964ec0 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -9,9 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Engine.h" + +#include + +#include + #include -#include "Engine.h" using namespace render; @@ -20,6 +25,31 @@ Engine::Engine() : _renderContext(std::make_shared()) { } +void Engine::load() { + auto config = getConfiguration(); + const QString configFile= "config/render.json"; + + QUrl path(PathUtils::resourcesPath() + configFile); + QFile file(path.toString()); + if (!file.exists()) { + qWarning() << "Engine configuration file" << path << "does not exist"; + } else if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Engine configuration file" << path << "cannot be opened"; + } else { + QString data = file.readAll(); + file.close(); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &error); + if (error.error == error.NoError) { + config->setPresetList(doc.object()); + qDebug() << "Engine configuration file" << path << "loaded"; + } else { + qWarning() << "Engine configuration file" << path << "failed to load:" << + error.errorString() << "at offset" << error.offset; + } + } +} + void Engine::run() { // Sync GPU state before beginning to render _renderContext->args->_context->syncCache(); diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index e94de6f266..1af0e6d76f 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -12,6 +12,8 @@ #ifndef hifi_render_Engine_h #define hifi_render_Engine_h +#include + #include "Context.h" #include "Task.h" @@ -25,6 +27,10 @@ public: Engine(); ~Engine() = default; + // Load any persisted settings, and set up the presets + // This should be run after adding all jobs, and before building ui + void load(); + // Register the scene void registerScene(const ScenePointer& scene) { _sceneContext->_scene = scene; } diff --git a/libraries/render/src/render/Task.h b/libraries/render/src/render/Task.h index 2ae2742fa1..7d74529b64 100644 --- a/libraries/render/src/render/Task.h +++ b/libraries/render/src/render/Task.h @@ -19,6 +19,8 @@ #include #include +#include "SettingHandle.h" + #include "Context.h" #include "gpu/Batch.h" @@ -59,10 +61,75 @@ class Job; class Task; class JobNoIO {}; +template class PersistentConfig : public C { +public: + const QString DEFAULT = "Default"; + const QString NONE = "None"; + + PersistentConfig() = delete; + PersistentConfig(const QString& path) : + _preset(QStringList() << "Render" << "Engine" << path, DEFAULT) { } + PersistentConfig(const QStringList& path) : + _preset(QStringList() << "Render" << "Engine" << path, DEFAULT) { } + PersistentConfig(const QString& path, bool enabled) : C(enabled), + _preset(QStringList() << "Render" << "Engine" << path, enabled ? DEFAULT : NONE) { } + PersistentConfig(const QStringList& path, bool enabled) : C(enabled), + _preset(QStringList() << "Render" << "Engine" << path, enabled ? DEFAULT : NONE) { } + + QStringList getPresetList() { + if (_presets.empty()) { + setPresetList(QJsonObject()); + } + return _presets.keys(); + } + + virtual void setPresetList(const QJsonObject& list) override { + assert(_presets.empty()); + + _default = toJsonValue(*this).toObject().toVariantMap(); + + _presets.unite(list.toVariantMap()); + if (C::alwaysEnabled || C::enabled) { + _presets.insert(DEFAULT, _default); + } + if (!C::alwaysEnabled) { + _presets.insert(NONE, QVariantMap{{ "enabled", false }}); + } + + auto preset = _preset.get(); + if (preset != _preset.getDefault() && _presets.contains(preset)) { + // Load the persisted configuration + C::load(_presets[preset].toMap()); + } + } + + QString getPreset() { return _preset.get(); } + + void setPreset(const QString& preset) { + _preset.set(preset); + if (_presets.contains(preset)) { + // Always start back at default to remain deterministic + QVariantMap config = _default; + QVariantMap presetConfig = _presets[preset].toMap(); + for (auto it = presetConfig.cbegin(); it != presetConfig.cend(); it++) { + config.insert(it.key(), it.value()); + } + C::load(config); + } + } + +protected: + QVariantMap _default; + QVariantMap _presets; + Setting::Handle _preset; +}; + // A default Config is always on; to create an enableable Config, use the ctor JobConfig(bool enabled) class JobConfig : public QObject { Q_OBJECT public: + using Persistent = PersistentConfig; + JobConfig() = default; JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} @@ -71,17 +138,31 @@ public: bool alwaysEnabled{ true }; bool enabled{ true }; + virtual void setPresetList(const QJsonObject& object) { + for (auto it = object.begin(); it != object.end(); it++) { + JobConfig* child = findChild(it.key(), Qt::FindDirectChildrenOnly); + if (child) { + child->setPresetList(it.value().toObject()); + } + } + } + // This must be named toJSON to integrate with the global scripting JSON object Q_INVOKABLE QString toJSON() { return QJsonDocument(toJsonValue(*this).toObject()).toJson(QJsonDocument::Compact); } - Q_INVOKABLE void load(const QVariantMap& map) { qObjectFromJsonValue(QJsonObject::fromVariantMap(map), *this); } + Q_INVOKABLE void load(const QVariantMap& map) { qObjectFromJsonValue(QJsonObject::fromVariantMap(map), *this); emit loaded(); } public slots: - void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); } + void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); emit loaded(); } + +signals: + void loaded(); }; class TaskConfig : public JobConfig { Q_OBJECT public: + using Persistent = PersistentConfig; + TaskConfig() = default ; TaskConfig(bool enabled) : JobConfig(enabled) {} @@ -95,15 +176,6 @@ public: return findChild(name); } - template void setJobEnabled(bool enable = true, std::string job = "") { - assert(getConfig(job)->alwaysEnabled != true); - getConfig(job)->enabled = enable; - refresh(); // trigger a Job->configure - } - template bool isJobEnabled(bool enable = true, std::string job = "") const { - return getConfig(job)->isEnabled(); - } - public slots: void refresh(); @@ -236,8 +308,7 @@ public: const Varying getInput() const { return _input; } const Varying getOutput() const { return _output; } - Model(const Varying& input, Data data = Data()) : Concept(std::make_shared()), _data(data), _input(input), _output(Output()) { - _config = _data._config; // use the data's config + Model(const Varying& input, Data data = Data()) : Concept(data._config), _data(data), _input(input), _output(Output()) { std::static_pointer_cast(_config)->init(&_data); applyConfiguration(); } @@ -273,9 +344,11 @@ public: config->setParent(_config.get()); config->setObjectName(name.c_str()); - // Connect dirty->refresh if defined + // Connect loaded->refresh + QObject::connect(config.get(), SIGNAL(loaded()), _config.get(), SLOT(refresh())); static const char* DIRTY_SIGNAL = "dirty()"; if (config->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { + // Connect dirty->refresh if defined QObject::connect(config.get(), SIGNAL(dirty()), _config.get(), SLOT(refresh())); } diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 5a04b37ca1..0b8140af1b 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -54,6 +54,7 @@ public: Spinner, Checkbox, Button, + ComboBox, // Special casing for an unusual preference Avatar }; @@ -236,6 +237,22 @@ protected: QString _placeholderText; }; +class ComboBoxPreference : public EditPreference { + Q_OBJECT + Q_PROPERTY(QStringList items READ getItems CONSTANT) + +public: + ComboBoxPreference(const QString& category, const QString& name, Getter getter, Setter setter) + : EditPreference(category, name, getter, setter) { } + Type getType() { return ComboBox; } + + const QStringList& getItems() { return _items; } + void setItems(const QStringList& items) { _items = items; } + +protected: + QStringList _items; +}; + class BrowsePreference : public EditPreference { Q_OBJECT Q_PROPERTY(QString browseLabel READ getBrowseLabel CONSTANT) diff --git a/libraries/shared/src/shared/JSONHelpers.cpp b/libraries/shared/src/shared/JSONHelpers.cpp index e717050055..c7cbf0e724 100644 --- a/libraries/shared/src/shared/JSONHelpers.cpp +++ b/libraries/shared/src/shared/JSONHelpers.cpp @@ -111,7 +111,7 @@ void qObjectFromJsonValue(const QJsonValue& j, QObject& o) { for (auto it = object.begin(); it != object.end(); it++) { std::string key = it.key().toStdString(); if (it.value().isObject()) { - QObject* child = o.findChild(key.c_str(), Qt::FindChildOption::FindDirectChildrenOnly); + QObject* child = o.findChild(key.c_str(), Qt::FindDirectChildrenOnly); if (child) { qObjectFromJsonValue(it.value(), *child); }