Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions windows/d3d11_output.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "d3d11_output.h"

#include <shared_mutex>
#include <unordered_set>

#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")

Expand All @@ -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<void *> g_live_outputs;
Comment on lines +31 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent stale handle aliasing.

Line 32 stores only an address. If an old output is destroyed and a new output reuses that address, g_live_outputs.find at Line 171 accepts a stale producer handle and sends its frame to the new output.

Use an opaque handle with a generation value, or retain non-reusable handle tokens until the producer releases them. The registry must validate output identity, not only the current address.

Also applies to: 169-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@windows/d3d11_output.cpp` around lines 31 - 32, Replace the address-only
entries managed by g_live_mutex and g_live_outputs with non-reusable opaque
handle tokens or generation-tagged handles, and associate each token with its
specific output instance. Update the producer-handle validation and lookup flow
around the g_live_outputs.find check at lines 169–180 to reject handles from
destroyed outputs, even when memory addresses are reused, while preserving valid
current-output frame delivery.

} // namespace

D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar)
: texture_registrar_(texture_registrar) {
surface_desc_ = std::make_unique<FlutterDesktopGpuSurfaceDescriptor>();
Expand All @@ -32,7 +43,12 @@ D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar)
flutter::GpuSurfaceTexture(kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle,
[&](size_t width, size_t height) {
std::lock_guard<std::mutex> 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();
}));

Expand All @@ -41,11 +57,18 @@ D3D11Output::D3D11Output(flutter::TextureRegistrar *texture_registrar)
} else {
unusable_ = true;
}

{
std::unique_lock<std::shared_mutex> 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<std::shared_mutex> lock(g_live_mutex);
g_live_outputs.erase(this);
}
Comment on lines 67 to 72

bool D3D11Output::SetTexture(void *texture) {
Expand All @@ -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<std::mutex> 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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -140,4 +171,18 @@ void D3D11Output::SetFPS() {
}
}

bool D3D11OutputSetTexture(void *output, void *texture) {
std::shared_lock<std::shared_mutex> lock(g_live_mutex);
if (g_live_outputs.find(output) == g_live_outputs.end())
return false;
return static_cast<D3D11Output *>(output)->SetTexture(texture);
Comment on lines +174 to +178
}

uint64_t D3D11OutputConsumed(void *output) {
std::shared_lock<std::shared_mutex> lock(g_live_mutex);
if (g_live_outputs.find(output) == g_live_outputs.end())
return 0;
return static_cast<D3D11Output *>(output)->Consumed();
}

} // namespace flutter_gpu_texture_renderer
11 changes: 10 additions & 1 deletion windows/d3d11_output.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define FLUTTER_PLUGIN_D3D11_OUTPUT_PLUGIN_H_

#include <atomic>
#include <chrono>
#include <d3d11.h>
#include <dxgi.h>
#include <flutter/method_channel.h>
Expand All @@ -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;
Expand All @@ -45,12 +47,19 @@ class D3D11Output {
std::atomic_char16_t this_fps_ = 0;
std::atomic<std::chrono::steady_clock::time_point> fps_time_point_ =
std::chrono::steady_clock::now();
std::atomic<uint64_t> consumed_ = 0;
bool unusable_ = false;
bool desc_ready_ = false;
size_t fail_counter_ = 0;
bool rendering_ = false;
std::atomic<bool> 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_
19 changes: 13 additions & 6 deletions windows/flutter_gpu_texture_renderer_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,19 @@ void FlutterGpuTextureRendererPlugin::HandleMethodCall(
} else if (method_call.method_name().compare("unregisterTexture") == 0) {
auto args = std::get<flutter::EncodableMap>(*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<D3D11Output> &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<D3D11Output> &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<D3D11Output> 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<flutter::EncodableMap>(*method_call.arguments());
Expand Down
13 changes: 9 additions & 4 deletions windows/flutter_gpu_texture_renderer_plugin_c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ void FlutterGpuTextureRendererPluginCApiRegisterWithRegistrar(
->GetRegistrar<flutter::PluginRegistrarWindows>(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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define FLUTTER_PLUGIN_FLUTTER_GPU_TEXTURE_RENDERER_PLUGIN_C_API_H_

#include <flutter_plugin_registrar.h>
#include <stdint.h>

#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
Expand All @@ -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
Expand Down