diff --git a/CMakeLists.txt b/CMakeLists.txt index 258bde1b60..d4b6a9cd08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -593,7 +593,6 @@ set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples) if (WEBGL) add_subdirectory(web/filament-js) add_subdirectory(web/samples) - add_subdirectory(${EXTERNAL}/imgui/tnt) endif() if (IS_HOST_PLATFORM) @@ -601,6 +600,7 @@ if (IS_HOST_PLATFORM) add_subdirectory(${LIBRARIES}/filamentapp) add_subdirectory(${LIBRARIES}/filagui) add_subdirectory(${LIBRARIES}/imageio) + add_subdirectory(${LIBRARIES}/viewer) add_subdirectory(${FILAMENT}/java/filamat) add_subdirectory(${FILAMENT}/java/filament) diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt index 278fc785eb..5f321d6369 100644 --- a/android/gltfio-android/CMakeLists.txt +++ b/android/gltfio-android/CMakeLists.txt @@ -29,7 +29,6 @@ set(GLTFIO_SRCS ${GLTFIO_DIR}/include/gltfio/AssetLoader.h ${GLTFIO_DIR}/include/gltfio/MaterialProvider.h ${GLTFIO_DIR}/include/gltfio/ResourceLoader.h - ${GLTFIO_DIR}/include/gltfio/SimpleViewer.h ${GLTFIO_DIR}/include/gltfio/FilamentAsset.h ${GLTFIO_DIR}/include/gltfio/FilamentInstance.h diff --git a/build/common/test_list.txt b/build/common/test_list.txt index 491ec64ab7..382927d75d 100644 --- a/build/common/test_list.txt +++ b/build/common/test_list.txt @@ -1,3 +1,4 @@ +libs/viewer/test_settings filament/test/test_filament --gtest_filter=-FilamentTest.FroxelData:FilamentExposureWithEngineTest.SetExposure:FilamentExposureWithEngineTest.ComputeEV100:RenderingTest.* filament/test/test_material_parser libs/math/test_math diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index 65bf14300a..19b6eb86b8 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -12,7 +12,6 @@ set(PUBLIC_HDRS include/gltfio/AssetLoader.h include/gltfio/MaterialProvider.h include/gltfio/ResourceLoader.h - include/gltfio/SimpleViewer.h include/gltfio/FilamentAsset.h include/gltfio/FilamentInstance.h ) diff --git a/libs/viewer/CMakeLists.txt b/libs/viewer/CMakeLists.txt new file mode 100644 index 0000000000..4e0148d0de --- /dev/null +++ b/libs/viewer/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.10) +project(viewer) + +set(TARGET viewer) +set(PUBLIC_HDR_DIR include) + +# ================================================================================================== +# Sources and headers +# ================================================================================================== +set(PUBLIC_HDRS + include/viewer/SimpleViewer.h + include/viewer/Settings.h +) + +set(SRCS + src/SimpleViewer.cpp + src/Settings.cpp +) + +# ================================================================================================== +# Include and target definitions +# ================================================================================================== +add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS}) +target_link_libraries(${TARGET} PUBLIC imgui cgltf filament gltfio filagui) +target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) + +# ================================================================================================== +# Compiler flags +# ================================================================================================== +if (MSVC) + target_compile_options(${TARGET} PRIVATE $<$:/fp:fast>) +else() + target_compile_options(${TARGET} PRIVATE $<$:-ffast-math>) + target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register) +endif() + +# ================================================================================================== +# Installation +# ================================================================================================== +install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR}) +install(DIRECTORY ${PUBLIC_HDR_DIR}/viewer DESTINATION include) + +# ================================================================================================== +# Tests +# ================================================================================================== +if (NOT ANDROID AND NOT WEBGL AND NOT IOS) + add_executable(test_settings tests/test_settings.cpp) + target_link_libraries(test_settings PRIVATE ${TARGET} gtest) +endif() diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h new file mode 100644 index 0000000000..19844beefd --- /dev/null +++ b/libs/viewer/include/viewer/Settings.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 mIcable 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. + */ + +#ifndef VIEWER_SETTINGS_H +#define VIEWER_SETTINGS_H + +#include +#include + +#include +#include + +namespace filament { +namespace viewer { + +struct ColorGradingSettings; +struct DynamicLightingSettings; +struct Settings; +struct ViewSettings; + +using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions; +using AntiAliasing = filament::View::AntiAliasing; +using BloomOptions = filament::View::BloomOptions; +using DepthOfFieldOptions = filament::View::DepthOfFieldOptions; +using Dithering = filament::View::Dithering; +using FogOptions = filament::View::FogOptions; +using RenderQuality = filament::View::RenderQuality; +using ShadowType = filament::View::ShadowType; +using TemporalAntiAliasingOptions = filament::View::TemporalAntiAliasingOptions; +using ToneMapping = filament::ColorGrading::ToneMapping; +using VignetteOptions = filament::View::VignetteOptions; + +// Reads the given JSON blob and updates the corresponding fields in the given Settings object. +// +// - The given JSON blob need not specify all settings. +// - Returns true if successful. +// - This function writes warnings and error messages into the utils log. +bool readJson(const char* jsonChunk, size_t size, Settings* out); + +// Generates human-readable JSON strings from settings objects. +std::string writeJson(const AmbientOcclusionOptions& out); +std::string writeJson(const BloomOptions& out); +std::string writeJson(const ColorGradingSettings& out); +std::string writeJson(const DepthOfFieldOptions& out); +std::string writeJson(const DynamicLightingSettings& out); +std::string writeJson(const FogOptions& out); +std::string writeJson(const RenderQuality& out); +std::string writeJson(const Settings& out); +std::string writeJson(const TemporalAntiAliasingOptions& out); +std::string writeJson(const ViewSettings& out); +std::string writeJson(const VignetteOptions& out); + +struct ColorGradingSettings { + bool enabled = true; + filament::ColorGrading::QualityLevel quality = filament::ColorGrading::QualityLevel::MEDIUM; + ToneMapping toneMapping = ToneMapping::ACES_LEGACY; + int temperature = 0; + int tint = 0; + math::float3 outRed{1.0f, 0.0f, 0.0f}; + math::float3 outGreen{0.0f, 1.0f, 0.0f}; + math::float3 outBlue{0.0f, 0.0f, 1.0f}; + math::float4 shadows{1.0f, 1.0f, 1.0f, 0.0f}; + math::float4 midtones{1.0f, 1.0f, 1.0f, 0.0f}; + math::float4 highlights{1.0f, 1.0f, 1.0f, 0.0f}; + math::float4 ranges{0.0f, 0.333f, 0.550f, 1.0f}; + float contrast = 1.0f; + float vibrance = 1.0f; + float saturation = 1.0f; + math::float3 slope{1.0f}; + math::float3 offset{0.0f}; + math::float3 power{1.0f}; + math::float3 gamma{1.0f}; + math::float3 midPoint{1.0f}; + math::float3 scale{1.0f}; + bool linkedCurves = false; +}; + +struct DynamicLightingSettings { + float zLightNear = 5; + float zLightFar = 100; +}; + +// This defines fields in the same order as the setter methods in filament::View. +struct ViewSettings { + uint8_t sampleCount = 1; + AntiAliasing antiAliasing = AntiAliasing::FXAA; + TemporalAntiAliasingOptions taa; + ColorGradingSettings colorGrading; + AmbientOcclusionOptions ssao; + BloomOptions bloom; + FogOptions fog; + DepthOfFieldOptions dof; + VignetteOptions vignette; + Dithering dithering = Dithering::TEMPORAL; + RenderQuality renderQuality; + DynamicLightingSettings dynamicLighting; + ShadowType shadowType = ShadowType::PCF; + bool postProcessingEnabled = true; +}; + +struct Settings { + ViewSettings view; +}; + +} // namespace viewer +} // namespace filament + +#endif // VIEWER_SETTINGS_H diff --git a/libs/viewer/include/viewer/SimpleViewer.h b/libs/viewer/include/viewer/SimpleViewer.h new file mode 100644 index 0000000000..06ddbbd886 --- /dev/null +++ b/libs/viewer/include/viewer/SimpleViewer.h @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 mIcable 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. + */ + +#ifndef VIEWER_SIMPLEVIEWER_H +#define VIEWER_SIMPLEVIEWER_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +namespace filament { +namespace viewer { + +/** + * \class SimpleViewer SimpleViewer.h gltfio/SimpleViewer.h + * \brief Manages the state for a simple glTF viewer with imgui controls and a tree view. + * + * This is a utility that can be used across multiple platforms, including web. + * + * \note If you don't need ImGui controls, there is no need to use this class, just use AssetLoader + * instead. + */ +class SimpleViewer { +public: + using Animator = gltfio::Animator; + using FilamentAsset = gltfio::FilamentAsset; + using FilamentInstance = gltfio::FilamentInstance; + + static constexpr int DEFAULT_SIDEBAR_WIDTH = 350; + + /** + * Constructs a SimpleViewer that has a fixed association with the given Filament objects. + * + * Upon construction, the simple viewer may create some additional Filament objects (such as + * light sources) that it owns. + */ + SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view, + int sidebarWidth = DEFAULT_SIDEBAR_WIDTH); + + /** + * Destroys the SimpleViewer and any Filament entities that it owns. + */ + ~SimpleViewer(); + + /** + * Adds the asset's ready-to-render entities into the scene and optionally transforms the root + * node to make it fit into a unit cube at the origin. + * + * The viewer does not claim ownership over the asset or its entities. Clients should use + * AssetLoader and ResourceLoader to load an asset before passing it in. + * + * @param asset The asset to view. + * @param scale Adds a transform to the root to fit the asset into a unit cube at the origin. + * @param instanceToAnimate Optional instance from which to get the animator. + */ + void populateScene(FilamentAsset* asset, bool scale, + FilamentInstance* instanceToAnimate = nullptr); + + /** + * Removes the current asset from the viewer. + * + * This removes all the asset entities from the Scene, but does not destroy them. + */ + void removeAsset(); + + /** + * Sets or changes the current scene's IBL to allow the UI manipulate it. + */ + void setIndirectLight(filament::IndirectLight* ibl, filament::math::float3 const* sh3); + + /** + * Applies the currently-selected glTF animation to the transformation hierarchy and updates + * the bone matrices on all renderables. + */ + void applyAnimation(double currentTime); + + /** + * Constructs ImGui controls for the current frame and responds to everything that the user has + * changed since the previous frame. + * + * If desired this can be used in conjunction with the filagui library, which allows clients to + * render ImGui controls with Filament. + */ + void updateUserInterface(); + + /** + * Retrieves the current width of the ImGui "window" which we are using as a sidebar. + * Clients can monitor this value to adjust the size of the view. + */ + int getSidebarWidth() const { return mSidebarWidth; } + + /** + * Allows clients to inject custom UI. + */ + void setUiCallback(std::function callback) { mCustomUI = callback; } + + /** + * Draws the bounding box of each renderable. + * Defaults to false. + */ + void enableWireframe(bool b) { mEnableWireframe = b; } + + /** + * Enables a built-in light source (useful for creating shadows). + * Defaults to true. + */ + void enableSunlight(bool b) { mEnableSunlight = b; } + + /** + * Enables dithering on the view. + * Defaults to true. + */ + void enableDithering(bool b) { mEnableDithering = b; } + + /** + * Enables FXAA antialiasing in the post-process pipeline. + * Defaults to true. + */ + void enableFxaa(bool b) { mEnableFxaa = b; } + + /** + * Enables hardware-based MSAA antialiasing. + * Defaults to true. + */ + void enableMsaa(bool b) { mEnableMsaa = b; } + + /** + * Enables screen-space ambient occlusion in the post-process pipeline. + * Defaults to true. + */ + void enableSSAO(bool b) { mSSAOOptions.enabled = b; } + + /** + * Enables Bloom. + * Defaults to true. + */ + void enableBloom(bool bloom) { + mBloomOptions.enabled = bloom; + } + + /** + * Adjusts the intensity of the IBL. + * See also filament::IndirectLight::setIntensity(). + * Defaults to 30000.0. + */ + void setIBLIntensity(float brightness) { mIblIntensity = brightness; } + +private: + void updateIndirectLight(); + + // Immutable properties set from the constructor. + filament::Engine* const mEngine; + filament::Scene* const mScene; + filament::View* const mView; + const utils::Entity mSunlight; + + // Properties that can be changed from the application. + FilamentAsset* mAsset = nullptr; + Animator* mAnimator = nullptr; + filament::IndirectLight* mIndirectLight = nullptr; + std::function mCustomUI; + + // Properties that can be changed from the UI. + int mCurrentAnimation = 1; + bool mResetAnimation = true; + float mIblIntensity = 30000.0f; + float mIblRotation = 0.0f; + float mSunlightIntensity = 100000.0f; // <-- This value is overridden when loading an IBL. + filament::math::float3 mSunlightColor = filament::Color::toLinear({ 0.98, 0.92, 0.89}); + filament::math::float3 mSunlightDirection = {0.6, -1.0, -0.8}; + bool mEnableWireframe = false; + bool mEnableSunlight = true; + bool mEnableVsm = false; + bool mEnableShadows = true; + int mShadowCascades = 1; + bool mEnableContactShadows = false; + std::array mSplitPositions = {0.25f, 0.50f, 0.75f}; + bool mEnableDithering = true; + bool mEnableFxaa = true; + bool mEnableMsaa = true; + filament::View::AmbientOcclusionOptions mSSAOOptions = { .enabled = true }; + filament::View::BloomOptions mBloomOptions = { .enabled = true }; + filament::View::FogOptions mFogOptions = {}; + filament::View::TemporalAntiAliasingOptions mTAAOptions = {}; + int mSidebarWidth; + uint32_t mFlags; +}; + +filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds); + +} // namespace viewer +} // namespace filament + +#endif // VIEWER_SIMPLEVIEWER_H diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp new file mode 100644 index 0000000000..d18fd851b4 --- /dev/null +++ b/libs/viewer/src/Settings.cpp @@ -0,0 +1,834 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 mIcable 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. + */ + +#include + +#include + +#include +#include + +#include + +// NOTE: We are including cgltf only to get JSMN. We should consider adding jsmn to third_party. +#define CGLTF_IMPLEMENTATION +#include + +#define CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return -1; } +#define CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return -1; } +#define STR(tok, jsonChunk) std::string(jsonChunk + tok.start, tok.end - tok.start) + +using namespace utils; + +namespace filament { +namespace viewer { + +// Compares a JSON string token against a C string. +static int compare(jsmntok_t tok, const char* jsonChunk, const char* str) { + size_t slen = strlen(str); + size_t tlen = tok.end - tok.start; + return (slen == tlen) ? strncmp(jsonChunk + tok.start, str, slen) : 128; +} + +// Skips over an unused token. +static int parse(jsmntok_t const* tokens, int i) { + int end = i + 1; + while (i < end) { + switch (tokens[i].type) { + case JSMN_OBJECT: + end += tokens[i].size * 2; break; + case JSMN_ARRAY: + end += tokens[i].size; break; + case JSMN_PRIMITIVE: + case JSMN_STRING: + break; + default: return -1; + } + i++; + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, uint8_t* val) { + CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + *val = strtol(jsonChunk + tokens[i].start, nullptr, 10); + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, uint32_t* val) { + CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + *val = strtol(jsonChunk + tokens[i].start, nullptr, 10); + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, int* val) { + CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + *val = strtol(jsonChunk + tokens[i].start, nullptr, 10); + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, float* val) { + CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + *val = strtod(jsonChunk + tokens[i].start, nullptr); + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, float* vals, int size) { + CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].size != size) { + slog.w << "Expected " << size << " floats, got " << tokens[i].size << io::endl; + return i + 1 + tokens[i].size; + } + ++i; + for (int j = 0; j < size; ++j) { + i = parse(tokens, i, jsonChunk, &vals[j]); + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, bool* val) { + CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + *val = 0 == compare(tokens[i], jsonChunk, "true"); + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, math::float3* val) { + float values[3]; + i = parse(tokens, i, jsonChunk, values, 3); + *val = {values[0], values[1], values[2]}; + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, math::float4* val) { + float values[4]; + i = parse(tokens, i, jsonChunk, values, 4); + *val = {values[0], values[1], values[2], values[3]}; + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AntiAliasing* out) { + if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = AntiAliasing::NONE; } + else if (0 == compare(tokens[i], jsonChunk, "FXAA")) { *out = AntiAliasing::FXAA; } + else { + slog.w << "Invalid AntiAliasing: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +using BlendMode = BloomOptions::BlendMode; + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BlendMode* out) { + if (0 == compare(tokens[i], jsonChunk, "ADD")) { *out = BlendMode::ADD; } + else if (0 == compare(tokens[i], jsonChunk, "INTERPOLATE")) { *out = BlendMode::INTERPOLATE; } + else { + slog.w << "Invalid BlendMode: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +using VQL = filament::View::QualityLevel; + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, VQL* out) { + if (0 == compare(tokens[i], jsonChunk, "LOW")) { *out = VQL::LOW; } + else if (0 == compare(tokens[i], jsonChunk, "MEDIUM")) { *out = VQL::MEDIUM; } + else if (0 == compare(tokens[i], jsonChunk, "HIGH")) { *out = VQL::HIGH; } + else if (0 == compare(tokens[i], jsonChunk, "ULTRA")) { *out = VQL::ULTRA; } + else { + slog.w << "Invalid QualityLevel: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +using CGQL = filament::ColorGrading::QualityLevel; + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, CGQL* out) { + if (0 == compare(tokens[i], jsonChunk, "LOW")) { *out = CGQL::LOW; } + else if (0 == compare(tokens[i], jsonChunk, "MEDIUM")) { *out = CGQL::MEDIUM; } + else if (0 == compare(tokens[i], jsonChunk, "HIGH")) { *out = CGQL::HIGH; } + else if (0 == compare(tokens[i], jsonChunk, "ULTRA")) { *out = CGQL::ULTRA; } + else { + slog.w << "Invalid QualityLevel: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ToneMapping* out) { + if (0 == compare(tokens[i], jsonChunk, "LINEAR")) { *out = ToneMapping::LINEAR; } + else if (0 == compare(tokens[i], jsonChunk, "ACES_LEGACY")) { *out = ToneMapping::ACES_LEGACY; } + else if (0 == compare(tokens[i], jsonChunk, "ACES")) { *out = ToneMapping::ACES; } + else if (0 == compare(tokens[i], jsonChunk, "FILMIC")) { *out = ToneMapping::FILMIC; } + else if (0 == compare(tokens[i], jsonChunk, "UCHIMURA")) { *out = ToneMapping::UCHIMURA; } + else if (0 == compare(tokens[i], jsonChunk, "REINHARD")) { *out = ToneMapping::REINHARD; } + else if (0 == compare(tokens[i], jsonChunk, "DISPLAY_RANGE")) { *out = ToneMapping::DISPLAY_RANGE; } + else { + slog.w << "Invalid ToneMapping: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Dithering* out) { + if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = Dithering::NONE; } + else if (0 == compare(tokens[i], jsonChunk, "TEMPORAL")) { *out = Dithering::TEMPORAL; } + else { + slog.w << "Invalid Dithering: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ShadowType* out) { + if (0 == compare(tokens[i], jsonChunk, "PCF")) { *out = ShadowType::PCF; } + else if (0 == compare(tokens[i], jsonChunk, "VSM")) { *out = ShadowType::VSM; } + else { + slog.w << "Invalid ShadowType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; + } + return i + 1; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, + TemporalAntiAliasingOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "filterWidth") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->filterWidth); + } else if (compare(tok, jsonChunk, "feedback") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->feedback); + } else if (compare(tok, jsonChunk, "enabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else { + slog.w << "Invalid taa key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid taa value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGradingSettings* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "enabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else if (compare(tok, jsonChunk, "quality") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->quality); + } else if (compare(tok, jsonChunk, "toneMapping") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->toneMapping); + } else if (compare(tok, jsonChunk, "temperature") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->temperature); + } else if (compare(tok, jsonChunk, "tint") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->tint); + } else if (compare(tok, jsonChunk, "outRed") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->outRed); + } else if (compare(tok, jsonChunk, "outGreen") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->outGreen); + } else if (compare(tok, jsonChunk, "outBlue") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->outBlue); + } else if (compare(tok, jsonChunk, "shadows") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->shadows); + } else if (compare(tok, jsonChunk, "midtones") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->midtones); + } else if (compare(tok, jsonChunk, "highlights") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->highlights); + } else if (compare(tok, jsonChunk, "ranges") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->ranges); + } else if (compare(tok, jsonChunk, "contrast") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->contrast); + } else if (compare(tok, jsonChunk, "vibrance") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->vibrance); + } else if (compare(tok, jsonChunk, "saturation") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->saturation); + } else if (compare(tok, jsonChunk, "slope") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->slope); + } else if (compare(tok, jsonChunk, "offset") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->offset); + } else if (compare(tok, jsonChunk, "power") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->power); + } else if (compare(tok, jsonChunk, "gamma") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->gamma); + } else if (compare(tok, jsonChunk, "midPoint") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->midPoint); + } else if (compare(tok, jsonChunk, "scale") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->scale); + } else if (compare(tok, jsonChunk, "linkedCurves") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->linkedCurves); + } else { + slog.w << "Invalid color grading key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid color grading value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, + AmbientOcclusionOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "radius") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->radius); + } else if (compare(tok, jsonChunk, "power") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->power); + } else if (compare(tok, jsonChunk, "bias") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->bias); + } else if (compare(tok, jsonChunk, "resolution") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->resolution); + } else if (compare(tok, jsonChunk, "intensity") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->intensity); + } else if (compare(tok, jsonChunk, "quality") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->quality); + } else if (compare(tok, jsonChunk, "upsampling") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->upsampling); + } else if (compare(tok, jsonChunk, "enabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else if (compare(tok, jsonChunk, "minHorizonAngleRad") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->minHorizonAngleRad); + } else { + slog.w << "Invalid AO key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid AO value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "strength") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->strength); + } else if (compare(tok, jsonChunk, "resolution") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->resolution); + } else if (compare(tok, jsonChunk, "anamorphism") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->anamorphism); + } else if (compare(tok, jsonChunk, "levels") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->levels); + } else if (compare(tok, jsonChunk, "blendMode") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->blendMode); + } else if (compare(tok, jsonChunk, "threshold") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->threshold); + } else if (compare(tok, jsonChunk, "enabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else if (compare(tok, jsonChunk, "highlight") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->highlight); + } else { + slog.w << "Invalid bloom options key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid bloom options value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, FogOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (0 == compare(tok, jsonChunk, "distance")) { + i = parse(tokens, i + 1, jsonChunk, &out->distance); + } else if (0 == compare(tok, jsonChunk, "maximumOpacity")) { + i = parse(tokens, i + 1, jsonChunk, &out->maximumOpacity); + } else if (0 == compare(tok, jsonChunk, "height")) { + i = parse(tokens, i + 1, jsonChunk, &out->height); + } else if (0 == compare(tok, jsonChunk, "heightFalloff")) { + i = parse(tokens, i + 1, jsonChunk, &out->heightFalloff); + } else if (0 == compare(tok, jsonChunk, "color")) { + i = parse(tokens, i + 1, jsonChunk, &out->color); + } else if (0 == compare(tok, jsonChunk, "density")) { + i = parse(tokens, i + 1, jsonChunk, &out->density); + } else if (0 == compare(tok, jsonChunk, "inScatteringStart")) { + i = parse(tokens, i + 1, jsonChunk, &out->inScatteringStart); + } else if (0 == compare(tok, jsonChunk, "inScatteringSize")) { + i = parse(tokens, i + 1, jsonChunk, &out->inScatteringSize); + } else if (0 == compare(tok, jsonChunk, "fogColorFromIbl")) { + i = parse(tokens, i + 1, jsonChunk, &out->fogColorFromIbl); + } else if (0 == compare(tok, jsonChunk, "enabled")) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else { + slog.w << "Invalid fog options key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid fog options value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (0 == compare(tok, jsonChunk, "focusDistance")) { + i = parse(tokens, i + 1, jsonChunk, &out->focusDistance); + } else if (0 == compare(tok, jsonChunk, "cocScale")) { + i = parse(tokens, i + 1, jsonChunk, &out->cocScale); + } else if (0 == compare(tok, jsonChunk, "maxApertureDiameter")) { + i = parse(tokens, i + 1, jsonChunk, &out->maxApertureDiameter); + } else if (0 == compare(tok, jsonChunk, "enabled")) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else { + slog.w << "Invalid dof options key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid dof options value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, VignetteOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (0 == compare(tok, jsonChunk, "midPoint")) { + i = parse(tokens, i + 1, jsonChunk, &out->midPoint); + } else if (0 == compare(tok, jsonChunk, "roundness")) { + i = parse(tokens, i + 1, jsonChunk, &out->roundness); + } else if (0 == compare(tok, jsonChunk, "feather")) { + i = parse(tokens, i + 1, jsonChunk, &out->feather); + } else if (0 == compare(tok, jsonChunk, "color")) { + i = parse(tokens, i + 1, jsonChunk, &out->color); + } else if (0 == compare(tok, jsonChunk, "enabled")) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else { + slog.w << "Invalid vignette options key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid vignette options value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, RenderQuality* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (0 == compare(tok, jsonChunk, "hdrColorBuffer")) { + i = parse(tokens, i + 1, jsonChunk, &out->hdrColorBuffer); + } else { + slog.w << "Invalid render quality key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid render quality value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, + DynamicLightingSettings* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (0 == compare(tok, jsonChunk, "zLightNear")) { + i = parse(tokens, i + 1, jsonChunk, &out->zLightNear); + } else if (0 == compare(tok, jsonChunk, "zLightFar")) { + i = parse(tokens, i + 1, jsonChunk, &out->zLightFar); + } else { + slog.w << "Invalid dynamic lighting key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid dynamic lighting value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewSettings* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "sampleCount") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->sampleCount); + } else if (compare(tok, jsonChunk, "antiAliasing") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->antiAliasing); + } else if (compare(tok, jsonChunk, "taa") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->taa); + } else if (compare(tok, jsonChunk, "colorGrading") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->colorGrading); + } else if (compare(tok, jsonChunk, "ssao") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->ssao); + } else if (compare(tok, jsonChunk, "bloom") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->bloom); + } else if (compare(tok, jsonChunk, "fog") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->fog); + } else if (compare(tok, jsonChunk, "dof") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->dof); + } else if (compare(tok, jsonChunk, "vignette") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->vignette); + } else if (compare(tok, jsonChunk, "dithering") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->dithering); + } else if (compare(tok, jsonChunk, "renderQuality") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->renderQuality); + } else if (compare(tok, jsonChunk, "dynamicLighting") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->dynamicLighting); + } else if (compare(tok, jsonChunk, "shadowType") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->shadowType); + } else if (compare(tok, jsonChunk, "postProcessingEnabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->postProcessingEnabled); + } else { + slog.w << "Invalid view setting key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid view setting value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "view") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->view); + } else { + slog.w << "Invalid group key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid group value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + +bool readJson(const char* jsonChunk, size_t size, Settings* out) { + jsmn_parser parser = { 0, 0, 0 }; + + int tokenCount = jsmn_parse(&parser, jsonChunk, size, nullptr, 0); + if (tokenCount <= 0) { + return false; + } + + jsmntok_t* tokens = (jsmntok_t*) malloc(sizeof(jsmntok_t) * tokenCount); + assert(tokens); + + jsmn_init(&parser); + tokenCount = jsmn_parse(&parser, jsonChunk, size, tokens, tokenCount); + + if (tokenCount <= 0) { + free(tokens); + return false; + } + + int i = parse(tokens, 0, jsonChunk, out); + free(tokens); + return i >= 0; +} + +static std::string writeJson(bool v) { return v ? "true" : "false"; } +static std::string writeJson(float v) { return std::to_string(v); } +static std::string writeJson(int v) { return std::to_string(v); } +static std::string writeJson(uint32_t v) { return std::to_string(v); } +static std::string writeJson(uint8_t v) { return std::to_string((int)v); } + +static const char* writeJson(AntiAliasing in) { + switch (in) { + case AntiAliasing::NONE: return "NONE"; + case AntiAliasing::FXAA: return "FXAA"; + } + return "INVALID"; +} + +static const char* writeJson(Dithering in) { + switch (in) { + case Dithering::NONE: return "NONE"; + case Dithering::TEMPORAL: return "TEMPORAL"; + } + return "INVALID"; +} + +static const char* writeJson(ShadowType in) { + switch (in) { + case ShadowType::PCF: return "PCF"; + case ShadowType::VSM: return "VSM"; + } + return "INVALID"; +} + +static const char* writeJson(BlendMode in) { + switch (in) { + case BlendMode::ADD: return "ADD"; + case BlendMode::INTERPOLATE: return "INTERPOLATE"; + } + return "INVALID"; +} + +static const char* writeJson(VQL in) { + switch (in) { + case VQL::LOW: return "LOW"; + case VQL::MEDIUM: return "MEDIUM"; + case VQL::HIGH: return "HIGH"; + case VQL::ULTRA: return "ULTRA"; + } + return "INVALID"; +} + +static const char* writeJson(CGQL in) { + switch (in) { + case CGQL::LOW: return "LOW"; + case CGQL::MEDIUM: return "MEDIUM"; + case CGQL::HIGH: return "HIGH"; + case CGQL::ULTRA: return "ULTRA"; + } + return "INVALID"; +} + +static const char* writeJson(ToneMapping in) { + switch (in) { + case ToneMapping::LINEAR: return "LINEAR"; + case ToneMapping::ACES_LEGACY: return "ACES_LEGACY"; + case ToneMapping::ACES: return "ACES"; + case ToneMapping::FILMIC: return "FILMIC"; + case ToneMapping::UCHIMURA: return "UCHIMURA"; + case ToneMapping::REINHARD: return "REINHARD"; + case ToneMapping::DISPLAY_RANGE: return "DISPLAY_RANGE"; + } + return "INVALID"; +} + +static std::string writeJson(const float* v, int count) { + std::ostringstream oss; + oss << "["; + for (int i = 0; i < count; i++) { + oss << v; + if (i < count - 1) { + oss << ", "; + } + } + oss << "]"; + return oss.str(); +} + +static std::string writeJson(math::float3 v) { + return writeJson(&v.x, 3); +} + +static std::string writeJson(math::float4 v) { + return writeJson(&v.x, 4); +} + +std::string writeJson(const Settings& in) { + std::ostringstream oss; + oss << "{\n" + << "\"view\": " + << writeJson(in.view) + << "}"; + return oss.str(); +} + +std::string writeJson(const TemporalAntiAliasingOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"filterWidth\": " << writeJson(in.filterWidth) << ",\n" + << "\"feedback\": " << writeJson(in.feedback) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const ColorGradingSettings& in) { + std::ostringstream oss; + oss << "{\n" + << "\"enabled\": " << writeJson(in.enabled) << ",\n" + << "\"quality\": " << writeJson(in.quality) << ",\n" + << "\"toneMapping\": " << writeJson(in.toneMapping) << ",\n" + << "\"temperature\": " << writeJson(in.temperature) << ",\n" + << "\"tint\": " << writeJson(in.tint) << ",\n" + << "\"outRed\": " << writeJson(in.outRed) << ",\n" + << "\"outGreen\": " << writeJson(in.outGreen) << ",\n" + << "\"outBlue\": " << writeJson(in.outBlue) << ",\n" + << "\"shadows\": " << writeJson(in.shadows) << ",\n" + << "\"midtones\": " << writeJson(in.midtones) << ",\n" + << "\"highlights\": " << writeJson(in.highlights) << ",\n" + << "\"ranges\": " << writeJson(in.ranges) << ",\n" + << "\"contrast\": " << writeJson(in.contrast) << ",\n" + << "\"vibrance\": " << writeJson(in.vibrance) << ",\n" + << "\"saturation\": " << writeJson(in.saturation) << ",\n" + << "\"slope\": " << writeJson(in.slope) << ",\n" + << "\"offset\": " << writeJson(in.offset) << ",\n" + << "\"power\": " << writeJson(in.power) << ",\n" + << "\"gamma\": " << writeJson(in.gamma) << ",\n" + << "\"midPoint\": " << writeJson(in.midPoint) << ",\n" + << "\"scale\": " << writeJson(in.scale) << ",\n" + << "\"linkedCurves\": " << writeJson(in.linkedCurves) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const AmbientOcclusionOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"radius\": " << writeJson(in.radius) << ",\n" + << "\"power\": " << writeJson(in.power) << ",\n" + << "\"bias\": " << writeJson(in.bias) << ",\n" + << "\"resolution\": " << writeJson(in.resolution) << ",\n" + << "\"intensity\": " << writeJson(in.intensity) << ",\n" + << "\"quality\": " << writeJson(in.quality) << ",\n" + << "\"upsampling\": " << writeJson(in.upsampling) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << ",\n" + << "\"minHorizonAngleRad\": " << writeJson(in.minHorizonAngleRad) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const BloomOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"strength\": " << writeJson(in.strength) << ",\n" + << "\"resolution\": " << writeJson(in.resolution) << ",\n" + << "\"anamorphism\": " << writeJson(in.anamorphism) << ",\n" + << "\"levels\": " << writeJson(in.levels) << ",\n" + << "\"blendMode\": " << writeJson(in.blendMode) << ",\n" + << "\"threshold\": " << writeJson(in.threshold) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << ",\n" + << "\"highlight\": " << writeJson(in.highlight) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const FogOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"distance\": " << writeJson(in.distance) << ",\n" + << "\"maximumOpacity\": " << writeJson(in.maximumOpacity) << ",\n" + << "\"height\": " << writeJson(in.height) << ",\n" + << "\"heightFalloff\": " << writeJson(in.heightFalloff) << ",\n" + << "\"color\": " << writeJson(in.color) << ",\n" + << "\"density\": " << writeJson(in.density) << ",\n" + << "\"inScatteringStart\": " << writeJson(in.inScatteringStart) << ",\n" + << "\"inScatteringSize\": " << writeJson(in.inScatteringSize) << ",\n" + << "\"fogColorFromIbl\": " << writeJson(in.fogColorFromIbl) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const DepthOfFieldOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"focusDistance\": " << writeJson(in.focusDistance) << ",\n" + << "\"cocScale\": " << writeJson(in.cocScale) << ",\n" + << "\"maxApertureDiameter\": " << writeJson(in.maxApertureDiameter) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const VignetteOptions& in) { + std::ostringstream oss; + oss << "{\n" + << "\"midPoint\": " << writeJson(in.midPoint) << ",\n" + << "\"roundness\": " << writeJson(in.roundness) << ",\n" + << "\"feather\": " << writeJson(in.feather) << ",\n" + << "\"color\": " << writeJson(in.color) << ",\n" + << "\"enabled\": " << writeJson(in.enabled) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const RenderQuality& in) { + std::ostringstream oss; + oss << "{\n" + << "\"hdrColorBuffer\": " << writeJson(in.hdrColorBuffer) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const DynamicLightingSettings& in) { + std::ostringstream oss; + oss << "{\n" + << "\"zLightNear\": " << writeJson(in.zLightNear) << ",\n" + << "\"zLightFar\": " << writeJson(in.zLightFar) << "\n" + << "}"; + return oss.str(); +} + +std::string writeJson(const ViewSettings& in) { + std::ostringstream oss; + oss << "{\n" + << "\"sampleCount\": " << writeJson(in.sampleCount) << ",\n" + << "\"antiAliasing\": " << writeJson(in.antiAliasing) << ",\n" + << "\"taa\": " << writeJson(in.taa) << ",\n" + << "\"colorGrading\": " << writeJson(in.colorGrading) << ",\n" + << "\"ssao\": " << writeJson(in.ssao) << ",\n" + << "\"bloom\": " << writeJson(in.bloom) << ",\n" + << "\"fog\": " << writeJson(in.fog) << ",\n" + << "\"dof\": " << writeJson(in.dof) << ",\n" + << "\"vignette\": " << writeJson(in.vignette) << ",\n" + << "\"dithering\": " << writeJson(in.dithering) << ",\n" + << "\"renderQuality\": " << writeJson(in.renderQuality) << ",\n" + << "\"dynamicLighting\": " << writeJson(in.dynamicLighting) << ",\n" + << "\"shadowType\": " << writeJson(in.shadowType) << ",\n" + << "\"postProcessingEnabled\": " << writeJson(in.postProcessingEnabled) << "\n" + << "}"; + return oss.str(); +} + + +} // namespace viewer +} // namespace filament diff --git a/libs/gltfio/include/gltfio/SimpleViewer.h b/libs/viewer/src/SimpleViewer.cpp similarity index 69% rename from libs/gltfio/include/gltfio/SimpleViewer.h rename to libs/viewer/src/SimpleViewer.cpp index d4b9dcb3b8..4ea7b8a9bd 100644 --- a/libs/gltfio/include/gltfio/SimpleViewer.h +++ b/libs/viewer/src/SimpleViewer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,209 +14,7 @@ * limitations under the License. */ -#ifndef GLTFIO_SIMPLEVIEWER_H -#define GLTFIO_SIMPLEVIEWER_H - -// NOTE: This is an optional header-only utility to avoid a hard dependency on imgui. To use -// SimpleViewer, please add: -// -// #define GLTFIO_SIMPLEVIEWER_IMPLEMENTATION -// -// to one of your CPP source files to create the implementation. See gltf_viewer.cpp for an example. - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -namespace gltfio { - -/** - * \class SimpleViewer SimpleViewer.h gltfio/SimpleViewer.h - * \brief Manages the state for a simple glTF viewer with imgui controls and a tree view. - * - * This is a utility that can be used across multiple platforms, including web. - * - * \note If you don't need ImGui controls, there is no need to use this class, just use AssetLoader - * instead. - */ -class SimpleViewer { -public: - - static constexpr int DEFAULT_SIDEBAR_WIDTH = 350; - - /** - * Constructs a SimpleViewer that has a fixed association with the given Filament objects. - * - * Upon construction, the simple viewer may create some additional Filament objects (such as - * light sources) that it owns. - */ - SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view, - int sidebarWidth = DEFAULT_SIDEBAR_WIDTH); - - /** - * Destroys the SimpleViewer and any Filament entities that it owns. - */ - ~SimpleViewer(); - - /** - * Adds the asset's ready-to-render entities into the scene and optionally transforms the root - * node to make it fit into a unit cube at the origin. - * - * The viewer does not claim ownership over the asset or its entities. Clients should use - * AssetLoader and ResourceLoader to load an asset before passing it in. - * - * @param asset The asset to view. - * @param scale Adds a transform to the root to fit the asset into a unit cube at the origin. - * @param instanceToAnimate Optional instance from which to get the animator. - */ - void populateScene(FilamentAsset* asset, bool scale, - FilamentInstance* instanceToAnimate = nullptr); - - /** - * Removes the current asset from the viewer. - * - * This removes all the asset entities from the Scene, but does not destroy them. - */ - void removeAsset(); - - /** - * Sets or changes the current scene's IBL to allow the UI manipulate it. - */ - void setIndirectLight(filament::IndirectLight* ibl, filament::math::float3 const* sh3); - - /** - * Applies the currently-selected glTF animation to the transformation hierarchy and updates - * the bone matrices on all renderables. - */ - void applyAnimation(double currentTime); - - /** - * Constructs ImGui controls for the current frame and responds to everything that the user has - * changed since the previous frame. - * - * If desired this can be used in conjunction with the filagui library, which allows clients to - * render ImGui controls with Filament. - */ - void updateUserInterface(); - - /** - * Retrieves the current width of the ImGui "window" which we are using as a sidebar. - * Clients can monitor this value to adjust the size of the view. - */ - int getSidebarWidth() const { return mSidebarWidth; } - - /** - * Allows clients to inject custom UI. - */ - void setUiCallback(std::function callback) { mCustomUI = callback; } - - /** - * Draws the bounding box of each renderable. - * Defaults to false. - */ - void enableWireframe(bool b) { mEnableWireframe = b; } - - /** - * Enables a built-in light source (useful for creating shadows). - * Defaults to true. - */ - void enableSunlight(bool b) { mEnableSunlight = b; } - - /** - * Enables dithering on the view. - * Defaults to true. - */ - void enableDithering(bool b) { mEnableDithering = b; } - - /** - * Enables FXAA antialiasing in the post-process pipeline. - * Defaults to true. - */ - void enableFxaa(bool b) { mEnableFxaa = b; } - - /** - * Enables hardware-based MSAA antialiasing. - * Defaults to true. - */ - void enableMsaa(bool b) { mEnableMsaa = b; } - - /** - * Enables screen-space ambient occlusion in the post-process pipeline. - * Defaults to true. - */ - void enableSSAO(bool b) { mSSAOOptions.enabled = b; } - - /** - * Enables Bloom. - * Defaults to true. - */ - void enableBloom(bool bloom) { - mBloomOptions.enabled = bloom; - } - - /** - * Adjusts the intensity of the IBL. - * See also filament::IndirectLight::setIntensity(). - * Defaults to 30000.0. - */ - void setIBLIntensity(float brightness) { mIblIntensity = brightness; } - -private: - void updateIndirectLight(); - - // Immutable properties set from the constructor. - filament::Engine* const mEngine; - filament::Scene* const mScene; - filament::View* const mView; - const utils::Entity mSunlight; - - // Properties that can be changed from the application. - FilamentAsset* mAsset = nullptr; - Animator* mAnimator = nullptr; - filament::IndirectLight* mIndirectLight = nullptr; - std::function mCustomUI; - - // Properties that can be changed from the UI. - int mCurrentAnimation = 1; - bool mResetAnimation = true; - float mIblIntensity = 30000.0f; - float mIblRotation = 0.0f; - float mSunlightIntensity = 100000.0f; // <-- This value is overridden when loading an IBL. - filament::math::float3 mSunlightColor = filament::Color::toLinear({ 0.98, 0.92, 0.89}); - filament::math::float3 mSunlightDirection = {0.6, -1.0, -0.8}; - bool mEnableWireframe = false; - bool mEnableSunlight = true; - bool mEnableVsm = false; - bool mEnableShadows = true; - int mShadowCascades = 1; - bool mEnableContactShadows = false; - std::array mSplitPositions = {0.25f, 0.50f, 0.75f}; - bool mEnableDithering = true; - bool mEnableFxaa = true; - bool mEnableMsaa = true; - filament::View::AmbientOcclusionOptions mSSAOOptions = { .enabled = true }; - filament::View::BloomOptions mBloomOptions = { .enabled = true }; - filament::View::FogOptions mFogOptions = {}; - filament::View::TemporalAntiAliasingOptions mTAAOptions = {}; - int mSidebarWidth; - uint32_t mFlags; -}; - -filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds); - -} // namespace gltfio - -#ifdef GLTFIO_SIMPLEVIEWER_IMPLEMENTATION +#include #include #include @@ -231,9 +29,11 @@ filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds); #include #include +#include #include -namespace gltfio { +namespace filament { +namespace viewer { filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds) { using namespace filament::math; @@ -603,8 +403,5 @@ void SimpleViewer::updateUserInterface() { updateIndirectLight(); } -} // namespace gltfio - -#endif // GLTFIO_SIMPLEVIEWER_IMPLEMENTATION - -#endif // GLTFIO_SIMPLEVIEWER_H +} // namespace viewer +} // namespace filament diff --git a/libs/viewer/tests/test_settings.cpp b/libs/viewer/tests/test_settings.cpp new file mode 100644 index 0000000000..c7c5e5b812 --- /dev/null +++ b/libs/viewer/tests/test_settings.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * 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. + */ + +#include + +#include + +using namespace filament::viewer; + +class ViewSettingsTest : public testing::Test {}; + +static const char* JSON_TEST_DEFAULTS = R"TXT( +{ + "view": { + "sampleCount": 1, + "antiAliasing": "FXAA", + "taa": { + "enabled": false, + "filterWidth": 1.0, + "feedback": 0.04 + }, + "colorGrading": { + "enabled": true, + "quality": "MEDIUM", + "toneMapping": "ACES_LEGACY", + "temperature": 0, + "tint": 0, + "outRed": [1.0, 0.0, 0.0], + "outGreen": [0.0, 1.0, 0.0], + "outBlue": [0.0, 0.0, 1.0], + "shadows": [1.0, 1.0, 1.0, 0.0], + "midtones": [1.0, 1.0, 1.0, 0.0], + "highlights": [1.0, 1.0, 1.0, 0.0], + "ranges": [0.0, 0.333, 0.550, 1.0], + "contrast": 1.0, + "vibrance": 1.0, + "saturation": 1.0, + "slope": [1.0, 1.0, 1.0], + "offset": [0.0, 0.0, 0.0], + "power": [1.0, 1.0, 1.0], + "gamma": [1.0, 1.0, 1.0], + "midPoint": [1.0, 1.0, 1.0], + "scale": [1.0, 1.0, 1.0], + "linkedCurves": false + }, + "ssao": { + "enabled": false, + "radius": 0.3, + "power": 1.0, + "bias": 0.0005, + "resolution": 0.5, + "intensity": 1.0, + "quality": "LOW", + "upsampling": "LOW", + "minHorizonAngleRad": 0.0 + }, + "bloom": { + "enabled": false, + "strength": 0.10, + "resolution": 360, + "anamorphism": 1.0, + "levels": 6, + "blendMode": "ADD", + "threshold": true, + "highlight": 1000.0 + }, + "fog": { + "enabled": false, + "distance": 0.0, + "maximumOpacity": 1.0, + "height": 0.0, + "heightFalloff": 1.0, + "color": [0.5, 0.5, 0.5], + "density": 0.1, + "inScatteringStart": 0.0, + "inScatteringSize": -1.0, + "fogColorFromIbl": false + }, + "dof": { + "enabled": false, + "focusDistance": 10.0, + "cocScale": 1.0, + "maxApertureDiameter": 0.01 + }, + "vignette": { + "enabled": false, + "midPoint": 0.5, + "roundness": 0.5, + "feather": 0.5, + "color": [0, 0, 0, 1] + }, + "dithering": "TEMPORAL", + "renderQuality": { + "hdrColorBuffer": "HIGH" + }, + "dynamicLighting": { + "zLightNear": 5, + "zLightFar": 100, + }, + "shadowType": "PCF", + "postProcessingEnabled": true + } +} +)TXT"; + +TEST_F(ViewSettingsTest, JsonTestDefaults) { + Settings settings1 = {0}; + ASSERT_TRUE(readJson(JSON_TEST_DEFAULTS, strlen(JSON_TEST_DEFAULTS), &settings1)); + + ASSERT_TRUE(settings1.view.bloom.threshold); + + Settings settings2; + ASSERT_TRUE(readJson("{}", strlen("{}"), &settings2)); + ASSERT_EQ(writeJson(settings1), writeJson(settings2)); + + Settings settings3; + ASSERT_EQ(writeJson(settings1), writeJson(settings3)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 4f45693aa6..feb27bbc5f 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -240,8 +240,8 @@ if (NOT ANDROID) # Sample app specific target_link_libraries(frame_generator PRIVATE imageio) - target_link_libraries(gltf_viewer PRIVATE gltf-resources gltfio) - target_link_libraries(gltf_instances PRIVATE gltf-resources gltfio) + target_link_libraries(gltf_viewer PRIVATE gltf-resources gltfio viewer) + target_link_libraries(gltf_instances PRIVATE gltf-resources gltfio viewer) target_link_libraries(hellopbr PRIVATE filameshio suzanne-resources) target_link_libraries(sample_cloth PRIVATE filameshio) target_link_libraries(sample_normal_map PRIVATE filameshio) diff --git a/samples/gltf_instances.cpp b/samples/gltf_instances.cpp index 6bb5bf58c4..2540b9ce05 100644 --- a/samples/gltf_instances.cpp +++ b/samples/gltf_instances.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define GLTFIO_SIMPLEVIEWER_IMPLEMENTATION - #include #include #include @@ -23,12 +21,14 @@ #include #include #include +#include #include #include #include #include -#include + +#include #include @@ -46,6 +46,8 @@ using namespace filament; using namespace filament::math; +using namespace filament::viewer; + using namespace gltfio; using namespace utils; diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 7465982bd2..aeff0016c9 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define GLTFIO_SIMPLEVIEWER_IMPLEMENTATION - #include #include #include @@ -24,8 +22,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -33,7 +33,8 @@ #include #include #include -#include + +#include #include @@ -46,6 +47,9 @@ #include #include +#include +#include + #include #include #include @@ -54,6 +58,8 @@ using namespace filament; using namespace filament::math; +using namespace filament::viewer; + using namespace gltfio; using namespace utils;