diff --git a/ios/include/FilamentViewer.hpp b/ios/include/FilamentViewer.hpp index 37ad9880..81c443b6 100644 --- a/ios/include/FilamentViewer.hpp +++ b/ios/include/FilamentViewer.hpp @@ -46,6 +46,7 @@ typedef int32_t EntityId; namespace polyvox { + enum ToneMapping { ACES, @@ -124,6 +125,7 @@ namespace polyvox void setPostProcessing(bool enabled); void setRecording(bool recording); + void setRecordingOutputDirectory(const char* path); AssetManager *const getAssetManager() @@ -156,11 +158,11 @@ namespace polyvox Skybox *_skybox = nullptr; Texture *_iblTexture = nullptr; IndirectLight *_indirectLight = nullptr; - bool _recomputeAabb = false; - bool _actualSize = false; + float _frameInterval = 1000.0 / 60.0; + // Camera properties Camera *_mainCamera = nullptr; // the default camera added to every scene. If you want the *active* camera, access via View. float _cameraFocalLength = 28.0f; @@ -192,13 +194,18 @@ namespace polyvox void loadKtxTexture(string path, ResourceBuffer data); void loadPngTexture(string path, ResourceBuffer data); void loadTextureFromPath(string path); - void savePng(void* data, size_t size); - - int _frameNumber = 0; + void savePng(void* data, size_t size, int frameNumber); - uint32_t _lastFrameTimeInNanos; + time_point_t _startTime = std::chrono::high_resolution_clock::now(); bool _recording = false; + std::string _recordingOutputDirectory = std::string("/tmp"); + std::mutex _recordingMutex; + }; + + struct FrameCallbackData { + FilamentViewer* viewer; + uint32_t frameNumber; }; } diff --git a/ios/include/FlutterFilamentApi.h b/ios/include/FlutterFilamentApi.h index b8da408c..a3d2af49 100644 --- a/ios/include/FlutterFilamentApi.h +++ b/ios/include/FlutterFilamentApi.h @@ -170,6 +170,7 @@ extern "C" FLUTTER_PLUGIN_EXPORT const char *get_name_for_entity(void *const assetManager, const EntityId entityId); FLUTTER_PLUGIN_EXPORT const EntityId find_child_entity_by_name(void *const assetManager, const EntityId parent, const char* name); FLUTTER_PLUGIN_EXPORT void set_recording(void *const viewer, bool recording); + FLUTTER_PLUGIN_EXPORT void set_recording_output_directory(void *const viewer, const char* outputDirectory); FLUTTER_PLUGIN_EXPORT void ios_dummy(); FLUTTER_PLUGIN_EXPORT void flutter_filament_free(void *ptr); #ifdef __cplusplus diff --git a/ios/include/ThreadPool.hpp b/ios/include/ThreadPool.hpp index ea743910..39b2e032 100644 --- a/ios/include/ThreadPool.hpp +++ b/ios/include/ThreadPool.hpp @@ -54,7 +54,7 @@ public: private: void add_worker() { std::thread t([this]() { - while(!stop) { + while(!stop || tasks.size() > 0) { std::function task; { std::unique_lock lock(access); diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 4916ab37..973d02a6 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -123,8 +123,6 @@ namespace polyvox : _resourceLoaderWrapper(resourceLoaderWrapper) { - _tp = new flutter_filament::ThreadPool(1); - ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); #if TARGET_OS_IPHONE @@ -139,12 +137,9 @@ namespace polyvox _renderer = _engine->createRenderer(); - float fr = 60.0f; - _renderer->setDisplayInfo({.refreshRate = fr}); + _frameInterval = 1000.0f / 60.0f; - Renderer::FrameRateOptions fro; - fro.interval = 1 / fr; - _renderer->setFrameRateOptions(fro); + setFrameInterval(_frameInterval); _scene = _engine->createScene(); @@ -307,6 +302,7 @@ namespace polyvox void FilamentViewer::setFrameInterval(float frameInterval) { + _frameInterval = frameInterval; Renderer::FrameRateOptions fro; fro.interval = frameInterval; _renderer->setFrameRateOptions(fro); @@ -1020,13 +1016,23 @@ namespace polyvox size_t pixelBufferSize = vp.width * vp.height * 4; auto* pixelBuffer = new uint8_t[pixelBufferSize]; auto callback = [](void *buf, size_t size, void *data) { - ((FilamentViewer*)data)->savePng(buf, size); + auto frameCallbackData = (FrameCallbackData*)data; + auto viewer = (FilamentViewer*)frameCallbackData->viewer; + viewer->savePng(buf, size, frameCallbackData->frameNumber); + delete frameCallbackData; }; + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = float(std::chrono::duration_cast(now - _startTime).count()); + + auto frameNumber = uint32_t(floor(elapsed / _frameInterval)); + + auto userData = new FrameCallbackData { this, frameNumber }; + auto pbd = Texture::PixelBufferDescriptor( pixelBuffer, pixelBufferSize, Texture::Format::RGBA, - Texture::Type::UBYTE, nullptr, callback, this); + Texture::Type::UBYTE, nullptr, callback, userData); _renderer->readPixels(_rt, 0, 0, vp.width, vp.height, std::move(pbd)); } @@ -1039,23 +1045,28 @@ namespace polyvox } } - void FilamentViewer::savePng(void* buf, size_t size) { + void FilamentViewer::savePng(void* buf, size_t size, int frameNumber) { + std::lock_guard lock(_recordingMutex); + if(!_recording) { + delete[] static_cast(buf); + return; + } - std::packaged_task lambda([=]() mutable { - - Viewport const &vp = _view->getViewport(); + Viewport const &vp = _view->getViewport(); + + std::packaged_task lambda([=]() mutable { int digits = 6; std::ostringstream stringStream; - stringStream << "/tmp/output_"; + stringStream << _recordingOutputDirectory << "/output_"; stringStream << std::setfill('0') << std::setw(digits); - stringStream << std::to_string(_frameNumber); + stringStream << std::to_string(frameNumber); stringStream << ".png"; std::string filename = stringStream.str(); ofstream wf(filename, ios::out | ios::binary); - // Log("%d %d %d", vp.width, vp.height, size); + LinearImage image(toLinearWithAlpha(vp.width, vp.height, vp.width * 4, static_cast(buf))); @@ -1064,7 +1075,6 @@ namespace polyvox ); delete[] static_cast(buf); - _frameNumber++; if(!result) { Log("Failed to encode"); @@ -1073,13 +1083,29 @@ namespace polyvox wf.close(); if(!wf.good()) { Log("Write failed!"); - } + } + }); _tp->add_task(lambda); } + void FilamentViewer::setRecordingOutputDirectory(const char* path) { + _recordingOutputDirectory = std::string(path); + auto outputDirAsPath = std::filesystem::path(path); + if(!std::filesystem::is_directory(outputDirAsPath)) { + std::filesystem::create_directories(outputDirAsPath); + } + } + void FilamentViewer::setRecording(bool recording) { + std::lock_guard lock(_recordingMutex); this->_recording = recording; + if(recording) { + _tp = new flutter_filament::ThreadPool(8); + _startTime = std::chrono::high_resolution_clock::now(); + } else { + delete _tp; + } } void FilamentViewer::updateViewportAndCameraProjection( diff --git a/ios/src/FlutterFilamentApi.cpp b/ios/src/FlutterFilamentApi.cpp index f66ffb15..51f1b679 100644 --- a/ios/src/FlutterFilamentApi.cpp +++ b/ios/src/FlutterFilamentApi.cpp @@ -487,6 +487,10 @@ extern "C" ((FilamentViewer*)viewer)->setRecording(recording); } + FLUTTER_PLUGIN_EXPORT void set_recording_output_directory(void *const viewer, const char* outputDirectory) { + ((FilamentViewer*)viewer)->setRecordingOutputDirectory(outputDirectory); + } + FLUTTER_PLUGIN_EXPORT void ios_dummy() { Log("Dummy called"); diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 11064c40..203ede7f 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -498,4 +498,9 @@ abstract class FilamentController { /// This will impact performance; handle with care. /// Future setRecording(bool recording); + + /// + /// Sets the output directory where recorded PNGs will be placed. + /// + Future setRecordingOutputDirectory(String outputDirectory); } diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index c11068a4..f3234ea7 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -1212,4 +1212,11 @@ class FilamentControllerFFI extends FilamentController { Future setRecording(bool recording) async { set_recording(_viewer!, recording); } + + @override + Future setRecordingOutputDirectory(String outputDir) async { + var pathPtr = outputDir.toNativeUtf8(allocator: calloc); + set_recording_output_directory(_viewer!, pathPtr.cast()); + calloc.free(pathPtr); + } } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index 6ff4b07e..51e69b8d 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -753,6 +753,14 @@ external void set_recording( bool recording, ); +@ffi.Native, ffi.Pointer)>( + symbol: 'set_recording_output_directory', + assetId: 'flutter_filament_plugin') +external void set_recording_output_directory( + ffi.Pointer viewer, + ffi.Pointer outputDirectory, +); + @ffi.Native( symbol: 'ios_dummy', assetId: 'flutter_filament_plugin') external void ios_dummy();