diff --git a/windows/d3d11_output.cpp b/windows/d3d11_output.cpp index 0d49546..dd810d1 100644 --- a/windows/d3d11_output.cpp +++ b/windows/d3d11_output.cpp @@ -1,5 +1,8 @@ #include "d3d11_output.h" +#include +#include + #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") @@ -21,6 +24,14 @@ namespace flutter_gpu_texture_renderer { +namespace { +// Membership is checked under a shared lock on every push, so removal +// (unique lock, in ~D3D11Output) drains in-flight pushes before the object's +// memory goes away. +std::shared_mutex g_live_mutex; +std::unordered_set g_live_outputs; +} // namespace + D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar) : texture_registrar_(texture_registrar) { surface_desc_ = std::make_unique(); @@ -32,7 +43,12 @@ D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar) flutter::GpuSurfaceTexture(kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, [&](size_t width, size_t height) { std::lock_guard lock(mutex_); - rendering_ = true; + // A null-handle descriptor makes the engine + // bail before the release callback; setting + // the flag then would leave it stuck true. + if (desc_ready_) { + rendering_ = true; + } return surface_desc_.get(); })); @@ -41,11 +57,18 @@ D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar) } else { unusable_ = true; } + + { + std::unique_lock lock(g_live_mutex); + g_live_outputs.insert(this); + } } D3D11Output::~D3D11Output() { - if (texture_id_) - texture_registrar_->UnregisterTexture(texture_id_); + // Unregistration happens in the plugin's unregisterTexture; here only make + // sure no push is still running on this object and no later push reaches it. + std::unique_lock lock(g_live_mutex); + g_live_outputs.erase(this); } bool D3D11Output::SetTexture(void *texture) { @@ -64,6 +87,13 @@ bool D3D11Output::SetTexture(void *texture) { // https://api.flutter.dev/linux-embedder/flutter__texture__registrar_8h_source.html bool D3D11Output::EnsureTexture(ID3D11Texture2D *texture) { std::lock_guard lock(mutex_); + // The raster thread is reading tex_buffers_ through the shared handle; + // overwriting it mid-read tears. Frames can be sparse (damage-driven), so + // wait briefly instead of dropping; on timeout push through (transient tear + // beats a stale frame, and the release callback may be lost on error paths). + for (int i = 0; rendering_ && i < 8; i++) { + std::this_thread::sleep_for(std::chrono::microseconds(500)); + } if (rendering_) { std::cout << __FILE__ << " rendering: " << rendering_ << std::endl; } @@ -111,6 +141,7 @@ bool D3D11Output::EnsureTexture(ID3D11Texture2D *texture) { surface_desc_->release_callback = [](void *release_context) { D3D11Output *self = (D3D11Output *)release_context; // self->SetFPS(); + self->consumed_.fetch_add(1, std::memory_order_relaxed); self->rendering_ = false; }; desc_ready_ = true; @@ -140,4 +171,18 @@ void D3D11Output::SetFPS() { } } +bool D3D11OutputSetTexture(void *output, void *texture) { + std::shared_lock lock(g_live_mutex); + if (g_live_outputs.find(output) == g_live_outputs.end()) + return false; + return static_cast(output)->SetTexture(texture); +} + +uint64_t D3D11OutputConsumed(void *output) { + std::shared_lock lock(g_live_mutex); + if (g_live_outputs.find(output) == g_live_outputs.end()) + return 0; + return static_cast(output)->Consumed(); +} + } // namespace flutter_gpu_texture_renderer diff --git a/windows/d3d11_output.h b/windows/d3d11_output.h index 3b10582..6f1e6a5 100644 --- a/windows/d3d11_output.h +++ b/windows/d3d11_output.h @@ -2,6 +2,7 @@ #define FLUTTER_PLUGIN_D3D11_OUTPUT_PLUGIN_H_ #include +#include #include #include #include @@ -23,6 +24,7 @@ class D3D11Output { bool SetTexture(void *texture); bool Present(); int16_t Fps() { return last_fps_; } + uint64_t Consumed() { return consumed_.load(std::memory_order_relaxed); } private: D3D11Output() = delete; @@ -45,12 +47,19 @@ class D3D11Output { std::atomic_char16_t this_fps_ = 0; std::atomic fps_time_point_ = std::chrono::steady_clock::now(); + std::atomic consumed_ = 0; bool unusable_ = false; bool desc_ready_ = false; size_t fail_counter_ = 0; - bool rendering_ = false; + std::atomic rendering_ = false; }; +// Rust's decode thread pushes textures through a raw D3D11Output pointer with +// no lifetime contract; these validate the pointer against the set of live +// objects so a concurrent unregister cannot free memory out from under a push. +bool D3D11OutputSetTexture(void *output, void *texture); +uint64_t D3D11OutputConsumed(void *output); + } // namespace flutter_gpu_texture_renderer #endif // FLUTTER_PLUGIN_D3D11_OUTPUT_PLUGIN_H_ diff --git a/windows/flutter_gpu_texture_renderer_plugin.cpp b/windows/flutter_gpu_texture_renderer_plugin.cpp index 52baa67..32a8b7c 100644 --- a/windows/flutter_gpu_texture_renderer_plugin.cpp +++ b/windows/flutter_gpu_texture_renderer_plugin.cpp @@ -76,12 +76,19 @@ void FlutterGpuTextureRendererPlugin::HandleMethodCall( } else if (method_call.method_name().compare("unregisterTexture") == 0) { auto args = std::get(*method_call.arguments()); auto id = args.at(flutter::EncodableValue("id")).LongValue(); - auto new_end = - std::remove_if(outputs_.begin(), outputs_.end(), - [id](const std::unique_ptr &output) { - return output->TextureId() == id; - }); - outputs_.erase(new_end, outputs_.end()); + auto it = std::find_if(outputs_.begin(), outputs_.end(), + [id](const std::unique_ptr &output) { + return output->TextureId() == id; + }); + if (it != outputs_.end()) { + // UnregisterTexture only posts the engine-side removal to the raster + // thread; deleting here would race an in-flight surface fetch or + // release callback. Keep the object alive until the engine is done. + std::shared_ptr output = std::move(*it); + outputs_.erase(it); + registrar_->texture_registrar()->UnregisterTexture(output->TextureId(), + [output] {}); + } return result->Success(); } else if (method_call.method_name().compare("output") == 0) { auto args = std::get(*method_call.arguments()); diff --git a/windows/flutter_gpu_texture_renderer_plugin_c_api.cpp b/windows/flutter_gpu_texture_renderer_plugin_c_api.cpp index 90acb2d..269f880 100644 --- a/windows/flutter_gpu_texture_renderer_plugin_c_api.cpp +++ b/windows/flutter_gpu_texture_renderer_plugin_c_api.cpp @@ -14,14 +14,19 @@ void FlutterGpuTextureRendererPluginCApiRegisterWithRegistrar( ->GetRegistrar(registrar)); } -using flutter_gpu_texture_renderer::D3D11Output; - void FlutterGpuTextureRendererPluginCApiSetTexture(void *output, void *texture) { if (!output || !texture) return; - D3D11Output *d3d11Output = (D3D11Output *)(output); - d3d11Output->SetTexture(texture); + // The pointer may already have been unregistered by the Dart side; the push + // validates it against the live-object set instead of dereferencing. + flutter_gpu_texture_renderer::D3D11OutputSetTexture(output, texture); +} + +uint64_t FlutterGpuTextureRendererPluginCApiGetConsumed(void *output) { + if (!output) + return 0; + return flutter_gpu_texture_renderer::D3D11OutputConsumed(output); } int64_t FlutterGpuTextureRendererPluginCApiGetAdapterLuid() { diff --git a/windows/include/flutter_gpu_texture_renderer/flutter_gpu_texture_renderer_plugin_c_api.h b/windows/include/flutter_gpu_texture_renderer/flutter_gpu_texture_renderer_plugin_c_api.h index 25501bc..63321e4 100644 --- a/windows/include/flutter_gpu_texture_renderer/flutter_gpu_texture_renderer_plugin_c_api.h +++ b/windows/include/flutter_gpu_texture_renderer/flutter_gpu_texture_renderer_plugin_c_api.h @@ -2,6 +2,7 @@ #define FLUTTER_PLUGIN_FLUTTER_GPU_TEXTURE_RENDERER_PLUGIN_C_API_H_ #include +#include #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) @@ -20,6 +21,11 @@ FLUTTER_PLUGIN_EXPORT void FlutterGpuTextureRendererPluginCApiSetTexture(void *o FLUTTER_PLUGIN_EXPORT int64_t FlutterGpuTextureRendererPluginCApiGetAdapterLuid(); +// Frames for which the engine fetched this output's surface descriptor; an +// EGL bind failure still advances it, so 0 means "never composited", not +// "rendered correctly". Also 0 if the output is unknown/unregistered. +FLUTTER_PLUGIN_EXPORT uint64_t FlutterGpuTextureRendererPluginCApiGetConsumed(void *output); + #if defined(__cplusplus) } // extern "C" #endif