/*
 * Copyright (C) 2018 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 <iostream>
#include <string>
#include <map>
#include <vector>

#include <getopt/getopt.h>

#include <imgui.h>

#include <filagui/ImGuiExtensions.h>

#include <utils/Path.h>

#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/Exposure.h>
#include <filament/DebugRegistry.h>
#include <filament/IndirectLight.h>
#include <filament/IndexBuffer.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/Renderer.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <filament/View.h>
#include <filament/VertexBuffer.h>

#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec4.h>
#include <math/norm.h>

#include <filamentapp/Config.h>
#include <filamentapp/IBL.h>
#include <filamentapp/FilamentApp.h>
#include <filamentapp/MeshAssimp.h>

#include "material_sandbox.h"

using namespace filament::math;
using namespace filament;
using namespace filamat;
using namespace utils;

static std::vector<Path> g_filenames;

static Scene* g_scene = nullptr;

std::unique_ptr<MeshAssimp> g_meshSet;
static std::map<std::string, MaterialInstance*> g_meshMaterialInstances;
static SandboxParameters g_params;
static ColorGradingOptions g_lastColorGradingOptions;
static ColorGrading* g_colorGrading = nullptr;
static Config g_config;
static bool g_shadowPlane = false;
static bool g_singleMode = false;
static float g_rangePlot[1024 * 3];
static float g_curvePlot[1024 * 3];

const static ImVec2 verticalSliderSize(18.0f, 160.0f);
const static ImVec2 plotLinesSize(320.0f, 160.0f);
const static ImVec2 plotLinesWideSize(480.0f, 120.0f);

static void printUsage(char* name) {
    std::string exec_name(Path(name).getName());
    std::string usage(
            "SAMPLE_MATERIAL showcases all material models\n"
            "Usage:\n"
            "    SAMPLE_MATERIAL [options] <mesh files (.obj, .fbx)>\n"
            "Options:\n"
            "   --help, -h\n"
            "       Prints this message\n\n"
            "   --api, -a\n"
            "       Specify the backend API: opengl (default), vulkan, or metal\n\n"
            "   --ibl=<path>, -i <path>\n"
            "       Applies an IBL\n\n"
            "       path can either be a directory containing IBL data files generated by cmgen,\n"
            "       or, a .hdr equiretangular image file\n\n"
            "   --split-view, -v\n"
            "       Splits the window into 4 views\n\n"
            "   --scale=[number], -s [number]\n"
            "       Applies uniform scale\n\n"
            "   --shadow-plane, -p\n"
            "       Enable shadow plane\n\n"
            "   --single\n"
            "       Only apply the edited material to the first renderable in the scene\n\n"
            "   --dirt\n"
            "       Specify a dirt texture\n\n"
            "   --camera=<camera mode>, -c <camera mode>\n"
            "       Set the camera mode: orbit (default) or flight\n"
            "       Flight mode uses the following controls:\n"
            "           Click and drag the mouse to pan the camera\n"
            "           Use the scroll weel to adjust movement speed\n"
            "           W / S: forward / backward\n"
            "           A / D: left / right\n"
            "           E / Q: up / down\n"
    );
    const std::string from("SAMPLE_MATERIAL");
    for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
        usage.replace(pos, from.length(), exec_name);
    }
    std::cout << usage;
}

static int handleCommandLineArgments(int argc, char* argv[], Config* config) {
    static constexpr const char* OPTSTR = "ha:vps:i:d:c:";
    static const struct option OPTIONS[] = {
            { "help",         no_argument,       nullptr, 'h' },
            { "api",          required_argument, nullptr, 'a' },
            { "ibl",          required_argument, nullptr, 'i' },
            { "split-view",   no_argument,       nullptr, 'v' },
            { "scale",        required_argument, nullptr, 's' },
            { "shadow-plane", no_argument,       nullptr, 'p' },
            { "single",       no_argument,       nullptr, 'n' },
            { "dirt",         required_argument, nullptr, 'd' },
            { "camera",       required_argument, nullptr, 'c' },
            { nullptr, 0, nullptr, 0 }  // termination of the option list
    };
    int opt;
    int option_index = 0;
    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
        std::string arg(optarg ? optarg : "");
        switch (opt) {
            default:
            case 'h':
                printUsage(argv[0]);
                exit(0);
            case 'a':
                if (arg == "opengl") {
                    config->backend = Engine::Backend::OPENGL;
                } else if (arg == "vulkan") {
                    config->backend = Engine::Backend::VULKAN;
                } else if (arg == "metal") {
                    config->backend = Engine::Backend::METAL;
                } else {
                    std::cerr << "Unrecognized backend. Must be 'opengl'|'vulkan'|'metal'." << std::endl;
                }
                break;
            case 'c':
                if (arg == "flight") {
                    config->cameraMode = camutils::Mode::FREE_FLIGHT;
                } else if (arg == "orbit") {
                    config->cameraMode = camutils::Mode::ORBIT;
                } else {
                    std::cerr << "Unrecognized camera mode. Must be 'flight'|'orbit'.\n";
                }
                break;
            case 'i':
                config->iblDirectory = arg;
                break;
            case 's':
                try {
                    config->scale = std::stof(arg);
                } catch (std::invalid_argument& e) {
                    // keep scale of 1.0
                } catch (std::out_of_range& e) {
                    // keep scale of 1.0
                }
                break;
            case 'v':
                config->splitView = true;
                break;
            case 'p':
                g_shadowPlane = true;
                break;
            case 'n':
                g_singleMode = true;
                break;
            case 'd':
                config->dirt =  arg;
                break;
        }
    }

    return optind;
}

static void cleanup(Engine* engine, View*, Scene*) {
    for (const auto& material : g_meshMaterialInstances) {
        engine->destroy(material.second);
    }

    for (auto& i : g_params.materialInstance) {
        engine->destroy(i);
    }

    for (auto& i : g_params.material) {
        engine->destroy(i);
    }

    g_meshSet.reset(nullptr);

    engine->destroy(g_params.light);
    engine->destroy(g_params.spotLight);
    engine->destroy(g_colorGrading);

    EntityManager& em = EntityManager::get();
    em.destroy(g_params.light);
    em.destroy(g_params.spotLight);
}

static void setup(Engine* engine, View*, Scene* scene) {
    g_scene = scene;

    g_meshSet = std::make_unique<MeshAssimp>(*engine);

    createInstances(g_params, *engine);

    for (auto& filename : g_filenames) {
        g_meshSet->addFromFile(filename, g_meshMaterialInstances);
    }

    auto& tcm = engine->getTransformManager();
    auto ei = tcm.getInstance(g_meshSet->getRenderables()[0]);
    tcm.setTransform(ei, mat4f{ mat3f(g_config.scale), float3(0.0f, 0.0f, -4.0f) } *
            tcm.getWorldTransform(ei));

    size_t count = 0;
    auto& rcm = engine->getRenderableManager();
    for (auto renderable : g_meshSet->getRenderables()) {
        auto instance = rcm.getInstance(renderable);
        if (!instance) continue;

        rcm.setCastShadows(instance, g_params.castShadows);
        rcm.setScreenSpaceContactShadows(instance, true);

        if (!g_singleMode || count == 0) {
            for (size_t i = 0; i < rcm.getPrimitiveCount(instance); i++) {
                rcm.setMaterialInstanceAt(instance, i, g_params.materialInstance[MATERIAL_LIT]);
            }
        } else {
            ei = tcm.getInstance(renderable);
            tcm.setTransform(ei, mat4f{ mat3f(g_config.scale), float3(0.0f, 0.0f, -3.0f) } *
                    tcm.getWorldTransform(ei));
        }
        count++;

        scene->addEntity(renderable);
    }

    scene->addEntity(g_params.light);

    // Parent the spot light to the root renderable in the mesh.
    tcm.create(g_params.spotLight, tcm.getInstance(g_meshSet->getRenderables()[0]));
    g_params.spotLightPosition = float3{0.0, 1.0, 0.0f};

    if (g_shadowPlane) {
        EntityManager& em = EntityManager::get();
        Material* shadowMaterial = Material::Builder()
                .package(RESOURCES_GROUNDSHADOW_DATA, RESOURCES_GROUNDSHADOW_SIZE)
                .build(*engine);
        shadowMaterial->setDefaultParameter("strength", 0.7f);

        const static uint32_t indices[] = {
                0, 1, 2, 2, 3, 0
        };

        const static filament::math::float3 vertices[] = {
                { -10, 0, -10 },
                { -10, 0,  10 },
                {  10, 0,  10 },
                {  10, 0, -10 },
        };

        short4 tbn = filament::math::packSnorm16(
                mat3f::packTangentFrame(
                        filament::math::mat3f{
                                float3{ 1.0f, 0.0f, 0.0f },
                                float3{ 0.0f, 0.0f, 1.0f },
                                float3{ 0.0f, 1.0f, 0.0f }
                        }
                ).xyzw);

        const static filament::math::short4 normals[] { tbn, tbn, tbn, tbn };

        VertexBuffer* vertexBuffer = VertexBuffer::Builder()
                .vertexCount(4)
                .bufferCount(2)
                .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
                .attribute(VertexAttribute::TANGENTS, 1, VertexBuffer::AttributeType::SHORT4)
                .normalized(VertexAttribute::TANGENTS)
                .build(*engine);

        vertexBuffer->setBufferAt(*engine, 0, VertexBuffer::BufferDescriptor(
                vertices, vertexBuffer->getVertexCount() * sizeof(vertices[0])));
        vertexBuffer->setBufferAt(*engine, 1, VertexBuffer::BufferDescriptor(
                normals, vertexBuffer->getVertexCount() * sizeof(normals[0])));

        IndexBuffer* indexBuffer = IndexBuffer::Builder()
                .indexCount(6)
                .build(*engine);

        indexBuffer->setBuffer(*engine, IndexBuffer::BufferDescriptor(
                indices, indexBuffer->getIndexCount() * sizeof(uint32_t)));

        Entity planeRenderable = em.create();
        RenderableManager::Builder(1)
                .boundingBox({{ 0, 0, 0 },
                              { 10, 1e-4f, 10 }})
                .material(0, shadowMaterial->getDefaultInstance())
                .geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
                        vertexBuffer, indexBuffer, 0, 6)
                .culling(false)
                .receiveShadows(true)
                .castShadows(false)
                .build(*engine, planeRenderable);

        scene->addEntity(planeRenderable);

        tcm.setTransform(tcm.getInstance(planeRenderable),
                filament::math::mat4f::translation(float3{ 0, -1, -4 }));
    }

    auto* ibl = FilamentApp::get().getIBL();
    if (ibl) {
        auto& params = g_params;
        IndirectLight* const pIndirectLight = ibl->getIndirectLight();
        params.lightDirection = IndirectLight::getDirectionEstimate(ibl->getSphericalHarmonics());
        float4 c = pIndirectLight->getColorEstimate(ibl->getSphericalHarmonics(), params.lightDirection);
        params.lightIntensity = c.w * pIndirectLight->getIntensity();
        params.lightColor = c.rgb;
    }

    g_params.bloomOptions.dirt = FilamentApp::get().getDirtTexture();
}

static filament::MaterialInstance* updateInstances(
        SandboxParameters& params, filament::Engine& engine) {

    int material = params.currentMaterialModel;
    if (material == MATERIAL_MODEL_LIT) {
        if (params.currentBlending == BLENDING_TRANSPARENT) material = MATERIAL_TRANSPARENT;
        if (params.currentBlending == BLENDING_FADE) material = MATERIAL_FADE;
        if (params.ssr) {
            if (params.currentBlending == BLENDING_THIN_REFRACTION) material = MATERIAL_THIN_SS_REFRACTION;
            if (params.currentBlending == BLENDING_SOLID_REFRACTION) material = MATERIAL_SOLID_SS_REFRACTION;
        } else {
            if (params.currentBlending == BLENDING_THIN_REFRACTION) material = MATERIAL_THIN_REFRACTION;
            if (params.currentBlending == BLENDING_SOLID_REFRACTION) material = MATERIAL_SOLID_REFRACTION;
        }
    }

    bool hasRefraction = params.currentBlending == BLENDING_THIN_REFRACTION ||
            params.currentBlending == BLENDING_SOLID_REFRACTION;

    MaterialInstance* materialInstance = params.materialInstance[material];
    materialInstance->setParameter("baseColor", RgbType::sRGB, params.color);

    if (params.currentMaterialModel != MATERIAL_MODEL_CLOTH) {
        math::float4 emissive(Color::toLinear(params.emissiveColor), params.emissiveExposureWeight);
        emissive.rgb *= Exposure::luminance(params.emissiveEV);
        materialInstance->setParameter("emissive", emissive);
    }

    if (params.currentMaterialModel == MATERIAL_MODEL_LIT) {
        materialInstance->setParameter("roughness", params.roughness);
        materialInstance->setParameter("metallic", params.metallic);
        if (!hasRefraction) {
            materialInstance->setParameter("reflectance", params.reflectance);
        }
        materialInstance->setParameter("sheenColor", RgbType::sRGB, params.sheenColor);
        materialInstance->setParameter("sheenRoughness", params.sheenRoughness);
        materialInstance->setParameter("clearCoat", params.clearCoat);
        materialInstance->setParameter("clearCoatRoughness", params.clearCoatRoughness);
        materialInstance->setParameter("anisotropy", params.anisotropy);

        if (params.currentBlending != BLENDING_OPAQUE) {
            materialInstance->setParameter("alpha", params.alpha);
        }

        if  (hasRefraction) {
            math::float3 color = Color::toLinear(params.transmittanceColor);
            materialInstance->setParameter("absorption",
                    Color::absorptionAtDistance(color, params.distance));
            materialInstance->setParameter("ior", params.ior);
            materialInstance->setParameter("transmission", params.transmission);
            materialInstance->setParameter("thickness", params.thickness);
        }
    }

    if (params.currentMaterialModel == MATERIAL_MODEL_SPECGLOSS) {
        materialInstance->setParameter("glossiness", params.glossiness);
        materialInstance->setParameter("specularColor", params.specularColor);
        materialInstance->setParameter("reflectance", params.reflectance);
        materialInstance->setParameter("clearCoat", params.clearCoat);
        materialInstance->setParameter("clearCoatRoughness", params.clearCoatRoughness);
        materialInstance->setParameter("anisotropy", params.anisotropy);
    }

    if (params.currentMaterialModel == MATERIAL_MODEL_SUBSURFACE) {
        materialInstance->setParameter("roughness", params.roughness);
        materialInstance->setParameter("metallic", params.metallic);
        materialInstance->setParameter("reflectance", params.reflectance);
        materialInstance->setParameter("thickness", params.thickness);
        materialInstance->setParameter("subsurfacePower", params.subsurfacePower);
        materialInstance->setParameter("subsurfaceColor", RgbType::sRGB, params.subsurfaceColor);
    }

    if (params.currentMaterialModel == MATERIAL_MODEL_CLOTH) {
        materialInstance->setParameter("roughness", params.roughness);
        materialInstance->setParameter("sheenColor", RgbType::sRGB, params.sheenColor);
        materialInstance->setParameter("subsurfaceColor", RgbType::sRGB, params.subsurfaceColor);
    }

    if (params.currentMaterialModel != MATERIAL_MODEL_UNLIT) {
        materialInstance->setSpecularAntiAliasingVariance(params.specularAntiAliasingVariance);
        materialInstance->setSpecularAntiAliasingThreshold(params.specularAntiAliasingThreshold);
    }

    return materialInstance;
}

static void computeRangePlot(SandboxParameters &parameters) {
    float4& ranges = parameters.colorGradingOptions.ranges;
    ranges.y = clamp(ranges.y, ranges.x + 1e-5f, ranges.w - 1e-5f); // darks
    ranges.z = clamp(ranges.z, ranges.x + 1e-5f, ranges.w - 1e-5f); // lights

    for (size_t i = 0; i < 1024; i++) {
        float x = i / 1024.0f;
        float s = 1.0f - smoothstep(ranges.x, ranges.y, x);
        float h = smoothstep(ranges.z, ranges.w, x);
        g_rangePlot[i]        = s;
        g_rangePlot[1024 + i] = 1.0f - s - h;
        g_rangePlot[2048 + i] = h;
    }
}

static void rangePlotSeriesStart(int series) {
    switch (series) {
        case 0:
            ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.4f, 0.25f, 1.0f));
            break;
        case 1:
            ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.8f, 0.25f, 1.0f));
            break;
        case 2:
            ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.17f, 0.21f, 1.0f));
            break;
    }
}

static void rangePlotSeriesEnd(int series) {
    if (series < 3) {
        ImGui::PopStyleColor();
    }
}

static float getRangePlotValue(int series, void* data, int index) {
    return ((float*) data)[series * 1024 + index];
}

inline float3 curves(float3 v, float3 shadowGamma, float3 midPoint, float3 highlightScale) {
    float3 d = 1.0f / (pow(midPoint, shadowGamma - 1.0f));
    float3 dark = pow(v, shadowGamma) * d;
    float3 light = highlightScale * (v - midPoint) + midPoint;
    return float3{
            v.r <= midPoint.r ? dark.r : light.r,
            v.g <= midPoint.g ? dark.g : light.g,
            v.b <= midPoint.b ? dark.b : light.b,
    };
}

static void computeCurvePlot(SandboxParameters &parameters) {
    ColorGradingOptions &colorGrading = parameters.colorGradingOptions;
    for (size_t i = 0; i < 1024; i++) {
        float3 x{i / 1024.0f * 2.0f};
        float3 y = curves(x, colorGrading.gamma, colorGrading.midPoint, colorGrading.scale);
        g_curvePlot[i]        = y.r;
        g_curvePlot[1024 + i] = y.g;
        g_curvePlot[2048 + i] = y.b;
    }
}

static void tooltipFloat(float value) {
    if (ImGui::IsItemActive() || ImGui::IsItemHovered()) {
        ImGui::SetTooltip("%.2f", value);
    }
}

static void pushSliderColors(float hue) {
    ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4) ImColor::HSV(hue, 0.5f, 0.5f));
    ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4) ImColor::HSV(hue, 0.6f, 0.5f));
    ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4) ImColor::HSV(hue, 0.7f, 0.5f));
    ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4) ImColor::HSV(hue, 0.9f, 0.9f));
}

static void popSliderColors() { ImGui::PopStyleColor(4); }

static void gui(filament::Engine* engine, filament::View*) {
    auto& params = g_params;
    ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
    ImGui::Begin("Parameters");
    {
        if (ImGui::CollapsingHeader("Material", ImGuiTreeNodeFlags_DefaultOpen)) {
            ImGui::Indent();
            ImGui::Combo("Model", &params.currentMaterialModel,
                    "Unlit\0Lit\0Subsurface\0Cloth\0Specular glossiness\0\0");

            if (params.currentMaterialModel == MATERIAL_MODEL_LIT) {
                ImGui::Combo("Blending", &params.currentBlending,
                        "Opaque\0Transparent\0Fade\0Thin refraction\0Solid refraction\0\0");
            }

            ImGui::ColorEdit3("Base color", &params.color.r);

            bool hasRefraction = params.currentBlending == BLENDING_THIN_REFRACTION ||
                    params.currentBlending == BLENDING_SOLID_REFRACTION;

            if (params.currentMaterialModel > MATERIAL_MODEL_UNLIT) {
                if (params.currentBlending == BLENDING_TRANSPARENT ||
                        params.currentBlending == BLENDING_FADE) {
                    ImGui::SliderFloat("Alpha", &params.alpha, 0.0f, 1.0f);
                }

                if (params.currentMaterialModel != MATERIAL_MODEL_SPECGLOSS) {
                    ImGui::SliderFloat("Roughness", &params.roughness, 0.0f, 1.0f);
                } else {
                    ImGui::SliderFloat("Glossiness", &params.glossiness, 0.0f, 1.0f);
                    ImGui::ColorEdit3("Specular color", &params.specularColor.r);
                }

                if (params.currentMaterialModel != MATERIAL_MODEL_CLOTH &&
                        params.currentMaterialModel != MATERIAL_MODEL_SPECGLOSS) {
                    if (!hasRefraction) {
                        ImGui::SliderFloat("Metallic", &params.metallic, 0.0f, 1.0f);
                        ImGui::SliderFloat("Reflectance", &params.reflectance, 0.0f, 1.0f);
                    }
                }

                if (params.currentMaterialModel != MATERIAL_MODEL_CLOTH &&
                        params.currentMaterialModel != MATERIAL_MODEL_SUBSURFACE) {
                    ImGui::ColorEdit3("Sheen color", &params.sheenColor.r);
                    ImGui::SliderFloat("Sheen roughness", &params.sheenRoughness, 0.0f, 1.0f);
                    ImGui::SliderFloat("Clear coat", &params.clearCoat, 0.0f, 1.0f);
                    ImGui::SliderFloat("Clear coat roughness", &params.clearCoatRoughness, 0.0f, 1.0f);
                    ImGui::SliderFloat("Anisotropy", &params.anisotropy, -1.0f, 1.0f);
                }

                if (params.currentMaterialModel == MATERIAL_MODEL_SUBSURFACE) {
                    ImGui::SliderFloat("Thickness", &params.thickness, 0.0f, 1.0f);
                    ImGui::SliderFloat("Subsurface power", &params.subsurfacePower, 1.0f, 24.0f);
                    ImGui::ColorEdit3("Subsurface color", &params.subsurfaceColor.r);
                }

                if (params.currentMaterialModel == MATERIAL_MODEL_CLOTH) {
                    ImGui::ColorEdit3("Sheen color", &params.sheenColor.r);
                    ImGui::ColorEdit3("Subsurface color", &params.subsurfaceColor.r);
                }

                if (hasRefraction) {
                    ImGui::SliderFloat("IOR", &params.ior, 1.0f, 3.0f);
                    ImGui::SliderFloat("Transmission", &params.transmission, 0.0f, 1.0f);
                    ImGui::SliderFloat("Thickness", &params.thickness, 0.0f, 1.0f);
                    ImGui::ColorEdit3("Transmittance", &params.transmittanceColor.r);
                    ImGui::SliderFloat("Distance", &params.distance, 0.0f, 4.0f);
                    ImGui::Checkbox("Screen space refraction", &params.ssr);
                }
            }

            ImGui::ColorEdit3("Emissive color", &params.emissiveColor.r);
            ImGui::SliderFloat("Emissive EV", &params.emissiveEV, -24.0f, 24.0f);
            ImGui::SliderFloat("Exposure weight", &params.emissiveExposureWeight, 0.0f, 1.0f);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Shading AA")) {
            ImGui::Indent();
            ImGui::SliderFloat("Variance", &params.specularAntiAliasingVariance, 0.0f, 1.0f);
            ImGui::SliderFloat("Threshold", &params.specularAntiAliasingThreshold, 0.0f, 1.0f);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Object")) {
            ImGui::Indent();
            ImGui::Checkbox("Cast shadows##object", &params.castShadows);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Camera")) {
            ImGui::Indent();
            ImGui::SliderFloat("Focal length", &FilamentApp::get().getCameraFocalLength(), 16.0f, 90.0f);
            ImGui::SliderFloat("Aperture", &params.cameraAperture, 1.0f, 32.0f);
            ImGui::SliderFloat("Speed", &params.cameraSpeed, 800.0f, 1.0f);
            ImGui::SliderFloat("ISO", &params.cameraISO, 25.0f, 6400.0f);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Indirect Light")) {
            ImGui::Indent();
            ImGui::SliderFloat("IBL", &params.iblIntensity, 0.0f, 50000.0f);
            ImGui::SliderAngle("Rotation", &params.iblRotation);
            if (ImGui::CollapsingHeader("SSAO")) {
                int quality = (int)params.ssaoOptions.quality;
                int lowpass = (int)params.ssaoOptions.lowPassFilter;
                bool upsampling = params.ssaoOptions.upsampling != View::QualityLevel::LOW;
                DebugRegistry& debug = engine->getDebugRegistry();
                ImGui::Checkbox("Enabled##ssao", &params.ssaoOptions.enabled);
                ImGui::SliderFloat("Radius", &params.ssaoOptions.radius, 0.05f, 5.0f);
                ImGui::SliderFloat("Bias", &params.ssaoOptions.bias, 0.0f, 0.01f, "%.6f");
                ImGui::SliderFloat("Min Horizon angle", &params.ssaoOptions.minHorizonAngleRad, 0.0f, (float)M_PI_4, "%.6f");
                ImGui::SliderFloat("Intensity", &params.ssaoOptions.intensity, 0.0f, 4.0f);
                ImGui::SliderFloat("Power", &params.ssaoOptions.power, 0.0f, 4.0f);
                ImGui::SliderInt("Quality", &quality, 0, 3);
                ImGui::SliderInt("Low Pass", &lowpass, 0, 2);
                ImGui::Checkbox("High quality upsampling", &upsampling);
                params.ssaoOptions.upsampling = upsampling ? View::QualityLevel::HIGH : View::QualityLevel::LOW;
                params.ssaoOptions.quality = (View::QualityLevel)quality;
                params.ssaoOptions.lowPassFilter = (View::QualityLevel)lowpass;
                if (ImGui::CollapsingHeader("Dominant Light Shadows")) {
                    int sampleCount = params.ssaoOptions.ssct.sampleCount;
                    int rayCount = params.ssaoOptions.ssct.rayCount;
                    ImGui::Checkbox("Enabled##dls", &params.ssaoOptions.ssct.enabled);
                    ImGui::SliderFloat("Cone angle", &params.ssaoOptions.ssct.lightConeRad, 0.0f, (float)M_PI_2);
                    ImGui::SliderFloat("Shadow Distance", &params.ssaoOptions.ssct.shadowDistance, 0.0f, 10.0f);
                    ImGui::SliderFloat("Contact dist max", &params.ssaoOptions.ssct.contactDistanceMax, 0.0f, 100.0f);
                    ImGui::SliderFloat("Intensity##dls", &params.ssaoOptions.ssct.intensity, 0.0f, 10.0f);
                    ImGui::SliderFloat("Depth bias", &params.ssaoOptions.ssct.depthBias, 0.0f, 1.0f);
                    ImGui::SliderFloat("Depth slope bias", &params.ssaoOptions.ssct.depthSlopeBias, 0.0f, 1.0f);
                    ImGui::SliderInt("Sample Count", &sampleCount, 1, 32);
                    ImGui::SliderInt("Ray Count", &rayCount, 1, 8);
                    ImGuiExt::DirectionWidget("Direction##dls", params.ssaoOptions.ssct.lightDirection.v);
                    params.ssaoOptions.ssct.sampleCount = sampleCount;
                    params.ssaoOptions.ssct.rayCount = rayCount;
                }
            }
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Directional Light")) {
            ImGui::Indent();
            ImGui::Checkbox("Enabled##directionalLight", &params.directionalLightEnabled);
            ImGui::ColorEdit3("Color##directionalLight", &params.lightColor.r);
            ImGui::SliderFloat("Lux", &params.lightIntensity, 0.0f, 150000.0f);
            ImGui::SliderFloat("Sun size", &params.sunAngularRadius, 0.1f, 10.0f);
            ImGui::SliderFloat("Halo size", &params.sunHaloSize, 1.01f, 40.0f);
            ImGui::SliderFloat("Halo falloff", &params.sunHaloFalloff, 0.0f, 2048.0f);
            ImGuiExt::DirectionWidget("Direction", params.lightDirection.v);
            if (ImGui::CollapsingHeader("Contact Shadows")) {
                DebugRegistry& debug = engine->getDebugRegistry();
                ImGui::Checkbox("Enabled##contactShadows", &params.screenSpaceContactShadows);
                ImGui::SliderInt("Steps", &params.stepCount, 0, 255);
                ImGui::SliderFloat("Distance", &params.maxShadowDistance, 0.0f, 10.0f);
            }
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Spot Light")) {
            ImGui::Indent();
            ImGui::Checkbox("Enabled##spotLight", &params.spotLightEnabled);
            ImGui::SliderFloat3("Position", &params.spotLightPosition.x, -5.0f, 5.0f);
            ImGui::ColorEdit3("Color##spotLight", &params.spotLightColor.r);
            ImGui::Checkbox("Cast shadows##spotLight", &params.spotLightCastShadows);
            ImGui::SliderFloat("Lumens", &params.spotLightIntensity, 0.0, 1000000.f);
            ImGui::SliderAngle("Cone angle", &params.spotLightConeAngle, 0.0f, 90.0f);
            ImGui::SliderFloat("Cone fade", &params.spotLightConeFade, 0.0f, 1.0f);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Fog")) {
            ImGui::Indent();
            ImGui::Checkbox("Enable Fog", &params.fogOptions.enabled);
            ImGui::SliderFloat("Start", &params.fogOptions.distance, 0.0f, 100.0f);
            ImGui::SliderFloat("Density", &params.fogOptions.density, 0.0f, 1.0f);
            ImGui::SliderFloat("Height", &params.fogOptions.height, 0.0f, 100.0f);
            ImGui::SliderFloat("Height Falloff", &params.fogOptions.heightFalloff, 0.0f, 10.0f);
            ImGui::SliderFloat("Scattering Start", &params.fogOptions.inScatteringStart, 0.0f, 100.0f);
            ImGui::SliderFloat("Scattering Size", &params.fogOptions.inScatteringSize, 0.1f, 100.0f);
            ImGui::Checkbox("Color from IBL", &params.fogOptions.fogColorFromIbl);
            ImGui::ColorPicker3("Color", params.fogOptions.color.v);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Post-processing")) {
            ImGui::Indent();
            ImGui::Checkbox("MSAA 4x", &params.msaa);
            ImGui::Checkbox("TAA", &params.taaOptions.enabled);
            if (params.taaOptions.enabled) {
                ImGui::Indent();
                ImGui::SliderFloat("feedback", &params.taaOptions.feedback, 0.0f, 1.0f);
                ImGui::SliderFloat("filter", &params.taaOptions.filterWidth, 0.02f, 2.0f);
                ImGui::Unindent();
            }
            ImGui::Checkbox("FXAA", &params.fxaa);
            ImGui::Checkbox("Bloom", &params.bloomOptions.enabled);
            if (params.bloomOptions.enabled) {
                ImGui::Indent();
                ImGui::SliderFloat("Strength", &params.bloomOptions.strength, 0.0f, 1.0f);
                ImGui::SliderFloat("Dirt", &params.bloomOptions.dirtStrength, 0.0f, 1.0f);
                ImGui::Unindent();
            }
            ImGui::Checkbox("Dithering", &params.dithering);
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Color grading")) {
            ColorGradingOptions& colorGrading = params.colorGradingOptions;

            ImGui::Indent();
            ImGui::Checkbox("Enabled##colorGrading", &params.colorGrading);
            ImGui::Combo("Tone-mapping", &colorGrading.toneMapping,
                    "Linear\0ACES (legacy)\0ACES\0Filmic\0EVILS\0Reinhard\0Display Range\0\0");
            if (ImGui::CollapsingHeader("While balance")) {
                ImGui::SliderInt("Temperature", &colorGrading.temperature, -100, 100);
                ImGui::SliderInt("Tint", &colorGrading.tint, -100, 100);
            }
            if (ImGui::CollapsingHeader("Channel mixer")) {
                pushSliderColors(0.0f / 7.0f);
                ImGui::VSliderFloat("##outRed.r", verticalSliderSize, &colorGrading.outRed.r, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outRed.r);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outRed.g", verticalSliderSize, &colorGrading.outRed.g, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outRed.g);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outRed.b", verticalSliderSize, &colorGrading.outRed.b, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outRed.b);
                ImGui::SameLine(0.0f, 18.0f);
                popSliderColors();

                pushSliderColors(2.0f / 7.0f);
                ImGui::VSliderFloat("##outGreen.r", verticalSliderSize, &colorGrading.outGreen.r, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outGreen.r);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outGreen.g", verticalSliderSize, &colorGrading.outGreen.g, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outGreen.g);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outGreen.b", verticalSliderSize, &colorGrading.outGreen.b, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outGreen.b);
                ImGui::SameLine(0.0f, 18.0f);
                popSliderColors();

                pushSliderColors(4.0f / 7.0f);
                ImGui::VSliderFloat("##outBlue.r", verticalSliderSize, &colorGrading.outBlue.r, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outBlue.r);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outBlue.g", verticalSliderSize, &colorGrading.outBlue.g, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outBlue.g);
                ImGui::SameLine();
                ImGui::VSliderFloat("##outBlue.b", verticalSliderSize, &colorGrading.outBlue.b, -2.0f, 2.0f, "");
                tooltipFloat(colorGrading.outBlue.b);
                popSliderColors();
            }
            if (ImGui::CollapsingHeader("Tonal ranges")) {
                ImGui::ColorEdit3("Shadows", &colorGrading.shadows.x);
                ImGui::SliderFloat("Shadows weight", &colorGrading.shadows.w, -2.0f, 2.0f);
                ImGui::ColorEdit3("Mid-tones", &colorGrading.midtones.x);
                ImGui::SliderFloat("Mid-tones weight", &colorGrading.midtones.w, -2.0f, 2.0f);
                ImGui::ColorEdit3("Highlights", &colorGrading.highlights.x);
                ImGui::SliderFloat("Highlights weight", &colorGrading.highlights.w, -2.0f, 2.0f);
                ImGui::SliderFloat4("Ranges", &colorGrading.ranges.x, 0.0f, 1.0f);
                computeRangePlot(params);
                ImGuiExt::PlotLinesSeries("", 3,
                        rangePlotSeriesStart, getRangePlotValue, rangePlotSeriesEnd,
                        g_rangePlot, 1024, 0, "", 0.0f, 1.0f, plotLinesWideSize);
            }
            if (ImGui::CollapsingHeader("Color decision list")) {
                ImGui::SliderFloat3("Slope", &colorGrading.slope.x, 0.0f, 2.0f);
                ImGui::SliderFloat3("Offset", &colorGrading.offset.x, -0.5f, 0.5f);
                ImGui::SliderFloat3("Power", &colorGrading.power.x, 0.0f, 2.0f);
            }
            if (ImGui::CollapsingHeader("Adjustments")) {
                ImGui::SliderFloat("Contrast", &colorGrading.contrast, 0.0f, 2.0f);
                ImGui::SliderFloat("Vibrance", &colorGrading.vibrance, 0.0f, 2.0f);
                ImGui::SliderFloat("Saturation", &colorGrading.saturation, 0.0f, 2.0f);
            }
            if (ImGui::CollapsingHeader("Curves")) {
                ImGui::Checkbox("Linked curves", &colorGrading.linkedCurves);

                computeCurvePlot(params);

                if (!colorGrading.linkedCurves) {
                    pushSliderColors(0.0f / 7.0f);
                    ImGui::VSliderFloat("##curveGamma.r", verticalSliderSize, &colorGrading.gamma.r, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.gamma.r);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveMid.r", verticalSliderSize, &colorGrading.midPoint.r, 0.0f, 2.0f, "");
                    tooltipFloat(colorGrading.midPoint.r);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveScale.r", verticalSliderSize, &colorGrading.scale.r, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.scale.r);
                    ImGui::SameLine(0.0f, 18.0f);
                    popSliderColors();

                    ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.0f, 0.7f, 0.8f));
                    ImGui::PlotLines("", g_curvePlot, 1024, 0, "Red", 0.0f, 2.0f, plotLinesSize);
                    ImGui::PopStyleColor();

                    pushSliderColors(2.0f / 7.0f);
                    ImGui::VSliderFloat("##curveGamma.g", verticalSliderSize, &colorGrading.gamma.g, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.gamma.g);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveMid.g", verticalSliderSize, &colorGrading.midPoint.g, 0.0f, 2.0f, "");
                    tooltipFloat(colorGrading.midPoint.g);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveScale.g", verticalSliderSize, &colorGrading.scale.g, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.scale.g);
                    ImGui::SameLine(0.0f, 18.0f);
                    popSliderColors();

                    ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.3f, 0.7f, 0.8f));
                    ImGui::PlotLines("", g_curvePlot + 1024, 1024, 0, "Green", 0.0f, 2.0f, plotLinesSize);
                    ImGui::PopStyleColor();

                    pushSliderColors(4.0f / 7.0f);
                    ImGui::VSliderFloat("##curveGamma.b", verticalSliderSize, &colorGrading.gamma.b, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.gamma.b);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveMid.b", verticalSliderSize, &colorGrading.midPoint.b, 0.0f, 2.0f, "");
                    tooltipFloat(colorGrading.midPoint.b);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveScale.b", verticalSliderSize, &colorGrading.scale.b, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.scale.b);
                    ImGui::SameLine(0.0f, 18.0f);
                    popSliderColors();

                    ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.6f, 0.7f, 0.8f));
                    ImGui::PlotLines("", g_curvePlot + 2048, 1024, 0, "Blue", 0.0f, 2.0f, plotLinesSize);
                    ImGui::PopStyleColor();
                } else {
                    ImGui::VSliderFloat("##curveGamma", verticalSliderSize, &colorGrading.gamma.r, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.gamma.r);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveMid", verticalSliderSize, &colorGrading.midPoint.r, 0.0f, 2.0f, "");
                    tooltipFloat(colorGrading.midPoint.r);
                    ImGui::SameLine();
                    ImGui::VSliderFloat("##curveScale", verticalSliderSize, &colorGrading.scale.r, 0.0f, 4.0f, "");
                    tooltipFloat(colorGrading.scale.r);
                    ImGui::SameLine(0.0f, 18.0f);

                    colorGrading.gamma = float3{colorGrading.gamma.r};
                    colorGrading.midPoint = float3{colorGrading.midPoint.r};
                    colorGrading.scale = float3{colorGrading.scale.r};

                    ImGui::PushStyleColor(ImGuiCol_PlotLines, (ImVec4) ImColor::HSV(0.17f, 0.21f, 0.9f));
                    ImGui::PlotLines("", g_curvePlot, 1024, 0, "RGB", 0.0f, 2.0f, plotLinesSize);
                    ImGui::PopStyleColor();
                }
            }
            ImGui::Unindent();
        }

        if (ImGui::CollapsingHeader("Debug")) {
            DebugRegistry& debug = engine->getDebugRegistry();
            ImGui::Indent();
            ImGui::Checkbox("Camera at origin",
                    debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
            ImGui::Checkbox("Stable Shadow Map", &params.stableShadowMap);
            ImGui::Checkbox("Light Far uses shadow casters",
                    debug.getPropertyAddress<bool>("d.shadowmap.far_uses_shadowcasters"));
            ImGui::Checkbox("Focus shadow casters",
                    debug.getPropertyAddress<bool>("d.shadowmap.focus_shadowcasters"));
            ImGui::Checkbox("Show checker board",
                    debug.getPropertyAddress<bool>("d.shadowmap.checkerboard"));

            ImGui::SliderFloat("Normal bias", &params.normalBias, 0.0f, 4.0f);
            ImGui::SliderFloat("Constant bias", &params.constantBias, 0.0f, 1.0f);
            ImGui::SliderFloat("Polygon Offset Scale", &params.polygonOffsetSlope, 0.0f, 10.0f);
            ImGui::SliderFloat("Polygon Offset Constant", &params.polygonOffsetConstant, 0.0f, 10.0f);

            bool* lispsm;
            if (debug.getPropertyAddress<bool>("d.shadowmap.lispsm", &lispsm)) {
                ImGui::Checkbox("Enable LiSPSM", lispsm);
                if (*lispsm) {
                    ImGui::SliderFloat("dzn",
                            debug.getPropertyAddress<float>("d.shadowmap.dzn"), 0.0f, 1.0f);
                    ImGui::SliderFloat("dzf",
                            debug.getPropertyAddress<float>("d.shadowmap.dzf"),-1.0f, 0.0f);
                }
            }
            ImGui::Unindent();
        }
    }
    ImGui::End();

    MaterialInstance* materialInstance = updateInstances(params, *engine);

    auto& rcm = engine->getRenderableManager();
    size_t count = 0;
    for (auto renderable : g_meshSet->getRenderables()) {
        auto instance = rcm.getInstance(renderable);
        if (!instance) continue;
        if (!g_singleMode || count == 0) {
            for (size_t i = 0; i < rcm.getPrimitiveCount(instance); i++) {
                rcm.setMaterialInstanceAt(instance, i, materialInstance);
            }
        }
        count++;
        rcm.setCastShadows(instance, params.castShadows);
    }

    if (params.directionalLightEnabled && !params.hasDirectionalLight) {
        g_scene->addEntity(params.light);
        params.hasDirectionalLight = true;
    } else if (!params.directionalLightEnabled && params.hasDirectionalLight) {
        g_scene->remove(params.light);
        params.hasDirectionalLight = false;
    }

    auto* ibl = FilamentApp::get().getIBL();
    if (ibl) {
        ibl->getIndirectLight()->setIntensity(params.iblIntensity);
        ibl->getIndirectLight()->setRotation(
                mat3f::rotation(params.iblRotation, float3{ 0, 1, 0 }));
    }

    auto& lcm = engine->getLightManager();
    auto lightInstance = lcm.getInstance(params.light);
    lcm.setColor(lightInstance, params.lightColor);
    lcm.setIntensity(lightInstance, params.lightIntensity);
    lcm.setDirection(lightInstance, params.lightDirection);
    lcm.setSunAngularRadius(lightInstance, params.sunAngularRadius);
    lcm.setSunHaloSize(lightInstance, params.sunHaloSize);
    lcm.setSunHaloFalloff(lightInstance, params.sunHaloFalloff);

    LightManager::ShadowOptions options = lcm.getShadowOptions(lightInstance);
    options.stable = params.stableShadowMap;
    options.normalBias = params.normalBias;
    options.constantBias = params.constantBias;
    options.polygonOffsetConstant = params.polygonOffsetConstant;
    options.polygonOffsetSlope = params.polygonOffsetSlope;
    options.screenSpaceContactShadows = params.screenSpaceContactShadows;
    options.stepCount = params.stepCount;
    options.maxShadowDistance = params.maxShadowDistance;
    lcm.setShadowOptions(lightInstance, options);

    if (params.spotLightEnabled && !params.hasSpotLight) {
        g_scene->addEntity(params.spotLight);
        params.hasSpotLight = true;
    } else if (!params.spotLightEnabled && params.hasSpotLight) {
        g_scene->remove(params.spotLight);
        params.hasSpotLight = false;
    }
    auto spotLightInstance = lcm.getInstance(params.spotLight);
    auto& tcm = engine->getTransformManager();
    tcm.setTransform(tcm.getInstance(params.spotLight),
            mat4f::translation(params.spotLightPosition));
    lcm.setColor(spotLightInstance, params.spotLightColor);
    lcm.setShadowCaster(spotLightInstance, params.spotLightCastShadows);
    lcm.setIntensity(spotLightInstance, params.spotLightIntensity);
    lcm.setSpotLightCone(spotLightInstance, params.spotLightConeAngle * params.spotLightConeFade,
            params.spotLightConeAngle);
}

static void preRender(filament::Engine* engine, filament::View* view, filament::Scene*,
        filament::Renderer* renderer) {
    view->setAntiAliasing(g_params.fxaa ? View::AntiAliasing::FXAA : View::AntiAliasing::NONE);
    view->setDithering(g_params.dithering ? View::Dithering::TEMPORAL : View::Dithering::NONE);
    view->setBloomOptions(g_params.bloomOptions);
    view->setFogOptions(g_params.fogOptions);
    view->setTemporalAntiAliasingOptions(g_params.taaOptions);
    view->setSampleCount((uint8_t) (g_params.msaa ? 4 : 1));
    view->setAmbientOcclusionOptions(g_params.ssaoOptions);

    if (g_params.colorGrading) {
        if (g_params.colorGradingOptions != g_lastColorGradingOptions) {
            ColorGradingOptions &options = g_params.colorGradingOptions;
            ColorGrading *colorGrading = ColorGrading::Builder()
                    .whiteBalance(options.temperature / 100.0f, options.tint / 100.0f)
                    .channelMixer(options.outRed, options.outGreen, options.outBlue)
                    .shadowsMidtonesHighlights(
                            Color::toLinear(options.shadows),
                            Color::toLinear(options.midtones),
                            Color::toLinear(options.highlights),
                            options.ranges
                    )
                    .slopeOffsetPower(options.slope, options.offset, options.power)
                    .contrast(options.contrast)
                    .vibrance(options.vibrance)
                    .saturation(options.saturation)
                    .curves(options.gamma, options.midPoint, options.scale)
                    .toneMapping(static_cast<ColorGrading::ToneMapping>(options.toneMapping))
                    .build(*engine);

            if (g_colorGrading) {
                engine->destroy(g_colorGrading);
            }

            g_colorGrading = colorGrading;
            g_lastColorGradingOptions = options;
        }
        view->setColorGrading(g_colorGrading);
    } else {
        view->setColorGrading(nullptr);
    }

    // Without an IBL, we must clear the swapchain to black before each frame.
    renderer->setClearOptions({
            .clearColor = { 0.0f, 0.0f, 0.0f, 1.0f },
            .clear = !FilamentApp::get().getIBL()  });

    Camera& camera = view->getCamera();
    camera.setExposure(g_params.cameraAperture, 1.0f / g_params.cameraSpeed, g_params.cameraISO);
}

int main(int argc, char* argv[]) {
    int option_index = handleCommandLineArgments(argc, argv, &g_config);
    int num_args = argc - option_index;
    if (num_args < 1) {
        printUsage(argv[0]);
        return 1;
    }

    for (int i = option_index; i < argc; i++) {
        utils::Path filename = argv[i];
        if (!filename.exists()) {
            std::cerr << "file " << argv[i] << " not found!" << std::endl;
            return 1;
        }
        g_filenames.push_back(filename);
    }

    g_params.bloomOptions.enabled = true;

    g_config.title = "Material Sandbox";
    FilamentApp& filamentApp = FilamentApp::get();
    filamentApp.run(g_config, setup, cleanup, gui, preRender);
    return 0;
}
