From 4b580b6eb1fbb56d274c3d142a3ff473de315105 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Fri, 26 Feb 2021 10:28:35 -0800 Subject: [PATCH] Add automation to Android build, change PNG to PPM. This adds libs/viewer to filament-utils-android and provides JNI bindings for AutomationEngine. This commit also changes the AutomationEngine to use PPM for screenshots rather than PNG. The reasoning is that it is wasteful to include stb and our two image-related libraries just to dump screenshots, especially for a feature we might not use on Android. --- CMakeLists.txt | 8 +- android/filament-android/libfilament-jni.map | 2 + android/filament-utils-android/CMakeLists.txt | 6 + .../src/main/cpp/AutomationEngine.cpp | 115 ++++++++++++ .../filament/utils/AutomationEngine.java | 167 ++++++++++++++++++ libs/filagui/src/ImGuiHelper.cpp | 1 - libs/viewer/CMakeLists.txt | 2 +- libs/viewer/include/viewer/AutomationEngine.h | 165 ++++++++++++----- libs/viewer/src/AutomationEngine.cpp | 48 +++-- libs/viewer/src/AutomationSpec.cpp | 1 + 10 files changed, 455 insertions(+), 60 deletions(-) create mode 100644 android/filament-utils-android/src/main/cpp/AutomationEngine.cpp create mode 100644 android/filament-utils-android/src/main/java/com/google/android/filament/utils/AutomationEngine.java 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",