diff --git a/ios/src/PolyvoxFilamentFFIApi.cpp b/ios/src/PolyvoxFilamentFFIApi.cpp index b3ba8099..ba56561b 100644 --- a/ios/src/PolyvoxFilamentFFIApi.cpp +++ b/ios/src/PolyvoxFilamentFFIApi.cpp @@ -2,475 +2,446 @@ #include "PolyvoxFilamentFFIApi.h" #include "FilamentViewer.hpp" -#include "filament/LightManager.h" #include "Log.hpp" #include "ThreadPool.hpp" +#include "filament/LightManager.h" -#include #include +#include +#include using namespace polyvox; -class RenderLoop -{ +class RenderLoop { public: - explicit RenderLoop() - { - _t = new std::thread([this]() - { - while(!_stop) { - if(_rendering) { - doRender(); - } - std::function task; - { - std::unique_lock lock(_access); - if(_tasks.empty()) { - _cond.wait_for(lock, std::chrono::duration(_frameIntervalInMilliseconds)); - continue; - } - task = std::move(_tasks.front()); - _tasks.pop_front(); - } - task(); - } }); - } - ~RenderLoop() - { - _stop = true; - _t->join(); - } + explicit RenderLoop() { + _t = new std::thread([this]() { + while (!_stop) { + { + if (_rendering) { + doRender(); + } + } + std::function task; + { + std::unique_lock lock(_access); + if (_tasks.empty()) { + _cond.wait_for(lock, std::chrono::duration( + _frameIntervalInMilliseconds)); + continue; + } + task = std::move(_tasks.front()); + _tasks.pop_front(); + } + task(); + } + }); + } + ~RenderLoop() { + _stop = true; + _t->join(); + } - void* const createViewer( - void* const context, - void* const platform, - const char* uberArchivePath, - const ResourceLoaderWrapper* const loader, - void (*renderCallback)(void*), void* const owner - ) { - _renderCallback = renderCallback; - _renderCallbackOwner = owner; - std::packaged_task lambda([&]() mutable - { - return new FilamentViewer(context, loader, platform, uberArchivePath); - }); - auto fut = add_task(lambda); - fut.wait(); - _viewer = fut.get(); - return (void* const)_viewer; - } + void *const createViewer(void *const context, void *const platform, + const char *uberArchivePath, + const ResourceLoaderWrapper *const loader, + void (*renderCallback)(void *), void *const owner) { + _renderCallback = renderCallback; + _renderCallbackOwner = owner; + std::packaged_task lambda([&]() mutable { + std::thread::id this_id = std::this_thread::get_id(); + return new FilamentViewer(context, loader, platform, uberArchivePath); + }); + auto fut = add_task(lambda); + fut.wait(); + _viewer = fut.get(); + return (void *const)_viewer; + } - void destroyViewer() { - std::packaged_task lambda([&]() mutable { - _rendering = false; - destroy_filament_viewer(_viewer); - _viewer = nullptr; - }); - auto fut = add_task(lambda); - fut.wait(); - } + void destroyViewer() { + std::packaged_task lambda([&]() mutable { + _rendering = false; + destroy_filament_viewer(_viewer); + _viewer = nullptr; + }); + auto fut = add_task(lambda); + fut.wait(); + } - void setRendering(bool rendering) - { - std::packaged_task lambda([&]() mutable - { - this->_rendering = rendering; - }); - auto fut = add_task(lambda); - fut.wait(); - } + void setRendering(bool rendering) { + std::packaged_task lambda( + [&]() mutable { this->_rendering = rendering; }); + auto fut = add_task(lambda); + fut.wait(); + } - void doRender() - { - render(_viewer, 0, nullptr, nullptr, nullptr); - _renderCallback(_renderCallbackOwner); - } + void doRender() { + render(_viewer, 0, nullptr, nullptr, nullptr); + _renderCallback(_renderCallbackOwner); + } - void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) { - _frameIntervalInMilliseconds = frameIntervalInMilliseconds; - } + void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) { + _frameIntervalInMilliseconds = frameIntervalInMilliseconds; + } - template - auto add_task(std::packaged_task &pt) -> std::future - { - std::unique_lock lock(_access); - auto ret = pt.get_future(); - _tasks.push_back([pt = std::make_shared>(std::move(pt))] - { (*pt)(); }); - _cond.notify_one(); - return ret; - } + template + auto add_task(std::packaged_task &pt) -> std::future { + std::unique_lock lock(_access); + auto ret = pt.get_future(); + _tasks.push_back([pt = std::make_shared>( + std::move(pt))] { (*pt)(); }); + _cond.notify_one(); + return ret; + } private: - bool _stop = false; - bool _rendering = false; - float _frameIntervalInMilliseconds = 1000.0 / 60.0; - std::mutex _access; - FilamentViewer *_viewer = nullptr; - void (*_renderCallback)(void* const) = nullptr; - void *_renderCallbackOwner = nullptr; - std::thread *_t = nullptr; - std::condition_variable _cond; - std::deque> _tasks; + bool _stop = false; + bool _rendering = false; + float _frameIntervalInMilliseconds = 1000.0 / 60.0; + std::mutex _access; + FilamentViewer *_viewer = nullptr; + void (*_renderCallback)(void *const) = nullptr; + void *_renderCallbackOwner = nullptr; + std::thread *_t = nullptr; + std::condition_variable _cond; + std::deque> _tasks; }; -extern "C" -{ +extern "C" { - static RenderLoop *_rl; +static RenderLoop *_rl; - FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi( - void* const context, - void* const platform, - const char* uberArchivePath, - const ResourceLoaderWrapper* const loader, - void (*renderCallback)(void* const renderCallbackOwner), - void* const renderCallbackOwner) { - if (!_rl) - { - _rl = new RenderLoop(); - } - return _rl->createViewer(context, platform,uberArchivePath, loader, renderCallback, renderCallbackOwner); - } - - FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer) { - _rl->destroyViewer(); - } - - FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height) - { - Log("Creating swapchain %dx%d", width, height); - std::packaged_task lambda([&]() mutable - { - create_swap_chain(viewer, surface, width, height); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void* const viewer) - { - Log("Destroying swapchain"); - std::packaged_task lambda([&]() mutable - { - destroy_swap_chain(viewer); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height) - { - std::packaged_task lambda([&]() mutable - { create_render_target(viewer, nativeTextureId, width, height); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection_ffi(void* const viewer, const uint32_t width, const uint32_t height, const float scaleFactor) - { - Log("Update viewport %dx%d", width, height); - std::packaged_task lambda([&]() mutable - { - update_viewport_and_camera_projection(viewer, width, height, scaleFactor); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void set_rendering_ffi(void* const viewer, bool rendering) - { - if (!_rl) - { - Log("No render loop!"); // PANIC? - } - else - { - if (rendering) - { - Log("Set rendering to true"); - } - else - { - Log("Set rendering to false"); - } - _rl->setRendering(rendering); - } - } - - FLUTTER_PLUGIN_EXPORT void set_frame_interval_ffi(float frameIntervalInMilliseconds) { - _rl->setFrameIntervalInMilliseconds(frameIntervalInMilliseconds); - } - - FLUTTER_PLUGIN_EXPORT void render_ffi(void* const viewer) - { - std::packaged_task lambda([&]() mutable - { - _rl->doRender(); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void set_background_color_ffi(void* const viewer, const float r, const float g, const float b, const float a) - { - std::packaged_task lambda([&]() mutable - { set_background_color(viewer, r, g, b, a); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT EntityId load_gltf_ffi(void* const assetManager, const char *path, const char *relativeResourcePath) - { - std::packaged_task lambda([&]() mutable - { return load_gltf(assetManager, path, relativeResourcePath); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - FLUTTER_PLUGIN_EXPORT EntityId load_glb_ffi(void* const assetManager, const char *path, bool unlit) - { - std::packaged_task lambda([&]() mutable - { return load_glb(assetManager, path, unlit); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - FLUTTER_PLUGIN_EXPORT void clear_background_image_ffi(void* const viewer) - { - std::packaged_task lambda([&] - { - clear_background_image(viewer); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void set_background_image_ffi(void* const viewer, const char *path, bool fillHeight) - { - std::packaged_task lambda([&] - { - set_background_image(viewer, path, fillHeight); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void set_background_image_position_ffi(void* const viewer, float x, float y, bool clamp) - { - std::packaged_task lambda([&] - { set_background_image_position(viewer, x, y, clamp); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void set_tone_mapping_ffi(void* const viewer, int toneMapping) - { - std::packaged_task lambda([&] - { set_tone_mapping(viewer, toneMapping); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void set_bloom_ffi(void* const viewer, float strength) - { - std::packaged_task lambda([&] - { set_bloom(viewer, strength); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void load_skybox_ffi(void* const viewer, const char *skyboxPath) - { - std::packaged_task lambda([&] - { load_skybox(viewer, skyboxPath); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void load_ibl_ffi(void* const viewer, const char *iblPath, float intensity) - { - std::packaged_task lambda([&] - { load_ibl(viewer, iblPath, intensity); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void remove_skybox_ffi(void* const viewer) - { - std::packaged_task lambda([&] - { remove_skybox(viewer); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void remove_ibl_ffi(void* const viewer) - { - std::packaged_task lambda([&] - { remove_ibl(viewer); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - EntityId add_light_ffi(void* const viewer, uint8_t type, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows) - { - std::packaged_task lambda([&] - { return add_light(viewer, type, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - FLUTTER_PLUGIN_EXPORT void remove_light_ffi(void* const viewer, EntityId entityId) - { - std::packaged_task lambda([&] - { remove_light(viewer, entityId); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void clear_lights_ffi(void* const viewer) - { - std::packaged_task lambda([&] - { clear_lights(viewer); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void remove_asset_ffi(void* const viewer, EntityId asset) - { - std::packaged_task lambda([&] - { remove_asset(viewer, asset); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - FLUTTER_PLUGIN_EXPORT void clear_assets_ffi(void* const viewer) - { - std::packaged_task lambda([&] - { clear_assets(viewer); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT bool set_camera_ffi(void* const viewer, EntityId asset, const char *nodeName) - { - std::packaged_task lambda([&] - { return set_camera(viewer, asset, nodeName); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - FLUTTER_PLUGIN_EXPORT void set_bone_animation_ffi( - void *assetManager, - EntityId asset, - const float *const frameData, - int numFrames, - int numBones, - const char **const boneNames, - const char **const meshName, - int numMeshTargets, - float frameLengthInMs) - { - std::packaged_task lambda([&] - { set_bone_animation( - assetManager, asset, frameData, numFrames, numBones, - boneNames, meshName, numMeshTargets, frameLengthInMs); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void get_morph_target_name_ffi(void *assetManager, EntityId asset, const char *meshName, char *const outPtr, int index) - { - std::packaged_task lambda([&] - { get_morph_target_name(assetManager, asset, meshName, outPtr, index); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT int get_morph_target_name_count_ffi(void *assetManager, EntityId asset, const char *meshName) - { - std::packaged_task lambda([&] - { return get_morph_target_name_count(assetManager, asset, meshName); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - void set_morph_target_weights_ffi( - void* const assetManager, - EntityId asset, - const char *const entityName, - const float *const morphData, - int numWeights) - { - // TODO - } - - FLUTTER_PLUGIN_EXPORT void play_animation_ffi(void* const assetManager, EntityId asset, int index, bool loop, bool reverse, bool replaceActive, float crossfade) - { - std::packaged_task lambda([&] - { play_animation(assetManager, asset, index, loop, reverse, replaceActive, crossfade); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void set_animation_frame_ffi(void* const assetManager, EntityId asset, int animationIndex, int animationFrame) - { - std::packaged_task lambda([&] - { set_animation_frame(assetManager, asset, animationIndex, animationFrame); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void stop_animation_ffi(void* const assetManager, EntityId asset, int index) - { - std::packaged_task lambda([&] - { stop_animation(assetManager, asset, index); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT int get_animation_count_ffi(void* const assetManager, EntityId asset) - { - std::packaged_task lambda([&] - { return get_animation_count(assetManager, asset); }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void* const assetManager, EntityId asset, char *const outPtr, int index) - { - std::packaged_task lambda([&] { - get_animation_name(assetManager, asset, outPtr, index); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void* const viewer, bool enabled) { - std::packaged_task lambda([&] { - set_post_processing(viewer, enabled); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT void pick_ffi(void* const viewer, int x, int y, EntityId* entityId) { - std::packaged_task lambda([&] { - pick(viewer, x, y, entityId); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - } - - FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity_ffi(void* const assetManager, const EntityId entityId) { - std::packaged_task lambda([&] { - return get_name_for_entity(assetManager, entityId); - }); - auto fut = _rl->add_task(lambda); - fut.wait(); - return fut.get(); - } - - - FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() { - Log("Dummy called"); - } +FLUTTER_PLUGIN_EXPORT void *const create_filament_viewer_ffi( + void *const context, void *const platform, const char *uberArchivePath, + const ResourceLoaderWrapper *const loader, + void (*renderCallback)(void *const renderCallbackOwner), + void *const renderCallbackOwner) { + if (!_rl) { + _rl = new RenderLoop(); + } + return _rl->createViewer(context, platform, uberArchivePath, loader, + renderCallback, renderCallbackOwner); +} + +FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void *const viewer) { + _rl->destroyViewer(); +} + +FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void *const viewer, + void *const surface, + uint32_t width, + uint32_t height) { + Log("Creating swapchain %dx%d", width, height); + std::packaged_task lambda( + [&]() mutable { create_swap_chain(viewer, surface, width, height); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void *const viewer) { + Log("Destroying swapchain"); + std::packaged_task lambda( + [&]() mutable { + destroy_swap_chain(viewer); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void *const viewer, + intptr_t nativeTextureId, + uint32_t width, + uint32_t height) { + std::packaged_task lambda([&]() mutable { + create_render_target(viewer, nativeTextureId, width, height); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection_ffi( + void *const viewer, const uint32_t width, const uint32_t height, + const float scaleFactor) { + Log("Update viewport %dx%d", width, height); + std::packaged_task lambda([&]() mutable { + update_viewport_and_camera_projection(viewer, width, height, scaleFactor); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void set_rendering_ffi(void *const viewer, + bool rendering) { + if (!_rl) { + Log("No render loop!"); // PANIC? + } else { + if (rendering) { + Log("Set rendering to true"); + } else { + Log("Set rendering to false"); + } + _rl->setRendering(rendering); + } +} + +FLUTTER_PLUGIN_EXPORT void +set_frame_interval_ffi(float frameIntervalInMilliseconds) { + _rl->setFrameIntervalInMilliseconds(frameIntervalInMilliseconds); +} + +FLUTTER_PLUGIN_EXPORT void render_ffi(void *const viewer) { + std::packaged_task lambda([&]() mutable { _rl->doRender(); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void +set_background_color_ffi(void *const viewer, const float r, const float g, + const float b, const float a) { + std::packaged_task lambda( + [&]() mutable { set_background_color(viewer, r, g, b, a); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT EntityId load_gltf_ffi(void *const assetManager, + const char *path, + const char *relativeResourcePath) { + std::packaged_task lambda([&]() mutable { + return load_gltf(assetManager, path, relativeResourcePath); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +FLUTTER_PLUGIN_EXPORT EntityId load_glb_ffi(void *const assetManager, + const char *path, bool unlit) { + std::packaged_task lambda( + [&]() mutable { return load_glb(assetManager, path, unlit); }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +FLUTTER_PLUGIN_EXPORT void clear_background_image_ffi(void *const viewer) { + std::packaged_task lambda([&] { clear_background_image(viewer); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void set_background_image_ffi(void *const viewer, + const char *path, + bool fillHeight) { + std::packaged_task lambda( + [&] { set_background_image(viewer, path, fillHeight); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void set_background_image_position_ffi(void *const viewer, + float x, float y, + bool clamp) { + std::packaged_task lambda( + [&] { set_background_image_position(viewer, x, y, clamp); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void set_tone_mapping_ffi(void *const viewer, + int toneMapping) { + std::packaged_task lambda( + [&] { set_tone_mapping(viewer, toneMapping); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void set_bloom_ffi(void *const viewer, float strength) { + std::packaged_task lambda([&] { set_bloom(viewer, strength); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void load_skybox_ffi(void *const viewer, + const char *skyboxPath) { + std::packaged_task lambda([&] { load_skybox(viewer, skyboxPath); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void load_ibl_ffi(void *const viewer, const char *iblPath, + float intensity) { + std::packaged_task lambda( + [&] { load_ibl(viewer, iblPath, intensity); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void remove_skybox_ffi(void *const viewer) { + std::packaged_task lambda([&] { remove_skybox(viewer); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void remove_ibl_ffi(void *const viewer) { + std::packaged_task lambda([&] { remove_ibl(viewer); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +EntityId add_light_ffi(void *const viewer, uint8_t type, float colour, + float intensity, float posX, float posY, float posZ, + float dirX, float dirY, float dirZ, bool shadows) { + std::packaged_task lambda([&] { + return add_light(viewer, type, colour, intensity, posX, posY, posZ, dirX, + dirY, dirZ, shadows); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +FLUTTER_PLUGIN_EXPORT void remove_light_ffi(void *const viewer, + EntityId entityId) { + std::packaged_task lambda([&] { remove_light(viewer, entityId); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void clear_lights_ffi(void *const viewer) { + std::packaged_task lambda([&] { clear_lights(viewer); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void remove_asset_ffi(void *const viewer, + EntityId asset) { + std::packaged_task lambda([&] { remove_asset(viewer, asset); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} +FLUTTER_PLUGIN_EXPORT void clear_assets_ffi(void *const viewer) { + std::packaged_task lambda([&] { clear_assets(viewer); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT bool set_camera_ffi(void *const viewer, EntityId asset, + const char *nodeName) { + std::packaged_task lambda( + [&] { return set_camera(viewer, asset, nodeName); }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +FLUTTER_PLUGIN_EXPORT void set_bone_animation_ffi( + void *assetManager, EntityId asset, const float *const frameData, + int numFrames, int numBones, const char **const boneNames, + const char **const meshName, int numMeshTargets, float frameLengthInMs) { + std::packaged_task lambda([&] { + set_bone_animation(assetManager, asset, frameData, numFrames, numBones, + boneNames, meshName, numMeshTargets, frameLengthInMs); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void +get_morph_target_name_ffi(void *assetManager, EntityId asset, + const char *meshName, char *const outPtr, int index) { + std::packaged_task lambda([&] { + get_morph_target_name(assetManager, asset, meshName, outPtr, index); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT int +get_morph_target_name_count_ffi(void *assetManager, EntityId asset, + const char *meshName) { + std::packaged_task lambda([&] { + return get_morph_target_name_count(assetManager, asset, meshName); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +void set_morph_target_weights_ffi(void *const assetManager, EntityId asset, + const char *const entityName, + const float *const morphData, + int numWeights) { + // TODO +} + +FLUTTER_PLUGIN_EXPORT void play_animation_ffi(void *const assetManager, + EntityId asset, int index, + bool loop, bool reverse, + bool replaceActive, + float crossfade) { + std::packaged_task lambda([&] { + play_animation(assetManager, asset, index, loop, reverse, replaceActive, + crossfade); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void set_animation_frame_ffi(void *const assetManager, + EntityId asset, + int animationIndex, + int animationFrame) { + std::packaged_task lambda([&] { + set_animation_frame(assetManager, asset, animationIndex, animationFrame); + }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void stop_animation_ffi(void *const assetManager, + EntityId asset, int index) { + std::packaged_task lambda( + [&] { stop_animation(assetManager, asset, index); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT int get_animation_count_ffi(void *const assetManager, + EntityId asset) { + std::packaged_task lambda( + [&] { return get_animation_count(assetManager, asset); }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} +FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void *const assetManager, + EntityId asset, + char *const outPtr, + int index) { + std::packaged_task lambda( + [&] { get_animation_name(assetManager, asset, outPtr, index); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void *const viewer, + bool enabled) { + std::packaged_task lambda( + [&] { set_post_processing(viewer, enabled); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT void pick_ffi(void *const viewer, int x, int y, + EntityId *entityId) { + std::packaged_task lambda([&] { pick(viewer, x, y, entityId); }); + auto fut = _rl->add_task(lambda); + fut.wait(); +} + +FLUTTER_PLUGIN_EXPORT const char * +get_name_for_entity_ffi(void *const assetManager, const EntityId entityId) { + std::packaged_task lambda( + [&] { return get_name_for_entity(assetManager, entityId); }); + auto fut = _rl->add_task(lambda); + fut.wait(); + return fut.get(); +} + +FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() { Log("Dummy called"); } } diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 93f82946..f3f9417e 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -8,11 +8,18 @@ typedef FilamentEntity = int; enum ToneMapper { ACES, FILMIC, LINEAR } +class TextureDetails { + final int textureId; + final int width; + final int height; + + TextureDetails({required this.textureId, required this.width, required this.height}); +} + abstract class FilamentController { // the current target size of the viewport, in logical pixels ui.Size size = ui.Size.zero; - Stream get textureId; Future get isReadyForScene; /// @@ -54,6 +61,7 @@ abstract class FilamentController { /// Future destroyViewer(); + /// /// Destroys the backing texture. You probably want to call [destroy] instead of this; this is exposed mostly for lifecycle changes which are handled by FilamentWidget. /// @@ -71,13 +79,13 @@ abstract class FilamentController { /// 5) The FilamentWidget will replace the empty Container with a Texture widget /// If you need to wait until a FilamentViewer has been created, [await] the [isReadyForScene] Future. /// - Future createViewer(int width, int height); + Future createViewer(int width, int height); /// /// Resize the viewport & backing texture. /// This is called by FilamentWidget; you shouldn't need to invoke this manually. /// - Future resize(int width, int height, {double scaleFactor = 1.0}); + Future resize(int width, int height, {double scaleFactor = 1.0}); /// /// Set the background image to [path] (which should have a file extension .png, .jpg, or .ktx). diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index 9ba29b01..70484db1 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -11,14 +11,13 @@ import 'package:polyvox_filament/generated_bindings.dart'; const FilamentEntity _FILAMENT_ASSET_ERROR = 0; + class FilamentControllerFFI extends FilamentController { late MethodChannel _channel = MethodChannel("app.polyvox.filament/event"); double _pixelRatio = 1.0; int? _textureId; - final _textureIdController = StreamController.broadcast(); - Stream get textureId => _textureIdController.stream; Completer _isReadyForScene = Completer(); Future get isReadyForScene => _isReadyForScene.future; @@ -105,20 +104,21 @@ class FilamentControllerFFI extends FilamentController { @override Future destroyTexture() async { + if(_textureId != null) { + throw Exception("No texture available"); + } print("Destroying texture"); // we need to flush all references to the previous texture ID before calling destroy, otherwise the Texture widget will attempt to render a non-existent texture and crash. // however, this is not a synchronous stream, so we need to ensure the Texture widget has been removed from the hierarchy before destroying _textureId = null; - _textureIdController.add(null); - - await _channel.invokeMethod("destroyTexture"); + await _channel.invokeMethod("destroyTexture", _textureId!); print("Texture destroyed"); } /// /// Called by `FilamentWidget`. You do not need to call this yourself. /// - Future createViewer(int width, int height) async { + Future createViewer(int width, int height) async { if (_viewer != null) { throw Exception( "Viewer already exists, make sure you call destroyViewer first"); @@ -133,8 +133,6 @@ class FilamentControllerFFI extends FilamentController { throw Exception("Failed to get resource loader"); } - print("Using loader ${loader.address}"); - size = ui.Size(width * _pixelRatio, height * _pixelRatio); print("Creating viewer with size $size"); @@ -195,45 +193,99 @@ class FilamentControllerFFI extends FilamentController { _assetManager = _lib.get_asset_manager(_viewer!); - _textureIdController.add(_textureId); - _isReadyForScene.complete(true); + return TextureDetails(textureId: _textureId!, width: width, height:height); } /// - /// I'm not exactly sure how to resize the backing textures on all platforms. - /// So for now, I'm sticking with the safe option when the widget is resized: destroying the swapchain, recreating the textures, and creating a new swapchain. + /// When a FilamentWidget is resized, it will call [resize]. This method will tear down/recreate the swapchain and propagate a new texture ID back to the FilamentWidget. + /// For "once-off" resizes, this is fine. + /// However, this can be problematic for consecutive resizes (e.g. dragging to expand/contract the parent window on desktop, or animating the size of the FilamentWidget itself). + /// It is too expensive to recreate the swapchain multiple times per second. + /// We therefore add a timer to FilamentWidget so that the call to [resize] is delayed (e.g. 50ms). + /// Any subsequent resizes before the delay window elapses will cancel the earlier call. + /// + /// The overall process looks like this: + /// 1) the window is resized + /// 2) (Windows only) PixelBufferTexture is requested to provide a new pixel buffer with a new size, and we return an empty texture + /// 3) After Xms, [resize] is invoked + /// 4) the viewer is instructed to stop rendering (synchronous) + /// 5) the existing Filament swapchain is destroyed (synchronous) + /// 6) the Flutter texture is unregistered + /// a) this is asynchronous, but + /// b) *** SEE NOTE BELOW ON WINDOWS *** by passing the method channel result through to the callback, we make this synchronous from the Flutter side, + // c) in this async callback, the glTexture is destroyed + /// 7) a new Flutter/OpenGL texture is created (synchronous) + /// 8) a new swapchain is created (synchronous) + /// 9) if the viewer was rendering prior to the resize, the viewer is instructed to recommence rendering + /// 10) the new texture ID is pushed to the FilamentWidget + /// 11) the FilamentWidget updates the Texture widget with the new texture. + /// + /// #### (Windows-only) ############################################################ + /// # As soon as the widget/window is resized, the PixelBufferTexture will be + /// # requested to provide a new pixel buffer for the new size. + /// # Even with zero delay to the call to [resize], this will be triggered *before* + /// # we have had a chance to anything else (like tear down the swapchain). + /// # On the backend, we deal with this by simply returning an empty texture as soon + /// # as the size changes, and will rely on the followup call to [resize] to actually + /// # destroy/recreate the pixel buffer and Flutter texture. + /// + /// NOTE RE ASYNC CALLBACK + /// # The bigger problem is a race condition when resize is called multiple times in quick succession (e.g dragging to resize on Windows). + /// # It looks like occasionally, the backend OpenGL texture is being destroyed while its corresponding swapchain is still active, causing a crash. + /// # I'm not exactly sure how/where this is occurring, but something clearly isn't synchronized between destroy_swap_chain_ffi and + /// # the asynchronous callback passed to FlutterTextureRegistrar::UnregisterTexture. + /// # Theoretically this could occur if resize_2 starts before resize_1 completes, i.e. + /// # 1) resize_1 destroys swapchain/texture and creates new texture + /// # 2) resize_2 destroys swapchain/texture + /// # 3) resize_1 creates new swapchain but texture isn't available, ergo crash + /// # + /// # I don't think this should happen if: + /// # 1) we add a flag on the Flutter side to ensure only one call to destroy/recreate the swapchain/texture is active at any given time, and + /// # 2) on the Flutter side, we are sure that calling destroyTexture only returns once the async callback on the native side has completed. + /// # For (1), checking if textureId is null at the entrypoint should be sufficient. + /// # For (2), we invoke flutter::MethodResult->Success in the UnregisterTexture callback. + /// # + /// # Maybe (2) doesn't actually make Flutter wait? + /// # + /// # The other possibility is that both (1) and (2) are fine and the issue is elsewhere. + /// # + /// # Either way, the current solution is to basically setup a double-buffer on resize. + /// # When destroyTexture is called, the active texture isn't destroyed yet, it's only marked as inactive. + /// # On subsequent calls to destroyTexture, the inactive texture is destroyed. + /// # This seems to work fine. + /// + /// # Another option is to only use a single large (e.g. 4k) texture and simply crop whenever a resize is requested. + /// # This might be preferable for other reasons (e.g. don't need to destroy/recreate the pixel buffer or swapchain). + /// # Given we don't do this on other platforms, I'm OK to stick with the existing solution for the time being. + /// ############################################################################ + /// + /// + /// + /// Other options: + /// 1) never destroy the texture, simply allocate a large (4k?) texture and crop as needed + /// 2) double-buffering? @override - Future resize(int width, int height, {double scaleFactor = 1.0}) async { + Future resize(int width, int height, {double scaleFactor = 1.0}) async { if (_textureId == null) { - print("No texture created, ignoring call to resize."); - return; + throw Exception("No texture created, ignoring call to resize."); } - - if (_resizing) { - print("Resize currently underway, ignoring"); - return; - } - - bool wasRendering = _rendering; - if (_viewer != null && _rendering) { - await setRendering(false); - } - - _resizing = true; - + var textureId = _textureId; + _textureId = null; + + _lib.set_rendering_ffi(_viewer!, false); + if (_viewer != null) { _lib.destroy_swap_chain_ffi(_viewer!); } - await destroyTexture(); + + await _channel.invokeMethod("destroyTexture", textureId); + size = ui.Size(width * _pixelRatio, height * _pixelRatio); var textures = await _channel.invokeMethod("createTexture", [size.width, size.height]); - print("Created new texture"); - var flutterTextureId = textures[0]; - _textureId = flutterTextureId; // void* on iOS (pointer to pixel buffer), void* on Android (pointer to native window), null on Windows/macOS var surfaceAddress = textures[1] as int? ?? 0; @@ -256,11 +308,10 @@ class FilamentControllerFFI extends FilamentController { _lib.update_viewport_and_camera_projection_ffi( _viewer!, size.width.toInt(), size.height.toInt(), 1.0); - _textureIdController.add(_textureId); - _resizing = false; - if (wasRendering) { - setRendering(true); - } + await setRendering(_rendering); + _textureId = textures[0]; + + return TextureDetails(textureId: _textureId!, width: width, height:height); } @override diff --git a/lib/filament_controller_method_channel.dart b/lib/filament_controller_method_channel.dart index 1b2aa2ea..66c1e957 100644 --- a/lib/filament_controller_method_channel.dart +++ b/lib/filament_controller_method_channel.dart @@ -91,7 +91,8 @@ class FilamentControllerMethodChannel extends FilamentController { /// 3) Initially, this widget will only contain an empty Container. After the first frame is rendered, the widget itself will automatically call [createViewer] with the width/height from its constraints /// 4) The FilamentWidget will replace the empty Container with the Texture widget. /// - Future createViewer(int width, int height) async { + Future createViewer(int width, int height) async { + throw Exception(); if (_viewer != null) { throw Exception( "Viewer already exists, make sure you call destroyViewer first"); @@ -119,7 +120,9 @@ class FilamentControllerMethodChannel extends FilamentController { bool _resizing = false; - Future resize(int width, int height, {double scaleFactor = 1.0}) async { + + Future resize(int width, int height, {double scaleFactor = 1.0}) async { + throw Exception(); _resizing = true; _textureId = await _channel.invokeMethod( "resize", [width * _pixelRatio, height * _pixelRatio, scaleFactor]); diff --git a/lib/widgets/filament_widget.dart b/lib/widgets/filament_widget.dart index 8e44a460..61b93c66 100644 --- a/lib/widgets/filament_widget.dart +++ b/lib/widgets/filament_widget.dart @@ -56,10 +56,8 @@ class FilamentWidget extends StatefulWidget { /// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered. /// final Widget? initial; - final void Function()? onResize; - const FilamentWidget( - {Key? key, required this.controller, this.onResize, this.initial}) + const FilamentWidget({Key? key, required this.controller, this.initial}) : super(key: key); @override @@ -67,23 +65,21 @@ class FilamentWidget extends StatefulWidget { } class _FilamentWidgetState extends State { - StreamSubscription? _textureIdListener; - int? _textureId; + TextureDetails? _textureDetails; late final AppLifecycleListener _listener; AppLifecycleState? _lastState; - bool _resizing = false; - String? _error; - Timer? _resizeTimer; + int? _width; + int? _height; void _handleStateChange(AppLifecycleState state) async { switch (state) { case AppLifecycleState.detached: print("Detached"); - _textureId = null; + _textureDetails = null; await widget.controller.destroyViewer(); await widget.controller.destroyTexture(); @@ -91,7 +87,7 @@ class _FilamentWidgetState extends State { case AppLifecycleState.hidden: print("Hidden"); if (Platform.isIOS) { - _textureId = null; + _textureDetails = null; await widget.controller.destroyViewer(); await widget.controller.destroyTexture(); } @@ -104,12 +100,16 @@ class _FilamentWidgetState extends State { break; case AppLifecycleState.resumed: print("Resumed"); - if (_textureId == null) { - var size = ((context.findRenderObject()) as RenderBox).size; - print("Size after resuming : $size"); - await widget.controller - .createViewer(size.width.toInt(), size.height.toInt()); - print("Created viewer Size after resuming"); + if (!Platform.isWindows) { + if (_textureDetails == null) { + var size = ((context.findRenderObject()) as RenderBox).size; + print("Size after resuming : $size"); + _height = size.height.ceil(); + _width = size.width.ceil(); + await widget.controller + .createViewer(_width!, _height!); + print("Created viewer Size after resuming"); + } } break; } @@ -130,9 +130,12 @@ class _FilamentWidgetState extends State { await Future.delayed(Duration(seconds: 2)); } var size = ((context.findRenderObject()) as RenderBox).size; - + _width = size.width.ceil(); + _height = size.height.ceil(); try { - await widget.controller.createViewer(size.width.toInt(), size.height.toInt()); + _textureDetails = await widget.controller + .createViewer(_width!, _height!); + } catch (err) { setState(() { _error = err.toString(); @@ -140,28 +143,20 @@ class _FilamentWidgetState extends State { } }); - _textureIdListener = widget.controller.textureId.listen((int? textureId) { - var size = ((context.findRenderObject()) as RenderBox).size; - print( - "Received new texture ID $textureId at size $size (current textureID $_textureId)"); - setState(() { - _textureId = textureId; - }); - }); - super.initState(); } @override void dispose() { - _textureIdListener?.cancel(); _listener.dispose(); - _resizeTimer?.cancel(); super.dispose(); } + Timer? _resizeTimer; + @override Widget build(BuildContext context) { + // if an error was encountered in creating a viewer, display the error message and don't even try to display a Texture widget. if (_error != null) { return Container( color: Colors.white, @@ -170,51 +165,51 @@ class _FilamentWidgetState extends State { Text(_error!) ])); } - return LayoutBuilder(builder: ((context, constraints) { - if (_textureId == null) { - return widget.initial ?? Container(color: Colors.red); - } - var texture = Texture( - key: ObjectKey("texture_$_textureId"), - textureId: _textureId!, + + // if no texture ID is available, display the [initial] widget (solid red by default) + late Widget content; + + if ( _textureDetails == null || _textureDetails!.height != _height || _textureDetails!.width != _width) { + content = widget.initial ?? Container(color: Colors.red); + } else { + content = Texture( + key: ObjectKey("texture_${_textureDetails!.textureId}"), + textureId: _textureDetails!.textureId, filterQuality: FilterQuality.none, + freeze: false, ); - return SizedBox( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: ResizeObserver( - onResized: (Size oldSize, Size newSize) async { - WidgetsBinding.instance.addPostFrameCallback((_) async { - _resizeTimer?.cancel(); + } - _resizeTimer = Timer(Duration(milliseconds: 500), () async { - // setState(() { - // _resizing = true; - // }); + // see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing + return ResizeObserver( + onResized: (Size oldSize, Size newSize) async { - // TODO - we could snapshot the widget to display while we resize? + _resizeTimer?.cancel(); - print("Resizing to $newSize"); - // await widget.controller - // .resize(newSize.width.toInt(), newSize.height.toInt()); - // WidgetsBinding.instance.addPostFrameCallback((_) async { - // setState(() { - // _resizing = false; - // widget.onResize?.call(); - // }); - // }); - }); + WidgetsBinding.instance.addPostFrameCallback((_) { + _resizeTimer = Timer(const Duration(milliseconds:50), () async { + var newWidth = newSize.width.ceil(); + var newHeight = newSize.height.ceil(); + _textureDetails = await widget.controller + .resize(newWidth, newHeight); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _width = newWidth; + _height = newHeight; }); - }, - child: _resizing - ? Container() - : Platform.isLinux || Platform.isWindows - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationX( - pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? - child: texture) - : texture)); - })); + }); + }); + }); + }, + child: Stack(children: [ + Positioned.fill( + child: Platform.isLinux || Platform.isWindows + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationX( + pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? + child: content) + : content) + ])); } } diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index e1471ed0..1a41aa20 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -12,6 +12,7 @@ set(PLUGIN_NAME "polyvox_filament_plugin") list(APPEND PLUGIN_SOURCES "polyvox_filament_plugin.cpp" "polyvox_filament_plugin.h" + "opengl_texture_buffer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/AssetManager.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/FilamentViewer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/PolyvoxFilamentApi.cpp" @@ -69,8 +70,8 @@ if(USE_ANGLE) else() list(APPEND GL_LIBS bluegl - bluevk - vkshaders + # bluevk + # vkshaders opengl32 ) set(ANGLE_OR_OPENGL_DIR opengl) diff --git a/windows/opengl_texture_buffer.cpp b/windows/opengl_texture_buffer.cpp new file mode 100644 index 00000000..0e3cf1d4 --- /dev/null +++ b/windows/opengl_texture_buffer.cpp @@ -0,0 +1,129 @@ +#include "opengl_texture_buffer.h" + +#include +#include +#include +#include + +#include + +namespace polyvox_filament { + +void _release_callback(void *releaseContext) { + // ((OpenGLTextureBuffer*)releaseContext)->unlock(); +} + +OpenGLTextureBuffer::OpenGLTextureBuffer( + flutter::PluginRegistrarWindows *pluginRegistrar, + flutter::TextureRegistrar *textureRegistrar, + std::unique_ptr> result, + uint32_t width, uint32_t height, HGLRC context, + std::shared_ptr renderMutex) + : _pluginRegistrar(pluginRegistrar), _textureRegistrar(textureRegistrar), + _width(width), _height(height), _context(context), + _renderMutex(renderMutex) { + + HWND hwnd = _pluginRegistrar->GetView()->GetNativeWindow(); + + HDC whdc = GetDC(hwnd); + + if (!_context || !wglMakeCurrent(whdc, _context)) { + result->Error("ERROR", "Failed to switch OpenGL context in constructor."); + return; + } + + glGenTextures(1, &glTextureId); + + if (glTextureId == 0) { + result->Error("ERROR", "Failed to generate texture, OpenGL err was %d", + glGetError()); + return; + } + + glBindTexture(GL_TEXTURE_2D, glTextureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _width, _height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, 0); + + GLenum err = glGetError(); + + if (err != GL_NO_ERROR) { + result->Error("ERROR", "Failed to generate texture, GL error was %d", err); + return; + } + wglMakeCurrent(NULL, NULL); + + pixelBuffer = std::make_unique(); + pixelData.reset(new uint8_t[_width * _height * 4]); + + pixelBuffer->buffer = pixelData.get(); + pixelBuffer->width = size_t(_width); + pixelBuffer->height = size_t(_height); + pixelBuffer->release_callback = _release_callback; + pixelBuffer->release_context = this; + + std::cout << "Created initial pixel data/buffer of size " << _width << "x" + << _height << std::endl; + + texture = + std::make_unique(flutter::PixelBufferTexture( + [=](size_t width, + size_t height) -> const FlutterDesktopPixelBuffer * { + if (width != this->_width || height != this->_height) { + std::cout << "Front-end widget has been resized, you need to " + "teardown/rebuild the swapchain. This pixel buffer " + "will be discarded." + << std::endl; + return nullptr; + } + uint8_t *data = (uint8_t *)pixelData.get(); + + if (!_context || !wglMakeCurrent(whdc, _context)) { + std::cout << "Failed to switch OpenGL context in callback." + << std::endl; + } else { + // It seems there's at least 1 frame delay between resizing a + // front-end widget and the layout operation being performed on + // Windows. I haven't found a way to guarantee that we can resize + // the OpenGL texture before the pixel buffer callback here. (If + // you can find/suggest a way, please let me know). This means we + // need to manually check that the requested size matches the + // current size of our GL texture, and return an empty pixel + // buffer if not. + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + GLenum err = glGetError(); + + if (err != GL_NO_ERROR) { + if (err == GL_INVALID_OPERATION) { + std::cout << "Invalid op" << std::endl; + } else if (err == GL_INVALID_VALUE) { + std::cout << "Invalid value" << std::endl; + } else if (err == GL_OUT_OF_MEMORY) { + std::cout << "Out of mem" << std::endl; + } else if (err == GL_INVALID_ENUM) { + std::cout << "Invalid enum" << std::endl; + } else { + std::cout << "Unknown error" << std::endl; + } + } + wglMakeCurrent(NULL, NULL); + } + pixelBuffer->buffer = pixelData.get(); + return pixelBuffer.get(); + })); + + flutterTextureId = textureRegistrar->RegisterTexture(texture.get()); + std::cout << "Registered Flutter texture ID " << flutterTextureId + << std::endl; + std::vector resultList; + resultList.push_back(flutter::EncodableValue(flutterTextureId)); + resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); + resultList.push_back(flutter::EncodableValue(glTextureId)); + result->Success(resultList); +} + +OpenGLTextureBuffer::~OpenGLTextureBuffer() {} + +} // namespace polyvox_filament \ No newline at end of file diff --git a/windows/opengl_texture_buffer.h b/windows/opengl_texture_buffer.h new file mode 100644 index 00000000..44e58c97 --- /dev/null +++ b/windows/opengl_texture_buffer.h @@ -0,0 +1,53 @@ +#pragma once + +#ifndef _OPENGL_TEXTURE_BUFFER_H +#define _OPENGL_TEXTURE_BUFFER_H + +#include + +#include +#include +#include + +#include "GL/GL.h" +#include "GL/GLu.h" +#include "GL/wglext.h" + +#include +#include + +typedef uint32_t GLuint; + +namespace polyvox_filament { + +class OpenGLTextureBuffer { + public: + + OpenGLTextureBuffer( + flutter::PluginRegistrarWindows* pluginRegistrar, + flutter::TextureRegistrar* textureRegistrar, + std::unique_ptr> result, + uint32_t width, + uint32_t height, + HGLRC context, + std::shared_ptr renderMutex); + + ~OpenGLTextureBuffer(); + GLuint glTextureId = 0; + int64_t flutterTextureId = 0; + std::unique_ptr pixelBuffer; + std::unique_ptr pixelData; + std::unique_ptr texture; + + private: + flutter::PluginRegistrarWindows* _pluginRegistrar; + flutter::TextureRegistrar* _textureRegistrar; + uint32_t _width = 0; + uint32_t _height = 0; + HGLRC _context = NULL; + bool logged = false; + std::shared_ptr _renderMutex; +}; + +} +#endif // _OPENGL_TEXTURE_BUFFER_H \ No newline at end of file diff --git a/windows/polyvox_filament_plugin.cpp b/windows/polyvox_filament_plugin.cpp index 2407f701..c02ef6af 100644 --- a/windows/polyvox_filament_plugin.cpp +++ b/windows/polyvox_filament_plugin.cpp @@ -33,10 +33,6 @@ #include "PlatformANGLE.h" #endif -#include "GL/GL.h" -#include "GL/GLu.h" -#include "GL/wglext.h" - #include #include @@ -83,7 +79,7 @@ ResourceBuffer PolyvoxFilamentPlugin::loadResource(const char *name) { name_str = name_str.substr(8); } - TCHAR pBuf[256]; + TCHAR pBuf[512]; size_t len = sizeof(pBuf); int bytes = GetModuleFileName(NULL, pBuf, len); std::wstring_convert> converter; @@ -145,7 +141,10 @@ void PolyvoxFilamentPlugin::RenderCallback() { _internalD3DTexture2D.Get()); _D3D11DeviceContext->Flush(); #endif - _textureRegistrar->MarkTextureFrameAvailable(_flutterTextureId); + std::lock_guard guard(*(_renderMutex.get())); + if (_active) { + _textureRegistrar->MarkTextureFrameAvailable(_active->flutterTextureId); + } } #ifdef USE_ANGLE @@ -293,8 +292,9 @@ bool PolyvoxFilamentPlugin::MakeD3DTexture(uint32_t width, uint32_t height,std:: } #else bool PolyvoxFilamentPlugin::MakeOpenGLTexture(uint32_t width, uint32_t height,std::unique_ptr> result) { + HWND hwnd = _pluginRegistrar->GetView() - ->GetNativeWindow();; + ->GetNativeWindow(); HDC whdc = GetDC(hwnd); if (whdc == NULL) { @@ -302,146 +302,99 @@ bool PolyvoxFilamentPlugin::MakeOpenGLTexture(uint32_t width, uint32_t height,st return false; } - PIXELFORMATDESCRIPTOR pfd = { - sizeof(PIXELFORMATDESCRIPTOR), - 1, - PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags - PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette. - 32, // Colordepth of the framebuffer. - 0, 0, 0, 0, 0, 0, - 0, - 0, - 0, - 0, 0, 0, 0, - 32, // Number of bits for the depthbuffer - 0, // Number of bits for the stencilbuffer - 0, // Number of Aux buffers in the framebuffer. - PFD_MAIN_PLANE, - 0, - 0, 0, 0 - }; + if(!_renderMutex.get()) { + _renderMutex = std::make_shared(); + } - int pixelFormat = ChoosePixelFormat(whdc, &pfd); - SetPixelFormat(whdc, pixelFormat, &pfd); - // We need a tmp context to retrieve and call wglCreateContextAttribsARB. - HGLRC tempContext = wglCreateContext(whdc); - if (!wglMakeCurrent(whdc, tempContext)) { - result->Error("ERROR", "Failed to acquire temporary context", nullptr); + // we need a single context (since this will be passed to the renderer) + // if this is the first time we are attempting to create a texture, let's create the context + if(_context == NULL) { + + std::cout << "No GL context exists, creating" << std::endl; + + PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags + PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette. + 32, // Colordepth of the framebuffer. + 0, 0, 0, 0, 0, 0, + 0, + 0, + 0, + 0, 0, 0, 0, + 32, // Number of bits for the depthbuffer + 0, // Number of bits for the stencilbuffer + 0, // Number of Aux buffers in the framebuffer. + PFD_MAIN_PLANE, + 0, + 0, 0, 0 + }; + + int pixelFormat = ChoosePixelFormat(whdc, &pfd); + SetPixelFormat(whdc, pixelFormat, &pfd); + + // We need a tmp context to retrieve and call wglCreateContextAttribsARB. + HGLRC tempContext = wglCreateContext(whdc); + if (!wglMakeCurrent(whdc, tempContext)) { + result->Error("ERROR", "Failed to acquire temporary context", nullptr); + } + + GLenum err = glGetError(); + + if(err != GL_NO_ERROR) { + result->Error("ERROR", "GL Error @ 455 %d", err); + return false; + } + + PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr; + + wglCreateContextAttribs = + (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( + "wglCreateContextAttribsARB"); + + if (!wglCreateContextAttribs) { + result->Error("ERROR", "Failed to resolve wglCreateContextAttribsARB", + nullptr); + return false; + } + + for (int minor = 5; minor >= 1; minor--) { + std::vector mAttribs = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4, + WGL_CONTEXT_MINOR_VERSION_ARB, minor, 0}; + _context = wglCreateContextAttribs(whdc, nullptr, mAttribs.data()); + if (_context) { + break; + } + } + + wglMakeCurrent(NULL, NULL); + wglDeleteContext(tempContext); + + if (!_context || !wglMakeCurrent(whdc, _context)) { + result->Error("ERROR", "Failed to create OpenGL context."); + return false; + } + + } + + if(_active.get()) { + result->Error("ERROR", "Texture already exists. You must call destroyTexture before attempting to create a new one."); return false; - } + } - GLenum err = glGetError(); - - if(err != GL_NO_ERROR) { - result->Error("ERROR", "GL Error @ 455 %d", err); - return false; - } - - PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr; - - wglCreateContextAttribs = - (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( - "wglCreateContextAttribsARB"); - - if (!wglCreateContextAttribs) { - result->Error("ERROR", "Failed to resolve wglCreateContextAttribsARB", - nullptr); - return false; - } - - for (int minor = 5; minor >= 1; minor--) { - std::vector mAttribs = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4, - WGL_CONTEXT_MINOR_VERSION_ARB, minor, 0}; - _context = wglCreateContextAttribs(whdc, nullptr, mAttribs.data()); - if (_context) { - break; - } - } - - wglMakeCurrent(NULL, NULL); - wglDeleteContext(tempContext); - - - if (!_context || !wglMakeCurrent(whdc, _context)) { - result->Error("ERROR", "Failed to create OpenGL context."); - return false; - } - - glGenTextures(1, &_glTextureId); - - if(_glTextureId == 0) { - result->Error("ERROR", "Failed to generate texture, OpenGL err was %d", glGetError()); - return false; - } - - glBindTexture(GL_TEXTURE_2D, _glTextureId); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, 0); - - err = glGetError(); - - if (err != GL_NO_ERROR) { - result->Error("ERROR", "Failed to generate texture, GL error was %d", err); - return false; - } - wglMakeCurrent(NULL, NULL); - - _pixelData.reset(new uint8_t[width * height * 4]); - _pixelBuffer = std::make_unique(); - _pixelBuffer->buffer = _pixelData.get(); - - _pixelBuffer->width = size_t(width); - _pixelBuffer->height = size_t(height); - - _texture = std::make_unique(flutter::PixelBufferTexture( - [=](size_t width, - size_t height) -> const FlutterDesktopPixelBuffer * { - std::lock_guard guard(_renderMutex); - - if(!_context || !wglMakeCurrent(whdc, _context)) { - std::cout << "Failed to switch OpenGL context." << std::endl; - } else if (_glTextureId != 0) { - // uint8_t* data = (uint8_t*)_pixelData.get(); - uint8_t* data = new uint8_t[width*height*4]; - glBindTexture(GL_TEXTURE_2D, _glTextureId); - glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,data); - - GLenum err = glGetError(); - - if(err != GL_NO_ERROR) { - if(err == GL_INVALID_OPERATION) { - std::cout << "Invalid op" << std::endl; - } else if(err == GL_INVALID_VALUE) { - std::cout << "Invalid value" << std::endl; - } else if(err == GL_OUT_OF_MEMORY) { - std::cout << "Out of mem" << std::endl; - } else if(err == GL_INVALID_ENUM ) { - std::cout << "Invalid enum" << std::endl; - } else { - std::cout << "Unknown error" << std::endl; - } - } - glFinish(); - _pixelData.reset(data); - wglMakeCurrent(NULL, NULL); - } - _pixelBuffer->buffer = _pixelData.get(); - - return _pixelBuffer.get(); - })); - - _flutterTextureId = _textureRegistrar->RegisterTexture(_texture.get()); - std::cout << "Registered Flutter texture ID " << _flutterTextureId << std::endl; - - std::vector resultList; - resultList.push_back(flutter::EncodableValue(_flutterTextureId)); - resultList.push_back(flutter::EncodableValue((int64_t)nullptr)); - resultList.push_back(flutter::EncodableValue(_glTextureId)); - result->Success(resultList); - return true; + _active = std::make_unique( + _pluginRegistrar, + _textureRegistrar, + std::move(result), + width, + height, + _context, + _renderMutex + ); + + return _active->flutterTextureId != -1; } #endif @@ -467,30 +420,53 @@ void PolyvoxFilamentPlugin::DestroyTexture( const flutter::MethodCall &methodCall, std::unique_ptr> result) { + const auto *flutterTextureId = + std::get_if(methodCall.arguments()); + + if(!flutterTextureId) { + result->Error("NOT_IMPLEMENTED", "Flutter texture ID must be provided"); + return; + } + #ifdef USE_ANGLE // TODO result->Error("NOT_IMPLEMENTED", "Method is not implemented %s", methodCall.method_name()); #else - std::lock_guard guard(_renderMutex); + auto sh = std::make_shared>>(std::move(result)); - HWND hwnd = _pluginRegistrar->GetView() - ->GetNativeWindow(); - - HDC whdc = GetDC(hwnd); - - if(!_context || !wglMakeCurrent(whdc, _context)) { - result->Error("CONTEXT", "Failed to switch OpenGL context.", nullptr); + if(!_active) { + result->Success("Texture has already been detroyed, ignoring"); return; } - // for now we will just unregister the Flutter texture and delete the GL texture - // we will leave the pixel data/PixelBufferTexture intact - these will be replaced on the next call to createTexture - // if we wanted to be militant about cleaning up unused memory we could delete here first - // _textureRegistrar->UnregisterTexture(_flutterTextureId); - // _flutterTextureId = -1; - // glDeleteTextures(1, &_glTextureId); - // _glTextureId = 0; - // wglMakeCurrent(NULL, NULL); - result->Success(flutter::EncodableValue(true)); + + if(_active->flutterTextureId != *flutterTextureId) { + result->Error("TEXTURE_MISMATCH", "Specified texture ID is not active"); + return; + } + + _textureRegistrar->UnregisterTexture(_active->flutterTextureId, [=, + sharedResult=std::move(sh) + ]() { + + if(this->_inactive) { + + HWND hwnd = _pluginRegistrar->GetView()->GetNativeWindow(); + + HDC whdc = GetDC(hwnd); + + if (!wglMakeCurrent(whdc, _context)) { + std::cout << "Failed to switch OpenGL context in destructor." << std::endl; + // result->Error("CONTEXT", "Failed to switch OpenGL context.", nullptr); + return; + } + glDeleteTextures(1, &this->_inactive->glTextureId); + wglMakeCurrent(NULL, NULL); + } + this->_inactive = std::move(this->_active); + auto unique = std::move(*(sharedResult.get())); + unique->Success(flutter::EncodableValue(true)); + std::cout << "Destroyed OpenGLTextureBuffer." << std::endl; + }); #endif } @@ -524,8 +500,7 @@ void PolyvoxFilamentPlugin::HandleMethodCall( #else result->Success(flutter::EncodableValue((int64_t)nullptr)); #endif - } - else { + } else { result->Error("NOT_IMPLEMENTED", "Method is not implemented %s", methodCall.method_name()); } } diff --git a/windows/polyvox_filament_plugin.h b/windows/polyvox_filament_plugin.h index 12027af2..8b80519e 100644 --- a/windows/polyvox_filament_plugin.h +++ b/windows/polyvox_filament_plugin.h @@ -26,6 +26,8 @@ #include "GLES2/gl2.h" #include "GLES2/gl2ext.h" #include "PlatformAngle.h" +#else +#include "opengl_texture_buffer.h" #endif #include "PolyvoxFilamentApi.h" @@ -54,15 +56,8 @@ public: std::map _resources; - std::unique_ptr _texture = nullptr; - - std::unique_ptr _pixelBuffer = nullptr; - std::unique_ptr _pixelData = nullptr; - std::unique_ptr _textureDescriptor = nullptr; - int64_t _flutterTextureId = -1; - #ifdef USE_ANGLE // Device ID3D11Device* _D3D11Device = nullptr; @@ -75,12 +70,13 @@ public: filament::backend::PlatformANGLE* _platform = nullptr; bool MakeD3DTexture(uint32_t width, uint32_t height, std::unique_ptr> result); - #else - // OpenGL - HGLRC _context = NULL; - GLuint _glTextureId = 0; - std::mutex _renderMutex; + #else + std::shared_ptr _renderMutex; + std::unique_ptr _active = nullptr; + std::unique_ptr _inactive = nullptr; + // shared OpenGLContext + HGLRC _context = NULL; bool MakeOpenGLTexture(uint32_t width, uint32_t height, std::unique_ptr> result); #endif @@ -90,7 +86,6 @@ public: void DestroyTexture( const flutter::MethodCall &methodCall, std::unique_ptr> result); - void RenderCallback(); ResourceBuffer loadResource(const char *path);