diff --git a/media_kit_video/windows/CMakeLists.txt b/media_kit_video/windows/CMakeLists.txt index 0ddebd897..e813506be 100644 --- a/media_kit_video/windows/CMakeLists.txt +++ b/media_kit_video/windows/CMakeLists.txt @@ -27,6 +27,7 @@ if(MEDIA_KIT_LIBS_AVAILABLE) add_library( ${PLUGIN_NAME} SHARED "d3d11_renderer.cc" + "mailbox_swap_chain.cc" "media_kit_video_plugin_c_api.cc" "media_kit_video_plugin.cc" "video_output_manager.cc" @@ -46,6 +47,8 @@ if(MEDIA_KIT_LIBS_AVAILABLE) FLUTTER_PLUGIN_IMPL ) + target_compile_options(${PLUGIN_NAME} PRIVATE "/utf-8") + target_include_directories( ${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include" diff --git a/media_kit_video/windows/d3d11_renderer.cc b/media_kit_video/windows/d3d11_renderer.cc index 409c932da..94e05a473 100644 --- a/media_kit_video/windows/d3d11_renderer.cc +++ b/media_kit_video/windows/d3d11_renderer.cc @@ -1,7 +1,7 @@ -// This file is a part of media_kit +// This file is a part of media_kit // (https://github.com/media-kit/media-kit). // -// Copyright © 2025 & onwards, Predidit. +// Copyright © 2026 Predidit. // All rights reserved. // Use of this source code is governed by MIT license that can be found in the // LICENSE file. @@ -13,115 +13,62 @@ #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") -#define FAIL(message) \ - std::cout << "media_kit: D3D11Renderer: Failure: " << message \ - << std::endl; \ - return false - -#define CHECK_HRESULT(message) \ - if (FAILED(hr)) { \ - FAIL(message); \ - } - int D3D11Renderer::instance_count_ = 0; -D3D11Renderer::D3D11Renderer(int32_t width, int32_t height) +D3D11Renderer::D3D11Renderer(int32_t width, int32_t height, + IDXGIAdapter* flutter_adapter) : width_(width), height_(height) { - mutex_ = ::CreateMutex(NULL, FALSE, NULL); - if (!CreateD3D11Device()) { + if (!CreateD3D11Device(flutter_adapter)) { throw std::runtime_error("Unable to create Direct3D 11 device."); } - if (!CreateTexture()) { - throw std::runtime_error("Unable to create Direct3D 11 texture."); + if (!CreateMailbox()) { + throw std::runtime_error("Unable to create mailbox swap chain."); } instance_count_++; } D3D11Renderer::~D3D11Renderer() { - CleanUp(true); - ::ReleaseMutex(mutex_); - ::CloseHandle(mutex_); + mailbox_swap_chain_.Reset(); + d3d_11_device_context_.Reset(); + d3d_11_device_.Reset(); instance_count_--; } void D3D11Renderer::SetSize(int32_t width, int32_t height) { - if (width == width_ && height == height_) { - return; - } + if (width == width_ && height == height_) return; width_ = width; height_ = height; - - // Release the old texture reference - if (shared_texture_) { - shared_texture_->Release(); - shared_texture_ = nullptr; - } - - // Resize the swap chain (this will resize the back buffer) - if (swap_chain_) { - auto hr = swap_chain_->ResizeBuffers(1, width_, height_, - DXGI_FORMAT_B8G8R8A8_UNORM, 0); + if (mailbox_swap_chain_) { + const HRESULT hr = mailbox_swap_chain_->Resize(width_, height_); if (FAILED(hr)) { - std::cout << "media_kit: D3D11Renderer: Failed to resize swap chain" - << std::endl; - return; + std::cout << "media_kit: D3D11Renderer: Mailbox resize failed (hr=0x" + << std::hex << hr << std::dec << ")" << std::endl; } } - - // Recreate the shared texture with the new size - CreateTexture(); } -void D3D11Renderer::CopyTexture() { - ::WaitForSingleObject(mutex_, INFINITE); - - // With native DXGI rendering, mpv renders directly to the swap chain's back buffer. - // We need to copy the back buffer to our shared texture for Flutter. - if (d3d_11_device_context_ != nullptr && swap_chain_ != nullptr && shared_texture_) { - // Get the back buffer from the swap chain - Microsoft::WRL::ComPtr back_buffer; - auto hr = swap_chain_->GetBuffer(0, __uuidof(ID3D11Texture2D), - (void**)&back_buffer); - if (SUCCEEDED(hr) && back_buffer) { - // Copy from back buffer to shared texture - d3d_11_device_context_->CopyResource(shared_texture_.Get(), back_buffer.Get()); - d3d_11_device_context_->Flush(); - } +void D3D11Renderer::ProducerCommit() { + if (mailbox_swap_chain_) { + mailbox_swap_chain_->ProducerCommit(); } - - ::ReleaseMutex(mutex_); } -void D3D11Renderer::CleanUp(bool release_device) { - // Release texture - if (shared_texture_) { - shared_texture_->Release(); - shared_texture_ = nullptr; +HANDLE D3D11Renderer::ConsumerAcquire() { + if (mailbox_swap_chain_) { + return mailbox_swap_chain_->ConsumerAcquire(); } + return nullptr; +} - // Release swap chain - if (swap_chain_) { - swap_chain_->Release(); - swap_chain_ = nullptr; - } - - // Release device and context if the instance is being destroyed - if (release_device) { - if (d3d_11_device_context_) { - d3d_11_device_context_->Release(); - d3d_11_device_context_ = nullptr; - } - if (d3d_11_device_) { - d3d_11_device_->Release(); - d3d_11_device_ = nullptr; - } +HANDLE D3D11Renderer::ReadHandleSnapshot() const { + if (mailbox_swap_chain_) { + return mailbox_swap_chain_->ReadHandleSnapshot(); } + return nullptr; } -bool D3D11Renderer::CreateD3D11Device() { - if (d3d_11_device_ != nullptr) { - return true; // Already created - } +bool D3D11Renderer::CreateD3D11Device(IDXGIAdapter* flutter_adapter) { + if (d3d_11_device_) return true; const D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_1, @@ -131,90 +78,62 @@ bool D3D11Renderer::CreateD3D11Device() { D3D_FEATURE_LEVEL_9_3, }; - IDXGIAdapter* adapter = nullptr; + Microsoft::WRL::ComPtr adapter(flutter_adapter); D3D_DRIVER_TYPE driver_type = D3D_DRIVER_TYPE_UNKNOWN; - // Automatically selecting adapter on Windows 10 RTM or greater - if (Utils::IsWindows10RTMOrGreater()) { - adapter = nullptr; - driver_type = D3D_DRIVER_TYPE_HARDWARE; - } else { - IDXGIFactory* dxgi = nullptr; - ::CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgi); - if (dxgi) { - dxgi->EnumAdapters(0, &adapter); - dxgi->Release(); + if (!adapter) { + if (Utils::IsWindows10RTMOrGreater()) { + driver_type = D3D_DRIVER_TYPE_HARDWARE; + } else { + Microsoft::WRL::ComPtr dxgi; + ::CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgi); + if (dxgi) { + dxgi->EnumAdapters(0, &adapter); + } } + } else { + std::cout << "media_kit: D3D11Renderer: Using Flutter's DXGI adapter." + << std::endl; } - // Create swap chain descriptor for offscreen rendering - DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; - swap_chain_desc.BufferDesc.Width = width_; - swap_chain_desc.BufferDesc.Height = height_; - swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - swap_chain_desc.BufferDesc.RefreshRate.Numerator = 0; - swap_chain_desc.BufferDesc.RefreshRate.Denominator = 1; - swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.SampleDesc.Quality = 0; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.BufferCount = 1; - // Use desktop window for offscreen rendering - swap_chain_desc.OutputWindow = ::GetDesktopWindow(); - swap_chain_desc.Windowed = TRUE; - swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - - auto hr = ::D3D11CreateDeviceAndSwapChain( - adapter, driver_type, 0, 0, feature_levels, - sizeof(feature_levels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, - &swap_chain_desc, &swap_chain_, - &d3d_11_device_, nullptr, &d3d_11_device_context_); - - CHECK_HRESULT("D3D11CreateDeviceAndSwapChain"); - - Microsoft::WRL::ComPtr dxgi_device = nullptr; - auto dxgi_device_success = d3d_11_device_->QueryInterface( - __uuidof(IDXGIDevice), (void**)&dxgi_device); - if (SUCCEEDED(dxgi_device_success) && dxgi_device != nullptr) { - dxgi_device->SetGPUThreadPriority(5); // Must be in interval [-7, 7] + const HRESULT hr = ::D3D11CreateDevice( + adapter.Get(), driver_type, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + feature_levels, static_cast(std::size(feature_levels)), + D3D11_SDK_VERSION, d3d_11_device_.GetAddressOf(), nullptr, + d3d_11_device_context_.GetAddressOf()); + + if (FAILED(hr)) { + std::cout << "media_kit: D3D11Renderer: D3D11CreateDevice failed (hr=0x" + << std::hex << hr << std::dec << ")" << std::endl; + return false; } - auto level = d3d_11_device_->GetFeatureLevel(); + Microsoft::WRL::ComPtr dxgi_device; + if (SUCCEEDED(d3d_11_device_->QueryInterface(__uuidof(IDXGIDevice), + (void**)&dxgi_device)) && + dxgi_device) { + dxgi_device->SetGPUThreadPriority(5); + } + + const auto level = d3d_11_device_->GetFeatureLevel(); std::cout << "media_kit: D3D11Renderer: Direct3D Feature Level: " - << (((unsigned)level) >> 12) << "_" - << ((((unsigned)level) >> 8) & 0xf) << std::endl; + << (static_cast(level) >> 12) << "_" + << ((static_cast(level) >> 8) & 0xfu) << std::endl; return true; } -bool D3D11Renderer::CreateTexture() { - // Create a separate shared texture for Flutter - // (The swap chain back buffer cannot be shared directly with Flutter) - D3D11_TEXTURE2D_DESC texture_desc = {0}; - texture_desc.Width = width_; - texture_desc.Height = height_; - texture_desc.MipLevels = 1; - texture_desc.ArraySize = 1; - texture_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - texture_desc.SampleDesc.Count = 1; - texture_desc.SampleDesc.Quality = 0; - texture_desc.Usage = D3D11_USAGE_DEFAULT; - texture_desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - texture_desc.CPUAccessFlags = 0; - texture_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; - - auto hr = d3d_11_device_->CreateTexture2D(&texture_desc, nullptr, - &shared_texture_); - CHECK_HRESULT("ID3D11Device::CreateTexture2D"); - - Microsoft::WRL::ComPtr resource; - hr = shared_texture_.As(&resource); - CHECK_HRESULT("ID3D11Texture2D::As"); - - // Retrieve the shared HANDLE for interop with Flutter - hr = resource->GetSharedHandle(&handle_); - CHECK_HRESULT("IDXGIResource::GetSharedHandle"); - - shared_texture_->AddRef(); - +bool D3D11Renderer::CreateMailbox() { + MailboxSwapChain* raw = nullptr; + const HRESULT hr = + MailboxSwapChain::Create(d3d_11_device_.Get(), width_, height_, &raw); + if (FAILED(hr)) { + std::cout << "media_kit: D3D11Renderer: MailboxSwapChain::Create failed " + "(hr=0x" + << std::hex << hr << std::dec << ")" << std::endl; + return false; + } + mailbox_swap_chain_.Attach(raw); return true; -} +} \ No newline at end of file diff --git a/media_kit_video/windows/d3d11_renderer.h b/media_kit_video/windows/d3d11_renderer.h index caa165f01..88b628d49 100644 --- a/media_kit_video/windows/d3d11_renderer.h +++ b/media_kit_video/windows/d3d11_renderer.h @@ -1,7 +1,7 @@ -// This file is a part of media_kit +// This file is a part of media_kit // (https://github.com/media-kit/media-kit). // -// Copyright © 2025 & onwards, Predidit. +// Copyright © Predidit. // All rights reserved. // Use of this source code is governed by MIT license that can be found in the // LICENSE file. @@ -12,58 +12,76 @@ #include #include #include +#include #include #include -#include #include +#include "mailbox_swap_chain.h" #include "utils.h" -// |D3D11Renderer| provides an abstraction around Direct3D 11 for video -// rendering with libmpv's native DXGI support. -// This replaces the previous ANGLE-based implementation with a simpler, -// more efficient approach using mpv's built-in D3D11 renderer. - +// D3D11Renderer creates a D3D11 device and owns a MailboxSwapChain that +// implements the lock-free triple-buffer mailbox between the libmpv rendering +// thread (producer) and Flutter's render thread (consumer). +// +// The MailboxSwapChain is passed directly to mpv as the IDXGISwapChain* in +// mpv_dxgi_init_params. mpv calls GetBuffer(0, ...) to obtain a render +// target, renders into it, and flushes. The plugin then calls +// ProducerCommit() to atomically publish the frame. Flutter's +// GpuSurfaceTexture callback calls ConsumerAcquire() to receive the DXGI +// shared HANDLE of the newest complete frame — with no copy and no OS lock. class D3D11Renderer { public: - const int32_t width() const { return width_; } - const int32_t height() const { return height_; } - const HANDLE handle() const { return handle_; } - ID3D11Device* device() const { return d3d_11_device_; } - IDXGISwapChain* swap_chain() const { return swap_chain_; } - - D3D11Renderer(int32_t width, int32_t height); - + int32_t width() const { return width_; } + int32_t height() const { return height_; } + + // Raw device pointer used by VideoOutput to populate mpv_dxgi_init_params. + ID3D11Device* device() const { return d3d_11_device_.Get(); } + + // IDXGISwapChain* facade backed by MailboxSwapChain. + // Passed to mpv as mpv_dxgi_init_params::swapchain (void*). + IDXGISwapChain* swap_chain() const { return mailbox_swap_chain_.Get(); } + + // |flutter_adapter| is the IDXGIAdapter* returned by + // FlutterDesktopViewGetGraphicsAdapter. When non-null the D3D11 device is + // created on exactly that adapter so the plugin always shares the same GPU + // as the Flutter compositor. Pass nullptr to fall back to the legacy + // heuristic (hardware default on Win10+, adapter 0 on older Windows). + explicit D3D11Renderer(int32_t width, int32_t height, + IDXGIAdapter* flutter_adapter = nullptr); ~D3D11Renderer(); + // Recreates the three mailbox slots at the new dimensions. + // Must be called from the producer thread only. void SetSize(int32_t width, int32_t height); - void CopyTexture(); + // Called from the producer thread (mpv thread pool) after + // mpv_render_context_render returns. Publishes the rendered frame. + void ProducerCommit(); - private: - bool CreateD3D11Device(); + // Called from the consumer thread (Flutter GpuSurfaceTexture callback). + // Returns the DXGI shared HANDLE of the most recent complete frame. + HANDLE ConsumerAcquire(); - bool CreateTexture(); + // Returns the DXGI shared HANDLE for the current read slot without + // advancing mailbox state. Used once during texture registration before + // the consumer thread starts. + HANDLE ReadHandleSnapshot() const; - void CleanUp(bool release_device); + private: + bool CreateD3D11Device(IDXGIAdapter* flutter_adapter); + bool CreateMailbox(); int32_t width_ = 1; int32_t height_ = 1; - HANDLE handle_ = nullptr; - - // Sync operations. - HANDLE mutex_ = nullptr; - // D3D 11 - ID3D11Device* d3d_11_device_ = nullptr; - ID3D11DeviceContext* d3d_11_device_context_ = nullptr; - IDXGISwapChain* swap_chain_ = nullptr; + Microsoft::WRL::ComPtr d3d_11_device_; + Microsoft::WRL::ComPtr d3d_11_device_context_; - // Shared texture for Flutter rendering (created from swap chain back buffer) - Microsoft::WRL::ComPtr shared_texture_; + Microsoft::WRL::ComPtr mailbox_swap_chain_; static int instance_count_; }; -#endif +#endif // D3D11_RENDERER_H_ \ No newline at end of file diff --git a/media_kit_video/windows/mailbox_swap_chain.cc b/media_kit_video/windows/mailbox_swap_chain.cc new file mode 100644 index 000000000..39c6098d7 --- /dev/null +++ b/media_kit_video/windows/mailbox_swap_chain.cc @@ -0,0 +1,250 @@ +// This file is a part of media_kit +// (https://github.com/media-kit/media-kit). +// +// Copyright © 2026 Predidit. +// All rights reserved. +// Use of this source code is governed by MIT license that can be found in the +// LICENSE file. + +#include "mailbox_swap_chain.h" + +#include + +MailboxSwapChain::~MailboxSwapChain() { + ReleaseSlots(); +} + +HRESULT MailboxSwapChain::Create(ID3D11Device* device, + int32_t width, + int32_t height, + MailboxSwapChain** out) { + if (!device || !out) return E_INVALIDARG; + + auto* p = new (std::nothrow) MailboxSwapChain(); + if (!p) return E_OUTOFMEMORY; + + p->device_ = device; + p->width_ = (width > 0) ? width : 1; + p->height_ = (height > 0) ? height : 1; + + { + Microsoft::WRL::ComPtr ctx; + device->GetImmediateContext(&ctx); + const HRESULT hr2 = ctx.As(&p->context4_); + if (FAILED(hr2)) { + std::cout << "media_kit: MailboxSwapChain: ID3D11DeviceContext4 not available " + "(hr=0x" << std::hex << hr2 << std::dec << ")" << std::endl; + delete p; + return hr2; + } + } + + const HRESULT hr = p->AllocateSlots(); + if (FAILED(hr)) { + delete p; + return hr; + } + + *out = p; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MailboxSwapChain::QueryInterface(REFIID riid, + void** ppv) { + if (!ppv) return E_POINTER; + + if (riid == __uuidof(IUnknown) || riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIDeviceSubObject) || + riid == __uuidof(IDXGISwapChain)) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + + *ppv = nullptr; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE MailboxSwapChain::AddRef() { + return ref_count_.fetch_add(1u, std::memory_order_relaxed) + 1u; +} + +ULONG STDMETHODCALLTYPE MailboxSwapChain::Release() { + const ULONG prev = ref_count_.fetch_sub(1u, std::memory_order_acq_rel); + if (prev == 1u) delete this; + return prev - 1u; +} + +HRESULT STDMETHODCALLTYPE MailboxSwapChain::GetBuffer(UINT Buffer, + REFIID riid, + void** ppSurface) { + if (!ppSurface) return E_POINTER; + if (Buffer != 0) return DXGI_ERROR_INVALID_CALL; + + if (riid != __uuidof(ID3D11Texture2D) && + riid != __uuidof(ID3D11Resource)) { + return E_NOINTERFACE; + } + + ID3D11Texture2D* tex = slots_[write_slot_].texture.Get(); + if (!tex) return E_FAIL; + + tex->AddRef(); + *ppSurface = tex; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +MailboxSwapChain::GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) { + if (!pDesc) return E_POINTER; + *pDesc = {}; + pDesc->BufferDesc.Width = static_cast(width_); + pDesc->BufferDesc.Height = static_cast(height_); + pDesc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + pDesc->BufferCount = 1; + pDesc->SampleDesc.Count = 1; + pDesc->BufferUsage = + DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; + pDesc->Windowed = TRUE; + pDesc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + return S_OK; +} + +void MailboxSwapChain::ProducerCommit() { + { + auto& ws = slots_[write_slot_]; + context4_->Signal(ws.fence.Get(), ++ws.fence_value); + } + + const uint32_t desired = static_cast(write_slot_) | 0x4u; + uint32_t expected = mailbox_state_.load(std::memory_order_relaxed); + while (!mailbox_state_.compare_exchange_weak( + expected, desired, std::memory_order_release, + std::memory_order_relaxed)) {} + write_slot_ = static_cast(expected & 0x3u); +} + +HANDLE MailboxSwapChain::ConsumerAcquire() { + uint32_t expected = mailbox_state_.load(std::memory_order_acquire); + if (!(expected & 0x4u)) { + // No new frame — we already waited for this slot last time we acquired it. + return slots_[read_slot_].shared_handle; + } + const uint32_t desired = static_cast(read_slot_); // dirty=0 + while (!mailbox_state_.compare_exchange_weak( + expected, desired, std::memory_order_acq_rel, + std::memory_order_relaxed)) { + if (!(expected & 0x4u)) + return slots_[read_slot_].shared_handle; + } + read_slot_ = static_cast(expected & 0x3u); + + auto& rs = slots_[read_slot_]; + if (rs.fence->GetCompletedValue() < rs.fence_value) { + if (SUCCEEDED(rs.fence->SetEventOnCompletion(rs.fence_value, + rs.fence_event))) { + ::WaitForSingleObject(rs.fence_event, INFINITE); + } + } + return rs.shared_handle; +} + +HRESULT MailboxSwapChain::Resize(int32_t width, int32_t height) { + ReleaseSlots(); + width_ = (width > 0) ? width : 1; + height_ = (height > 0) ? height : 1; + mailbox_state_.store(2u, std::memory_order_relaxed); + write_slot_ = 0; + read_slot_ = 1; + return AllocateSlots(); +} + +HRESULT MailboxSwapChain::AllocateSlots() { + Microsoft::WRL::ComPtr device5; + { + const HRESULT hr = + device_->QueryInterface(__uuidof(ID3D11Device5), (void**)&device5); + if (FAILED(hr)) { + std::cout << "media_kit: MailboxSwapChain: ID3D11Device5 not available " + "(hr=0x" << std::hex << hr << std::dec << ")" << std::endl; + return hr; + } + } + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = static_cast(width_); + desc.Height = static_cast(height_); + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + + for (int i = 0; i < 3; ++i) { + HRESULT hr = device_->CreateTexture2D(&desc, nullptr, &slots_[i].texture); + if (FAILED(hr)) { + std::cout << "media_kit: MailboxSwapChain: CreateTexture2D slot " << i + << " failed (hr=0x" << std::hex << hr << std::dec << ")" + << std::endl; + return hr; + } + + Microsoft::WRL::ComPtr resource; + hr = slots_[i].texture.As(&resource); + if (FAILED(hr)) { + std::cout << "media_kit: MailboxSwapChain: As slot " << i + << " failed (hr=0x" << std::hex << hr << std::dec << ")" + << std::endl; + return hr; + } + + hr = resource->GetSharedHandle(&slots_[i].shared_handle); + if (FAILED(hr)) { + std::cout << "media_kit: MailboxSwapChain: GetSharedHandle slot " << i + << " failed (hr=0x" << std::hex << hr << std::dec << ")" + << std::endl; + return hr; + } + + hr = device5->CreateFence(0, D3D11_FENCE_FLAG_NONE, + __uuidof(ID3D11Fence), + (void**)&slots_[i].fence); + if (FAILED(hr)) { + std::cout << "media_kit: MailboxSwapChain: CreateFence slot " << i + << " failed (hr=0x" << std::hex << hr << std::dec << ")" + << std::endl; + return hr; + } + slots_[i].fence_value = 0; + + slots_[i].fence_event = + ::CreateEventW(nullptr, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, + nullptr); + if (!slots_[i].fence_event) { + const HRESULT hrE = HRESULT_FROM_WIN32(::GetLastError()); + std::cout << "media_kit: MailboxSwapChain: CreateEvent slot " << i + << " failed (hr=0x" << std::hex << hrE << std::dec << ")" + << std::endl; + return hrE; + } + } + + return S_OK; +} + +void MailboxSwapChain::ReleaseSlots() { + for (auto& slot : slots_) { + slot.texture.Reset(); + slot.shared_handle = nullptr; + slot.fence.Reset(); + slot.fence_value = 0; + if (slot.fence_event) { + ::CloseHandle(slot.fence_event); + slot.fence_event = nullptr; + } + } +} \ No newline at end of file diff --git a/media_kit_video/windows/mailbox_swap_chain.h b/media_kit_video/windows/mailbox_swap_chain.h new file mode 100644 index 000000000..5c1eaacd9 --- /dev/null +++ b/media_kit_video/windows/mailbox_swap_chain.h @@ -0,0 +1,151 @@ +// This file is a part of media_kit +// (https://github.com/media-kit/media-kit). +// +// Copyright © 2026 Predidit. +// All rights reserved. +// Use of this source code is governed by MIT license that can be found in the +// LICENSE file. + +#ifndef MAILBOX_SWAP_CHAIN_H_ +#define MAILBOX_SWAP_CHAIN_H_ + +#include +#include +#include +#include +#include + +#include +#include + +// Minimal IDXGISwapChain facade backed by a lock-free triple-buffer mailbox. +// +// Three BGRA8 textures are kept, each with a DXGI shared HANDLE. +// mailbox_state_ is a single atomic: +// bits [1:0] slot index in the mailbox (0-2) +// bit [2] dirty flag: 1 = producer has committed a new frame +// +// {write_slot_, mailbox slot, read_slot_} is always a permutation of {0,1,2}. +class MailboxSwapChain final : public IDXGISwapChain { + public: + // Returns an AddRef'd pointer (ref count = 1). device must outlive this. + static HRESULT Create(ID3D11Device* device, + int32_t width, + int32_t height, + MailboxSwapChain** out); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void** ppv) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; + + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID, UINT, + const void*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID, + const IUnknown*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID, UINT*, void*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetParent(REFIID, void**) override { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE GetDevice(REFIID, void**) override { + return E_NOTIMPL; + } + + // Present is a no-op: mpv drives presentation via done_frame / Flush. + HRESULT STDMETHODCALLTYPE Present(UINT, UINT) override { return S_OK; } + + // Called by the libmpv DXGI back-end each frame to obtain a render target. + HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, + REFIID riid, + void** ppSurface) override; + + HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) override; + + HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL, + IDXGIOutput*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL*, + IDXGIOutput**) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT, UINT, UINT, DXGI_FORMAT, + UINT) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput**) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetFrameStatistics( + DXGI_FRAME_STATISTICS*) override { + return E_NOTIMPL; + } + HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT*) override { + return E_NOTIMPL; + } + + // Called from the producer thread after mpv_render_context_render returns. + void ProducerCommit(); + + // Called from the consumer thread (Flutter GpuSurfaceTexture callback). + // Returns the DXGI shared HANDLE of the most recent complete frame. + HANDLE ConsumerAcquire(); + + // Recreates all three texture slots at the new dimensions. + // Must only be called from the producer thread with no active consumer. + HRESULT Resize(int32_t width, int32_t height); + + // Returns the current read-slot HANDLE without advancing mailbox state. + HANDLE ReadHandleSnapshot() const { + return slots_[read_slot_].shared_handle; + } + + int32_t width() const { return width_; } + int32_t height() const { return height_; } + + private: + MailboxSwapChain() = default; + ~MailboxSwapChain(); + + MailboxSwapChain(const MailboxSwapChain&) = delete; + MailboxSwapChain& operator=(const MailboxSwapChain&) = delete; + + HRESULT AllocateSlots(); + void ReleaseSlots(); + + struct TextureSlot { + Microsoft::WRL::ComPtr texture; + HANDLE shared_handle = nullptr; + Microsoft::WRL::ComPtr fence; + HANDLE fence_event = nullptr; + uint64_t fence_value = 0; + }; + + ID3D11Device* device_ = nullptr; + Microsoft::WRL::ComPtr context4_; + + int32_t width_ = 1; + int32_t height_ = 1; + + TextureSlot slots_[3]; + + // Lock-free mailbox: bits [1:0] = slot index (0-2), bit [2] = dirty; init = 2u. + std::atomic mailbox_state_{2u}; + + int write_slot_ = 0; // producer-private + int read_slot_ = 1; // consumer-private + + std::atomic ref_count_{1u}; +}; + +#endif // MAILBOX_SWAP_CHAIN_H_ \ No newline at end of file diff --git a/media_kit_video/windows/video_output.cc b/media_kit_video/windows/video_output.cc index dcb7f3ed9..837971f35 100644 --- a/media_kit_video/windows/video_output.cc +++ b/media_kit_video/windows/video_output.cc @@ -37,10 +37,14 @@ VideoOutput::VideoOutput(int64_t handle, if (configuration.enable_hardware_acceleration) { try { - // Create D3D11 renderer with swap chain. + IDXGIAdapter* flutter_adapter = nullptr; + if (auto* view = registrar_->GetView()) { + flutter_adapter = view->GetGraphicsAdapter(); + } d3d11_renderer_ = std::make_unique( static_cast(width_.value_or(1)), - static_cast(height_.value_or(1))); + static_cast(height_.value_or(1)), + flutter_adapter); // Initialize mpv with the D3D11 device and swap chain mpv_dxgi_init_params init_params = { @@ -162,7 +166,9 @@ void VideoOutput::Render() { if (d3d11_renderer_ != nullptr) { mpv_render_context_render(render_context_, nullptr); mpv_render_context_report_swap(render_context_); - d3d11_renderer_->CopyTexture(); + // Atomically publish the rendered slot to the mailbox so that Flutter's + // GpuSurfaceTexture callback can import it without a copy. + d3d11_renderer_->ProducerCommit(); } // S/W if (pixel_buffer_ != nullptr) { @@ -293,22 +299,29 @@ void VideoOutput::Resize(int64_t required_width, int64_t required_height) { auto texture = std::make_unique(); texture->struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor); - texture->handle = d3d11_renderer_->handle(); + // Seed with the current read-slot handle so Flutter has a valid surface + // even before the first mpv frame is committed. + texture->handle = d3d11_renderer_->ReadHandleSnapshot(); texture->width = texture->visible_width = d3d11_renderer_->width(); texture->height = texture->visible_height = d3d11_renderer_->height(); texture->release_context = nullptr; texture->release_callback = [](void*) {}; texture->format = kFlutterDesktopPixelFormatBGRA8888; - + auto texture_variant = std::make_unique(flutter::GpuSurfaceTexture( kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, [&](auto, auto) { std::lock_guard lock(textures_mutex_); if (texture_id_) { - return textures_.at(texture_id_).get(); - } else { - return (FlutterDesktopGpuSurfaceDescriptor*)nullptr; + auto* desc = textures_.at(texture_id_).get(); + // ConsumerAcquire() is lock-free. texture_id_ != 0 implies + // d3d11_renderer_ is valid: UnregisterTexture guarantees that + // Flutter stops invoking this callback before the destructor + // resets d3d11_renderer_. + desc->handle = d3d11_renderer_->ConsumerAcquire(); + return desc; } + return (FlutterDesktopGpuSurfaceDescriptor*)nullptr; })); // Register new texture. texture_id_ =