diff --git a/CMakeLists.txt b/CMakeLists.txt index ea0c83aa91..ab228aee29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -572,6 +572,7 @@ add_subdirectory(${EXTERNAL}/libgtest/tnt) add_subdirectory(${LIBRARIES}/camutils) add_subdirectory(${LIBRARIES}/filabridge) add_subdirectory(${LIBRARIES}/filaflat) +add_subdirectory(${LIBRARIES}/filagui) add_subdirectory(${LIBRARIES}/filameshio) add_subdirectory(${LIBRARIES}/geometry) add_subdirectory(${LIBRARIES}/gltfio) @@ -580,15 +581,18 @@ add_subdirectory(${LIBRARIES}/image) add_subdirectory(${LIBRARIES}/math) add_subdirectory(${LIBRARIES}/mathio) add_subdirectory(${LIBRARIES}/utils) +add_subdirectory(${LIBRARIES}/viewer) add_subdirectory(${FILAMENT}/filament) add_subdirectory(${FILAMENT}/shaders) add_subdirectory(${EXTERNAL}/hat-trie/tnt) +add_subdirectory(${EXTERNAL}/imgui/tnt) add_subdirectory(${EXTERNAL}/robin-map/tnt) add_subdirectory(${EXTERNAL}/smol-v/tnt) add_subdirectory(${EXTERNAL}/benchmark/tnt) add_subdirectory(${EXTERNAL}/meshoptimizer) add_subdirectory(${EXTERNAL}/cgltf/tnt) add_subdirectory(${EXTERNAL}/draco/tnt) +add_subdirectory(${EXTERNAL}/jsmn/tnt) add_subdirectory(${EXTERNAL}/stb/tnt) add_subdirectory(${EXTERNAL}/getopt) @@ -626,9 +630,7 @@ endif() if (IS_HOST_PLATFORM) add_subdirectory(${LIBRARIES}/bluegl) 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) @@ -638,8 +640,6 @@ if (IS_HOST_PLATFORM) add_subdirectory(${EXTERNAL}/astcenc/tnt) add_subdirectory(${EXTERNAL}/etc2comp) - add_subdirectory(${EXTERNAL}/imgui/tnt) - add_subdirectory(${EXTERNAL}/jsmn/tnt) add_subdirectory(${EXTERNAL}/libassimp/tnt) add_subdirectory(${EXTERNAL}/libpng/tnt) add_subdirectory(${EXTERNAL}/libsdl2/tnt) diff --git a/android/filament-android/libfilament-jni.map b/android/filament-android/libfilament-jni.map index 62370730a2..e573ed2491 100644 --- a/android/filament-android/libfilament-jni.map +++ b/android/filament-android/libfilament-jni.map @@ -10,10 +10,12 @@ LIBFILAMENT { *filament*Aabb*; *filament*IndirectLight*; *filament*LightManager*; + *filament*Renderer*; *filament*Transform*; *filament*Material*; *filament*IndexBuffer*; *filament*VertexBuffer*; + *filament*View*; *filament*Texture*; *filament*geometry*; diff --git a/android/filament-utils-android/CMakeLists.txt b/android/filament-utils-android/CMakeLists.txt index 2eecfe4c8c..1e003e2d6f 100644 --- a/android/filament-utils-android/CMakeLists.txt +++ b/android/filament-utils-android/CMakeLists.txt @@ -12,6 +12,10 @@ add_library(image STATIC IMPORTED) set_target_properties(image PROPERTIES IMPORTED_LOCATION ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a) +add_library(viewer STATIC IMPORTED) +set_target_properties(viewer PROPERTIES IMPORTED_LOCATION + ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libviewer.a) + include_directories(${FILAMENT_DIR}/include .. ../../libs/utils/include) @@ -19,6 +23,7 @@ include_directories(${FILAMENT_DIR}/include set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map") add_library(filament-utils-jni SHARED + src/main/cpp/AutomationEngine.cpp src/main/cpp/Bookmark.cpp src/main/cpp/Utils.cpp src/main/cpp/Manipulator.cpp @@ -35,4 +40,5 @@ target_link_libraries(filament-utils-jni gltfio-jni camutils image + viewer ) diff --git a/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp b/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp new file mode 100644 index 0000000000..a805db4565 --- /dev/null +++ b/android/filament-utils-android/src/main/cpp/AutomationEngine.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 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; +using namespace filament::viewer; + +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nCreateAutomationEngine(JNIEnv* env, jclass, + jstring spec_) { + const char* spec = env->GetStringUTFChars(spec_, 0); + jlong result = (jlong) AutomationEngine::createFromJSON(spec, strlen(spec)); + env->ReleaseStringUTFChars(spec_, spec); + return result; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nCreateDefaultAutomationEngine(JNIEnv* env, + jclass klass) { + return (jlong) AutomationEngine::createDefaultTest(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nSetOptions(JNIEnv* env, jclass klass, + jlong nativeAutomation, jfloat sleepDuration, jint minFrameCount, jboolean verbose) { + AutomationEngine* automation = (AutomationEngine*) nativeAutomation; + AutomationEngine::Options options = { + .sleepDuration = sleepDuration, + .minFrameCount = minFrameCount, + .verbose = (bool) verbose, + + // Since they write to the filesystem, we do not currently support exporting screenshots + // and JSON files on Android. + .exportScreenshots = false, + .exportSettings = false, + }; + automation->setOptions(options); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nStartRunning(JNIEnv* env, jclass klass, + jlong nativeAutomation) { + AutomationEngine* automation = (AutomationEngine*) nativeAutomation; + automation->startRunning(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nStartBatchMode(JNIEnv* env, jclass klass, + jlong nativeAutomation) { + AutomationEngine* automation = (AutomationEngine*) nativeAutomation; + automation->startBatchMode(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclass klass, + jlong nativeAutomation, jlong view, jlongArray materials, jlong renderer, jfloat deltaTime) { + using MaterialPointer = MaterialInstance*; + jsize materialCount = 0; + jlong* longMaterials = nullptr; + MaterialPointer* ptrMaterials = nullptr; + if (materials) { + materialCount = env->GetArrayLength(materials); + ptrMaterials = new MaterialPointer[materialCount]; + longMaterials = env->GetLongArrayElements(materials, nullptr); + for (jsize i = 0; i < materialCount; i++) { + ptrMaterials[i] = (MaterialPointer) longMaterials[i]; + } + } + AutomationEngine* automation = (AutomationEngine*) nativeAutomation; + automation->tick((View*) view, ptrMaterials, materialCount, (Renderer*) renderer, deltaTime); + if (longMaterials) { + env->ReleaseLongArrayElements(materials, longMaterials, 0); + delete[] ptrMaterials; + } +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nSignalBatchMode(JNIEnv*, jclass, jlong native) { + AutomationEngine* automation = (AutomationEngine*) native; + automation->signalBatchMode(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nStopRunning(JNIEnv*, jclass, jlong native) { + AutomationEngine* automation = (AutomationEngine*) native; + automation->stopRunning(); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nShouldClose(JNIEnv*, jclass, jlong native) { + AutomationEngine* automation = (AutomationEngine*) native; + return automation->shouldClose(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_utils_AutomationEngine_nDestroy(JNIEnv*, jclass, jlong native) { + AutomationEngine* automation = (AutomationEngine*) native; + delete automation; +} diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java new file mode 100644 index 0000000000..90577f0ef8 --- /dev/null +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2021 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. + */ + +package com.google.android.filament.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.filament.View; +import com.google.android.filament.MaterialInstance; +import com.google.android.filament.Renderer; + +/** + * The AutomationEngine iterates through settings permutations and pushes them to Filament. + * + * Upon construction, automation is given a JSON string that tells it how to generate permutations + * of settings. Automation is always in one of two states: running or idle. The running state can be + * entered immediately (startRunning) or by requesting batch mode (startBatchMode). + * + * Clients must call tick() after each frame is rendered, which provides an opportunity to push + * settings to Filament, increment the current test index (if enough time has elapsed), and request + * an asychronous screenshot. + * + * The time to sleep between tests is configurable and can be set to zero. Automation also waits a + * specified minimum number of frames between tests. + * + * Batch mode is meant for non-interactive applications. In batch mode, automation defers applying + * the first test case until the client unblocks it via signalBatchMode(). This is useful when + * waiting for a large model file to become fully loaded. Batch mode also offers a query + * (shouldClose) that is triggered after the last test has been invoked. + */ +public class AutomationEngine { + private long mNativeObject; + + /** + * Allows users to toggle screenshots, change the sleep duration between tests, etc. + */ + public static class Options { + /** + * Minimum time that automation waits between applying a settings object and advancing + * to the next test case. Specified in seconds. + */ + float sleepDuration = 0.2f; + + /** + * Similar to sleepDuration, but expressed as a frame count. Both the minimum sleep time + * and the minimum frame count must be elapsed before automation advances to the next test. + */ + int minFrameCount = 2; + + /** + * If true, test progress is dumped to the utils Log (info priority). + */ + boolean verbose = true; + } + + /** + * Creates an automation engine from a JSON specification. + * + * An example of a JSON spec can be found by searching the repo for DEFAULT_AUTOMATION. + * This is documented using a JSON schema (look for viewer/schemas/automation.json). + * + * @param jsonSpec Valid JSON string that conforms to the automation schema. + */ + public AutomationEngine(@NonNull String jsonSpec) { + mNativeObject = nCreateAutomationEngine(jsonSpec); + if (mNativeObject == 0) throw new IllegalStateException("Couldn't create AutomationEngine"); + } + + /** + * Creates an automation engine for the default test sequence. + * + * To see how the default test sequence is generated, search for DEFAULT_AUTOMATION. + */ + public AutomationEngine() { + mNativeObject = nCreateDefaultAutomationEngine(); + if (mNativeObject == 0) throw new IllegalStateException("Couldn't create AutomationEngine"); + } + + /** + * Configures the automation engine for users who wish to set up a custom sleep time + * between tests, etc. + */ + public void setOptions(@NonNull Options options) { + nSetOptions(mNativeObject, options.sleepDuration, options.minFrameCount, options.verbose); + } + + /** + * Activates automation. During the subsequent call to tick(), the first test is applied + * and automation enters the running state. + */ + public void startRunning() { nStartRunning(mNativeObject); } + + /** + * Activates automation, but enters a paused state until the user calls signalBatchMode(). + */ + public void startBatchMode() { nStartBatchMode(mNativeObject); } + + /** + * Notifies the automation engine that time has passed and a new frame has been rendered. + * + * This is when settings get applied, screenshots are (optionally) exported, etc. + * + * @param view The Filament View that automation pushes changes to. + * @param materials Optional set of of materials that can receive parameter tweaks. + * @param renderer The Filament Renderer that can be used to take screenshots. + * @param deltaTime The amount of time that has passed since the previous tick in seconds. + */ + public void tick(@NonNull View view, @Nullable MaterialInstance[] materials, + @NonNull Renderer renderer, float deltaTime) { + long[] nativeMaterialInstances = null; + if (materials != null) { + nativeMaterialInstances = new long[materials.length]; + for (int i = 0; i < nativeMaterialInstances.length; i++) { + nativeMaterialInstances[i] = materials[i].getNativeObject(); + } + } + long nativeView = view.getNativeObject(); + long nativeRenderer = renderer.getNativeObject(); + nTick(mNativeObject, nativeView, nativeMaterialInstances, nativeRenderer, deltaTime); + } + + /** + * Signals that batch mode can begin. Call this after all meshes and textures finish loading. + */ + public void signalBatchMode() { nSignalBatchMode(mNativeObject); } + + /** + * Cancels an in-progress automation session. + */ + public void stopRunning() { nStopRunning(mNativeObject); } + + /** + * Returns true if automation is in batch mode and all tests have finished. + */ + public boolean shouldClose() { return nShouldClose(mNativeObject); } + + @Override + protected void finalize() throws Throwable { + nDestroy(mNativeObject); + super.finalize(); + } + + private static native long nCreateAutomationEngine(String jsonSpec); + private static native long nCreateDefaultAutomationEngine(); + private static native void nSetOptions(long nativeObject, float sleepDuration, int minFrameCount, boolean verbose); + private static native void nStartRunning(long nativeObject); + private static native void nStartBatchMode(long nativeObject); + private static native void nTick(long nativeObject, long view, long[] materials, long renderer, float deltaTime); + private static native void nSignalBatchMode(long nativeObject); + private static native void nStopRunning(long nativeObject); + private static native boolean nShouldClose(long nativeObject); + private static native void nDestroy(long nativeObject); +} diff --git a/libs/filagui/src/ImGuiHelper.cpp b/libs/filagui/src/ImGuiHelper.cpp index e45f679f5f..ca823a001d 100644 --- a/libs/filagui/src/ImGuiHelper.cpp +++ b/libs/filagui/src/ImGuiHelper.cpp @@ -21,7 +21,6 @@ #include -#include #include #include #include diff --git a/libs/viewer/CMakeLists.txt b/libs/viewer/CMakeLists.txt index 18c5a1cc62..9a2ababd03 100644 --- a/libs/viewer/CMakeLists.txt +++ b/libs/viewer/CMakeLists.txt @@ -26,7 +26,7 @@ set(SRCS # Include and target definitions # ================================================================================================== add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS}) -target_link_libraries(${TARGET} PUBLIC imgui filament gltfio filagui jsmn imageio) +target_link_libraries(${TARGET} PUBLIC imgui filament gltfio_core filagui jsmn) target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) # ================================================================================================== diff --git a/libs/viewer/include/viewer/AutomationEngine.h b/libs/viewer/include/viewer/AutomationEngine.h index a3ed876ad9..16f0b31bf3 100644 --- a/libs/viewer/include/viewer/AutomationEngine.h +++ b/libs/viewer/include/viewer/AutomationEngine.h @@ -28,78 +28,160 @@ class View; namespace viewer { /** - * Provides a convenient way to iterate through an AutomationSpec while pushing settings to Filament - * and exporting screenshots. + * The AutomationEngine iterates through settings permutations and pushes them to Filament, + * optionally exporting screenshots along the way. * - * Upon construction, the engine is given an immutable reference to an AutomationSpec. The engine is - * always in one of two states: running or idle. The running state can be entered either immediately + * Upon construction, automation is given an immutable reference to an AutomationSpec. It is + * always in one of two states: running or idle. The running state can be entered immediately * (startRunning) or by requesting batch mode (startBatchMode). * - * Clients must call tick() after each frame is rendered, which gives the engine an opportunity to - * increment the current test (if enough time has elapsed) and request an asychronous screenshot. - * The time to sleep between tests is configurable and can be set to zero. The engine also waits a + * Clients must call tick() after each frame is rendered, which gives automation an opportunity to + * push settings to Filament, increment the current test index (if enough time has elapsed), and + * request an asychronous screenshot. + * + * The time to sleep between tests is configurable and can be set to zero. Automation also waits a * specified minimum number of frames between tests. * - * Batch mode is meant for non-interactive applications. In batch mode, the engine defers applying + * Batch mode is meant for non-interactive applications. In batch mode, automation defers applying * the first test case until the client unblocks it via signalBatchMode(). This is useful when * waiting for a large model file to become fully loaded. Batch mode also offers a query - * (shouldClose) that is triggered after the last screenshot has been written to disk. + * (shouldClose) that is triggered after the last test has been invoked. */ class AutomationEngine { public: + /** + * Allows users to toggle screenshots, change the sleep duration between tests, etc. + */ + struct Options { + /** + * Minimum time that automation waits between applying a settings object and advancing + * to the next test case. Specified in seconds. + */ + float sleepDuration = 0.2; + + /** + * Similar to sleepDuration, but expressed as a frame count. Both the minimum sleep time + * and the minimum frame count must be elapsed before automation advances to the next test. + */ + int minFrameCount = 2; + + /** + * If true, test progress is dumped to the utils Log (info priority). + */ + bool verbose = true; + + /** + * If true, the tick function writes out a screenshot before advancing to the next test. + */ + bool exportScreenshots = false; + + /** + * If true, the tick function writes out a settings JSON file before advancing. + */ + bool exportSettings = false; + }; + + /** + * Creates an automation engine and places it in an idle state. + * + * @param spec Specifies a set of settings permutations (owned by the client). + * @param settings Client-owned settings object. This not only supplies the initial + * state, it also receives changes during tick(). This is useful when + * building automation into an application that has a settings UI. + * + * @see setOptions + * @see startRunning + */ AutomationEngine(const AutomationSpec* spec, Settings* settings) : mSpec(spec), mSettings(settings) {} - // Enters the running state. + /** + * Shortcut constructor that creates an automation engine from a JSON string. + * + * This constructor can be used if the user does not need to monitor how the settings + * change over time and does not need ownership over the AutomationSpec. + * + * An example of a JSON spec can be found by searching the repo for DEFAULT_AUTOMATION. + * This is documented using a JSON schema (look for viewer/schemas/automation.json). + * + * @param jsonSpec Valid JSON string that conforms to the automation schema. + * @param size Number of characters in the JSON string. + * @return Automation engine or null if unable to read the JSON. + */ + static AutomationEngine* createFromJSON(const char* jsonSpec, size_t size); + + /** + * Creates an automation engine for the default test sequence. + * + * To see how the default test sequence is generated, search for DEFAULT_AUTOMATION. + */ + static AutomationEngine* createDefaultTest(); + + ~AutomationEngine(); + + /** + * Activates automation. During the subsequent call to tick(), the first test is applied + * and automation enters the running state. + */ void startRunning(); - // Enters the running state in batch mode. + /** + * Activates automation, but enters a paused state until the user calls signalBatchMode(). + */ void startBatchMode(); - // Notifies the engine that time has passed and a new frame has been rendered. - // This is when settings get applied, screenshots are (optionally) exported, etc. + /** + * Notifies the automation engine that time has passed and a new frame has been rendered. + * + * This is when settings get applied, screenshots are (optionally) exported, etc. + * + * @param view The Filament View that automation pushes changes to. + * @param materials An optional set of of materials that can receive parameter tweaks. + * @param materialCount The number of items in the materials array. + * @param renderer The Filament Renderer that can be used to take screenshots. + * @param deltaTime The amount of time that has passed since the previous tick in seconds. + */ void tick(View* view, MaterialInstance* const* materials, size_t materialCount, Renderer* renderer, float deltaTime); - // Signals that batch mode can begin. Call this after all meshes and textures finish loading. + /** + * Signals that batch mode can begin. Call this after all meshes and textures finish loading. + */ void signalBatchMode() { mBatchModeAllowed = true; } - // Cancels an in-progress automation session. + /** + * Cancels an in-progress automation session. + */ void stopRunning() { mIsRunning = false; } - // Signals that the application is closing, so all pending screenshots should be cancelled. + /** + * Signals that the application is closing, so all pending screenshots should be cancelled. + */ void terminate(); - // Convenience function that writes out a JSON file to disk. - static void exportSettings(const Settings& settings, const char* filename); - - struct Options { - // Minimum time that the engine waits between applying a settings object and subsequently - // taking a screenshot. After the screenshot is taken, the engine immediately advances to - // the next test case. Specified in seconds. - float sleepDuration = 0.2; - - // If true, the tick function writes out a screenshot before advancing to the next test. - bool exportScreenshots = false; - - // If true, the tick function writes out a settings JSON file before advancing. - bool exportSettings = false; - - // Similar to sleepDuration, but expressed as a frame count. Both the minimum sleep time - // and the minimum frame count must be elapsed before the engine advances to the next test. - int minFrameCount = 2; - - // If true, test progress is dumped to the utils Log (info priority). - bool verbose = true; - }; - - Options getOptions() const { return mOptions; } + /** + * Configures the automation engine for users who wish to set up a custom sleep time + * between tests, etc. + */ void setOptions(Options options) { mOptions = options; } + /** + * Returns true if automation is in batch mode and all tests have finished. + */ + bool shouldClose() const { return mShouldClose; } + + /** + * Convenience function that writes out a JSON file to disk containing all settings. + * + * @param Settings State vector to serialize. + * @param filename Desired JSON filename. + */ + static void exportSettings(const Settings& settings, const char* filename); + + Options getOptions() const { return mOptions; } bool isRunning() const { return mIsRunning; } size_t currentTest() const { return mCurrentTest; } size_t testCount() const { return mSpec->size(); } - bool shouldClose() const { return mShouldClose; } bool isBatchModeEnabled() const { return mBatchModeEnabled; } const char* getStatusMessage() const; @@ -116,6 +198,7 @@ private: bool mShouldClose = false; bool mBatchModeAllowed = false; bool mTerminated = false; + bool mOwnsSettings = false; public: // For internal use from a screenshot callback. diff --git a/libs/viewer/src/AutomationEngine.cpp b/libs/viewer/src/AutomationEngine.cpp index b314b04b2c..e1505e69f5 100644 --- a/libs/viewer/src/AutomationEngine.cpp +++ b/libs/viewer/src/AutomationEngine.cpp @@ -16,10 +16,6 @@ #include -#include - -#include - #include #include @@ -32,7 +28,6 @@ #include #include -using namespace image; using namespace utils; namespace filament { @@ -47,12 +42,12 @@ struct ScreenshotState { AutomationEngine* engine; }; -void exportScreenshot(View* view, Renderer* renderer, std::string filename, +static void exportScreenshot(View* view, Renderer* renderer, std::string filename, bool autoclose, AutomationEngine* automationEngine) { const Viewport& vp = view->getViewport(); const size_t byteCount = vp.width * vp.height * 3; - // Create a buffer descriptor that writes the PNG after the data becomes ready on the CPU. + // Create a buffer descriptor that writes the PPM after the data becomes ready on the CPU. backend::PixelBufferDescriptor buffer( new uint8_t[byteCount], byteCount, backend::PixelBufferDescriptor::PixelDataFormat::RGB, @@ -65,12 +60,10 @@ void exportScreenshot(View* view, Renderer* renderer, std::string filename, return; } const Viewport& vp = state->view->getViewport(); - LinearImage image(toLinear(vp.width, vp.height, vp.width * 3, - static_cast(buffer))); Path out(state->filename); - std::ofstream outputStream(out, std::ios::binary | std::ios::trunc); - ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "", - state->filename); + std::ofstream ppmStream(out); + ppmStream << "P6 " << vp.width << " " << vp.height << " " << 255 << std::endl; + ppmStream.write(static_cast(buffer), vp.width * vp.height * 3); delete[] static_cast(buffer); if (state->autoclose) { state->engine->requestClose(); @@ -85,6 +78,35 @@ void exportScreenshot(View* view, Renderer* renderer, std::string filename, std::move(buffer)); } +AutomationEngine* AutomationEngine::createFromJSON(const char* jsonSpec, size_t size) { + AutomationSpec* spec = AutomationSpec::generate(jsonSpec, size); + if (!spec) { + return nullptr; + } + Settings* settings = new Settings(); + AutomationEngine* result = new AutomationEngine(spec, settings); + result->mOwnsSettings = true; + return result; +} + +AutomationEngine* AutomationEngine::createDefaultTest() { + AutomationSpec* spec = AutomationSpec::generateDefaultTestCases(); + if (!spec) { + return nullptr; + } + Settings* settings = new Settings(); + AutomationEngine* result = new AutomationEngine(spec, settings); + result->mOwnsSettings = true; + return result; +} + +AutomationEngine::~AutomationEngine() { + if (mOwnsSettings) { + delete mSpec; + delete mSettings; + } +} + void AutomationEngine::startRunning() { mRequestStart = true; } @@ -157,7 +179,7 @@ void AutomationEngine::tick(View* view, MaterialInstance* const* materials, size } if (mOptions.exportScreenshots) { - exportScreenshot(view, renderer, prefix + ".png", isLastTest, this); + exportScreenshot(view, renderer, prefix + ".ppm", isLastTest, this); } if (isLastTest) { diff --git a/libs/viewer/src/AutomationSpec.cpp b/libs/viewer/src/AutomationSpec.cpp index d01386b675..4bdda6f43d 100644 --- a/libs/viewer/src/AutomationSpec.cpp +++ b/libs/viewer/src/AutomationSpec.cpp @@ -37,6 +37,7 @@ static const bool VERBOSE = false; namespace filament { namespace viewer { +// The default spec generates 65 test cases. static const char* DEFAULT_AUTOMATION = R"TXT([ { "name": "ppoff",