Files
filament/samples/materialinstancestress.cpp

308 lines
10 KiB
C++

/*
* Copyright (C) 2025 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 "common/arguments.h"
#include <filament/Engine.h>
#include <filament/LightManager.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <filament/View.h>
#include <utils/EntityManager.h>
#include <utils/Path.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <filameshio/MeshReader.h>
#include <getopt/getopt.h>
#include <imgui.h>
#include <iostream>
#include <random>
#include <string>
#include <vector>
#include "generated/resources/monkey.h"
#include "generated/resources/resources.h"
using namespace filament;
using namespace filament::math;
using namespace utils;
using namespace filamesh;
struct App {
struct UiState {
int objectCountSlider = 100;
float updateRatio = 0.5f;
bool animate = true;
int deltaCount = 5;
} ui;
int currentObjectCount = 0;
int desiredObjectCount = 1000;
uint32_t frameCounter = 0;
float spacing = 2.5f;
float distanceToCamera = -20.0f;
int gridDim = 1;
Material* litMaterial = nullptr;
MeshReader::Mesh suzanneTemplate;
Entity light;
std::vector<MaterialInstance*> materialInstances;
std::vector<Entity> renderables;
};
static void printUsage(char* name) {
std::string exec_name(Path(name).getName());
std::string usage(
"MATERIALINSTANCESTRESS renders a lot of suzanne models and changes their material "
"properties periodically.\n"
"Usage:\n"
" MATERIALINSTANCESTRESS [options]\n"
"Options:\n"
" --help, -h\n"
" Prints this message\n\n"
"API_USAGE");
const std::string from("MATERIALINSTANCESTRESS");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), exec_name);
}
const std::string apiUsage("API_USAGE");
for (size_t pos = usage.find(apiUsage); pos != std::string::npos; pos = usage.find(apiUsage, pos)) {
usage.replace(pos, apiUsage.length(), samples::getBackendAPIArgumentsUsage());
}
std::cout << usage;
}
static int handleCommandLineArguments(int argc, char* argv[], Config* config) {
static constexpr const char* OPTSTR = "ha:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
{ nullptr, 0, nullptr, 0 }
};
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':
config->backend = samples::parseArgumentsForBackend(arg);
break;
}
}
return optind;
}
static void removeObjects(Engine* engine, Scene* scene, App& app, int count) {
if (count <= 0) return;
EntityManager& em = EntityManager::get();
const int removeCount = std::min(count, app.currentObjectCount);
for (int i = 0; i < removeCount; ++i) {
Entity renderable = app.renderables.back();
scene->remove(renderable);
engine->destroy(renderable);
em.destroy(renderable);
app.renderables.pop_back();
MaterialInstance* mi = app.materialInstances.back();
engine->destroy(mi);
app.materialInstances.pop_back();
}
app.currentObjectCount = app.renderables.size();
}
static void clearScene(Engine* engine, Scene* scene, App& app) {
removeObjects(engine, scene, app, app.currentObjectCount);
}
static void addObjects(Engine* engine, Scene* scene, App& app, int count) {
if (count <= 0) return;
TransformManager& tcm = engine->getTransformManager();
EntityManager& em = EntityManager::get();
int const oldTotal = app.currentObjectCount;
int const newTotal = oldTotal + count;
app.renderables.reserve(newTotal);
app.materialInstances.reserve(newTotal);
for (int i = oldTotal; i < newTotal; ++i) {
int const x = i % app.gridDim;
int const y = i / app.gridDim;
float3 const position = {
(x - app.gridDim / 2.0f) * app.spacing,
(y - app.gridDim / 2.0f) * app.spacing,
app.distanceToCamera
};
MaterialInstance* mi = app.litMaterial->createInstance();
mi->setParameter("baseColor", RgbType::sRGB, float3{1.0f});
mi->setParameter("metallic", 0.0f);
mi->setParameter("roughness", 0.4f);
app.materialInstances.push_back(mi);
Entity renderable = em.create();
RenderableManager::Builder(1)
.boundingBox({{-1, -1, -1}, {1, 1, 1}})
.material(0, mi)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
app.suzanneTemplate.vertexBuffer, app.suzanneTemplate.indexBuffer)
.culling(true)
.castShadows(false)
.build(*engine, renderable);
scene->addEntity(renderable);
app.renderables.push_back(renderable);
tcm.setTransform(tcm.getInstance(renderable), mat4f::translation(position));
}
app.currentObjectCount = newTotal;
}
static void createSceneObjects(Engine* engine, Scene* scene, App& app) {
app.gridDim = static_cast<int>(ceil(sqrt(app.desiredObjectCount)));
addObjects(engine, scene, app, std::max(0, app.desiredObjectCount - app.currentObjectCount));
}
int main(int argc, char** argv) {
Config config;
config.title = "Material Instances Stress Test";
config.iblDirectory = FilamentApp::getRootAssetsPath() + "assets/ibl/lightroom_14b";
handleCommandLineArguments(argc, argv, &config);
App app;
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
app.desiredObjectCount = app.ui.objectCountSlider;
app.suzanneTemplate = MeshReader::loadMeshFromBuffer(engine,
MONKEY_SUZANNE_DATA, nullptr, nullptr, nullptr);
app.litMaterial = Material::Builder()
.package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE)
.build(*engine);
app.light = EntityManager::get().create();
LightManager::Builder(LightManager::Type::SUN)
.color(Color::toLinear<ACCURATE>(sRGBColor(0.98f, 0.92f, 0.89f)))
.intensity(110000)
.direction({0.7, -1, -0.8})
.sunAngularRadius(1.9f)
.castShadows(false)
.build(*engine, app.light);
scene->addEntity(app.light);
};
auto cleanup = [&app](Engine* engine, View* view, Scene* scene) {
clearScene(engine, scene, app);
engine->destroy(app.light);
EntityManager& em = EntityManager::get();
em.destroy(app.light);
engine->destroy(app.litMaterial);
engine->destroy(app.suzanneTemplate.renderable);
engine->destroy(app.suzanneTemplate.vertexBuffer);
engine->destroy(app.suzanneTemplate.indexBuffer);
};
auto gui = [&app](Engine* engine, View* view) {
ImGui::Begin("Material Instance Stress Test Controls");
ImGui::Text("Objects: %d", app.currentObjectCount);
ImGui::SliderInt("Object Count", &app.ui.objectCountSlider, 1, 1000);
if (ImGui::Button("Apply")) {
app.desiredObjectCount = app.ui.objectCountSlider;
}
ImGui::Separator();
ImGui::InputInt("Delta Count", &app.ui.deltaCount);
if (ImGui::Button("Add Objects")) {
app.desiredObjectCount += app.ui.deltaCount;
}
ImGui::SameLine();
if (ImGui::Button("Remove Objects")) {
app.desiredObjectCount = std::max(0, app.desiredObjectCount - app.ui.deltaCount);
}
ImGui::Separator();
ImGui::SliderFloat("Update Ratio / Frame", &app.ui.updateRatio, 0.0f, 1.0f);
ImGui::Checkbox("Animate", &app.ui.animate);
ImGui::End();
};
auto animate = [&app](Engine* engine, View* view, double now) {
if (app.currentObjectCount != app.desiredObjectCount) {
if (app.desiredObjectCount == app.ui.objectCountSlider) {
clearScene(engine, view->getScene(), app);
engine->flushAndWait();
createSceneObjects(engine, view->getScene(), app);
} else {
if (app.desiredObjectCount > app.currentObjectCount) {
const int toAdd = app.desiredObjectCount - app.currentObjectCount;
addObjects(engine, view->getScene(), app, toAdd);
} else {
const int toRemove = app.currentObjectCount - app.desiredObjectCount;
removeObjects(engine, view->getScene(), app, toRemove);
}
}
app.ui.objectCountSlider = app.currentObjectCount;
}
if (!app.ui.animate || app.currentObjectCount == 0) {
return;
}
const int updateCount = static_cast<int>(app.currentObjectCount * app.ui.updateRatio);
const uint32_t frame = app.frameCounter++;
for (int i = 0; i < updateCount; ++i) {
const int instanceIndex = (frame * i) % app.currentObjectCount;
MaterialInstance* mi = app.materialInstances[instanceIndex];
const float x = static_cast<float>(instanceIndex % app.gridDim);
const float y = static_cast<float>(instanceIndex / app.gridDim);
const float r = 0.5f + 0.5f * sin(now + (x + y) * 0.2f);
const float g = 0.5f + 0.5f * cos(now + (x - y) * 0.2f);
const float b = 0.5f + 0.5f * sin(now * 0.7f + y * 0.2f);
mi->setParameter("baseColor", RgbType::sRGB, float3{r, g, b});
}
};
FilamentApp::get().animate(animate);
FilamentApp::get().run(config, setup, cleanup, gui);
return 0;
}