diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props
index 6354c5c82b..000ecdaab2 100644
--- a/sources/Directory.Packages.props
+++ b/sources/Directory.Packages.props
@@ -14,6 +14,7 @@
+
@@ -22,7 +23,6 @@
-
diff --git a/sources/engine/Stride.Video/NativeMethods.json b/sources/engine/Stride.Video/NativeMethods.json
new file mode 100644
index 0000000000..340b621dd9
--- /dev/null
+++ b/sources/engine/Stride.Video/NativeMethods.json
@@ -0,0 +1,5 @@
+{
+ "$schema": "https://aka.ms/CsWin32.schema.json",
+ "allowMarshaling": false,
+ "comInterop": { "preserveSigMethods": [ "IMFMediaEngine.OnVideoStreamTick" ] }
+}
diff --git a/sources/engine/Stride.Video/NativeMethods.txt b/sources/engine/Stride.Video/NativeMethods.txt
new file mode 100644
index 0000000000..7dbe375ec0
--- /dev/null
+++ b/sources/engine/Stride.Video/NativeMethods.txt
@@ -0,0 +1,21 @@
+MFStartup
+MFShutdown
+CLSID_MFMediaEngineClassFactory
+
+CoCreateInstance
+IMFMediaEngine
+IMFMediaEngineEx
+IMFMediaEngineClassFactory
+IMFMediaEngineNotify
+MFCreateAttributes
+MFCreateDXGIDeviceManager
+MF_MEDIA_ENGINE_CALLBACK
+MF_MEDIA_ENGINE_EVENT
+MF_MEDIA_ENGINE_ERR
+MF_MEDIA_ENGINE_DXGI_MANAGER
+MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT
+MF_VERSION
+
+IStream
+STATFLAG
+MFCreateMFByteStreamOnStream
diff --git a/sources/engine/Stride.Video/Stride.Video.csproj b/sources/engine/Stride.Video/Stride.Video.csproj
index af0b1840c7..15b281ce88 100644
--- a/sources/engine/Stride.Video/Stride.Video.csproj
+++ b/sources/engine/Stride.Video/Stride.Video.csproj
@@ -1,4 +1,4 @@
-
+
true
true
@@ -24,7 +24,10 @@
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/sources/engine/Stride.Video/VideoInstance.Direct3D.cs b/sources/engine/Stride.Video/VideoInstance.Direct3D.cs
index 1949f71bef..fbbc059926 100644
--- a/sources/engine/Stride.Video/VideoInstance.Direct3D.cs
+++ b/sources/engine/Stride.Video/VideoInstance.Direct3D.cs
@@ -2,12 +2,14 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
#if STRIDE_GRAPHICS_API_DIRECT3D11
+#pragma warning disable CA1416 // Validate platform compatibility (no need, we check for Windows already at a higher level)
using System;
+using System.Collections;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
-using SharpDX.MediaFoundation;
+using System.Runtime.InteropServices;
using Silk.NET.Core.Native;
using Silk.NET.DXGI;
using Silk.NET.Direct3D11;
@@ -15,48 +17,45 @@
using Stride.Core.Serialization;
using Stride.Graphics;
using Stride.Media;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Media.MediaFoundation;
+using Windows.Win32.System.Com;
+using static Windows.Win32.PInvoke;
+using IUnknown = Windows.Win32.System.Com.IUnknown;
namespace Stride.Video
{
unsafe partial class VideoInstance
{
- private MediaEngine mediaEngine;
+ private IMFMediaEngine* mediaEngine;
private Texture videoOutputTexture;
private IDXGISurface* videoOutputSurface;
- private SharpDX.ComObject videoOutputSurfaceSharpDX; // TODO: Remove when Silk includes Media Foundation
private Stream videoFileStream;
- private ByteStream videoDataStream;
private int videoWidth;
private int videoHeight;
private bool reachedEOF;
- private static MediaEngineClassFactory mediaEngineFactory = new();
-
partial void ReleaseMediaImpl()
{
- mediaEngine?.Shutdown();
- mediaEngine?.Dispose();
+ if (mediaEngine != null)
+ {
+ mediaEngine->Shutdown();
+ mediaEngine->Release();
+ }
mediaEngine = null;
- // Randomly crashes in sharpDX and the native code when disabling this
- // The stream is problably accessed after disposal due to communication latency
- // Unfortunately we don't receive any events after the call to Shutdown where we could dispose those
-
- //videoDataStream?.Dispose();
- //videoDataStream = null;
- //videoFileStream?.Dispose();
- //videoFileStream = null;
+ videoFileStream?.Dispose();
+ videoFileStream = null;
if (videoOutputSurface != null)
videoOutputSurface->Release();
videoOutputSurface = null;
- videoOutputSurfaceSharpDX = null; // TODO: Remove when Silk includes Media Foundation
- // NOTE: Do not Dispose() it. It just wraps around the IDXGISurface
videoOutputTexture?.Dispose();
videoOutputTexture = null;
@@ -67,29 +66,29 @@ partial void PlayImpl()
if (playRange.Start > CurrentTime)
Seek(playRange.Start);
- mediaEngine.Play();
+ mediaEngine->Play();
}
partial void PauseImpl()
{
- mediaEngine.Pause();
+ mediaEngine->Pause();
}
partial void StopImpl()
{
- mediaEngine.Pause();
+ mediaEngine->Pause();
Seek(playRange.Start);
}
partial void SeekImpl(TimeSpan time)
{
- mediaEngine.CurrentTime = time.TotalSeconds;
+ mediaEngine->SetCurrentTime(time.TotalSeconds);
reachedEOF = false;
}
partial void ChangePlaySpeedImpl()
{
- mediaEngine.PlaybackRate = SpeedFactor;
+ mediaEngine->SetPlaybackRate(SpeedFactor);
}
partial void UpdatePlayRangeImpl()
@@ -100,7 +99,7 @@ partial void UpdatePlayRangeImpl()
partial void UpdateAudioVolumeImpl(float volume)
{
- mediaEngine.Volume = volume;
+ mediaEngine->SetVolume(volume);
}
partial void UpdateImpl(ref TimeSpan elapsed)
@@ -109,7 +108,7 @@ partial void UpdateImpl(ref TimeSpan elapsed)
return;
// Transfer frame if a new one is available
- if (mediaEngine.OnVideoStreamTick(out var presentationTimeTicks))
+ if (mediaEngine->OnVideoStreamTick(out var presentationTimeTicks).Succeeded)
{
CurrentTime = TimeSpan.FromTicks(presentationTimeTicks);
@@ -150,7 +149,7 @@ partial void UpdateImpl(ref TimeSpan elapsed)
// Now update the video texture with data of the new video frame
var graphicsContext = services.GetSafeServiceAs();
- mediaEngine.TransferVideoFrame(videoOutputSurfaceSharpDX, srcRef: null, new SharpDX.Mathematics.Interop.RawRectangle(0, 0, videoWidth, videoHeight), borderClrRef: null);
+ mediaEngine->TransferVideoFrame((IUnknown*)videoOutputSurface, pSrc: null, new RECT(0, 0, videoWidth, videoHeight), pBorderClr: null);
videoTexture.CopyDecoderOutputToTopLevelMipmap(graphicsContext, videoOutputTexture);
videoTexture.GenerateMipMaps(graphicsContext);
@@ -174,29 +173,40 @@ partial void InitializeMediaImpl(string url, long startPosition, long length, re
try
{
- //Assign our dxgi manager, and set format to bgra
- var attr = new MediaEngineAttributes
- {
- VideoOutputFormat = (int) Format.FormatB8G8R8A8Unorm,
- DxgiManager = videoSystem.DxgiDeviceManager
- };
+ CoCreateInstance(in CLSID_MFMediaEngineClassFactory, null, CLSCTX.CLSCTX_INPROC_SERVER, out IMFMediaEngineClassFactory* classFactory).ThrowOnFailure();
+ using var _1 = ComHelpers.AsPtr(classFactory);
- mediaEngine = new MediaEngine(mediaEngineFactory, attr);
+ //Assign our dxgi manager, and set format to bgra
+ IMFAttributes* attr;
+ MFCreateAttributes(&attr, 1).ThrowOnFailure();
+ using var _2 = ComHelpers.AsPtr(attr);
+ attr->SetUINT32(in MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, (uint)Format.FormatB8G8R8A8Unorm);
+ attr->SetUnknown(in MF_MEDIA_ENGINE_DXGI_MANAGER, (IUnknown*)videoSystem.DxgiDeviceManager);
// Register our PlayBackEvent
- mediaEngine.PlaybackEvent += OnPlaybackCallback;
+ using var notify = ComHelpers.CreateCCW(new Notify(this));
+ attr->SetUnknown(in MF_MEDIA_ENGINE_CALLBACK, notify);
+
+ IMFMediaEngine* engine;
+ classFactory->CreateInstance(default, attr, &engine);
+ mediaEngine = engine;
// set the video source
- using var mediaEngineEx = mediaEngine.QueryInterface();
+ mediaEngine->QueryInterface(out var mediaEngineEx).ThrowOnFailure();
+ using var _3 = ComHelpers.AsPtr(mediaEngineEx);
videoFileStream = new VirtualFileStream(File.OpenRead(url), startPosition, startPosition + length);
- videoDataStream = new ByteStream(videoFileStream);
+ using var videoDataStream = ComHelpers.CreateCCW(new ComStreamWrapper(videoFileStream));
+ IMFByteStream* byteStream;
+ MFCreateMFByteStreamOnStream(videoDataStream, &byteStream);
+ using var _4 = ComHelpers.AsPtr(byteStream);
// Creates an URL to the file
var uri = new Uri(url, UriKind.RelativeOrAbsolute);
+ using var uri_bstr = ComHelpers.AsBSTR(uri.AbsoluteUri);
// Set the source stream
- mediaEngineEx.SetSourceFromByteStream(videoDataStream, uri.AbsoluteUri);
+ mediaEngineEx->SetSourceFromByteStream(byteStream, uri_bstr);
}
catch
{
@@ -208,8 +218,11 @@ partial void InitializeMediaImpl(string url, long startPosition, long length, re
private unsafe void CompleteMediaInitialization()
{
//Get our video size
- mediaEngine.GetNativeVideoSize(out videoWidth, out videoHeight);
- Duration = TimeSpan.FromSeconds(mediaEngine.Duration);
+ mediaEngine->GetNativeVideoSize(out var width, out var height);
+ videoWidth = (int)width;
+ videoHeight = (int)height;
+
+ Duration = TimeSpan.FromSeconds(mediaEngine->GetDuration());
//Get DXGI surface to be used by our media engine
videoOutputTexture = Texture.New2D(GraphicsDevice, videoWidth, videoHeight, 1, PixelFormat.B8G8R8A8_UNorm, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
@@ -220,12 +233,11 @@ private unsafe void CompleteMediaInitialization()
result.Throw();
videoOutputSurface = outputSurface;
- videoOutputSurfaceSharpDX = new SharpDX.ComObject((IntPtr) videoOutputSurface);
AllocateVideoTexture(videoWidth, videoHeight);
if (videoComponent.PlayAudio != true || videoComponent.AudioEmitters.Any(e => e != null))
- mediaEngine.Muted = true;
+ mediaEngine->SetMuted(true);
}
///
@@ -234,64 +246,243 @@ private unsafe void CompleteMediaInitialization()
/// The play event.
/// The param1.
/// The param2.
- private void OnPlaybackCallback(MediaEngineEvent playEvent, long param1, int param2)
+ private void OnPlaybackCallback(MF_MEDIA_ENGINE_EVENT playEvent, nuint param1, uint param2)
{
switch (playEvent)
{
- case MediaEngineEvent.ResourceLost:
- case MediaEngineEvent.StreamRenderingerror:
- case MediaEngineEvent.Suspend:
- case MediaEngineEvent.Abort:
- case MediaEngineEvent.Emptied:
- case MediaEngineEvent.Stalled:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_RESOURCELOST:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_STREAMRENDERINGERROR:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_SUSPEND:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_ABORT:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_EMPTIED:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_STALLED:
break;
- case MediaEngineEvent.LoadedMetadata:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA:
CompleteMediaInitialization();
break;
- case MediaEngineEvent.Error:
- Logger.Error($"Failed to load the video source. The file codec or format is likely not to be supported. MedieEngine error code=[{(MediaEngineErr)param1}], Windows error code=[{param2}]");
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_ERROR:
+ Logger.Error($"Failed to load the video source. The file codec or format is likely not to be supported. MedieEngine error code=[{(MF_MEDIA_ENGINE_ERR)param1}], Windows error code=[{param2}]");
ReleaseMedia();
break;
- case MediaEngineEvent.FirstFrameReady:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY:
break;
- case MediaEngineEvent.LoadedData:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_LOADEDDATA:
break;
- case MediaEngineEvent.CanPlay:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_CANPLAY:
break;
- case MediaEngineEvent.Seeked:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_SEEKED:
break;
- case MediaEngineEvent.Ended:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_ENDED:
reachedEOF = true;
break;
- case MediaEngineEvent.LoadStart:
- case MediaEngineEvent.Progress:
- case MediaEngineEvent.Waiting:
- case MediaEngineEvent.Playing:
- case MediaEngineEvent.CanPlayThrough:
- case MediaEngineEvent.Seeking:
- case MediaEngineEvent.Play:
- case MediaEngineEvent.Pause:
- case MediaEngineEvent.TimeUpdate:
- case MediaEngineEvent.RateChange:
- case MediaEngineEvent.DurationChange:
- case MediaEngineEvent.VolumeChange:
- case MediaEngineEvent.FormatChange:
- case MediaEngineEvent.PurgeQueuedEvents:
- case MediaEngineEvent.TimelineMarker:
- case MediaEngineEvent.BalanceChange:
- case MediaEngineEvent.DownloadComplete:
- case MediaEngineEvent.BufferingStarted:
- case MediaEngineEvent.BufferingEnded:
- case MediaEngineEvent.FrameStepCompleted:
- case MediaEngineEvent.NotifyStableState:
- case MediaEngineEvent.Trackschange:
- case MediaEngineEvent.OpmInformation:
- case MediaEngineEvent.DelayloadeventChanged:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_LOADSTART:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_PROGRESS:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_WAITING:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_PLAYING:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_CANPLAYTHROUGH:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_SEEKING:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_PLAY:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_PAUSE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_TIMEUPDATE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_RATECHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_VOLUMECHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_FORMATCHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_PURGEQUEUEDEVENTS:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_TIMELINE_MARKER:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_BALANCECHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_DOWNLOADCOMPLETE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_FRAMESTEPCOMPLETED:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_TRACKSCHANGE:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_OPMINFO:
+ case MF_MEDIA_ENGINE_EVENT.MF_MEDIA_ENGINE_EVENT_DELAYLOADEVENT_CHANGED:
break;
default:
throw new ArgumentOutOfRangeException(nameof(playEvent), playEvent, null);
}
}
+
+ sealed class Notify : IMFMediaEngineNotify.Interface
+ {
+ private readonly VideoInstance instance;
+
+ public Notify(VideoInstance instance)
+ {
+ this.instance = instance;
+ }
+
+ public HRESULT EventNotify(uint @event, nuint param1, uint param2)
+ {
+ instance.OnPlaybackCallback((MF_MEDIA_ENGINE_EVENT)@event, param1, param2);
+ return default;
+ }
+ }
+
+ sealed class ComStreamWrapper : IStream.Interface
+ {
+ private readonly Stream stream;
+
+ public ComStreamWrapper(Stream stream)
+ {
+ this.stream = stream;
+ }
+
+ public HRESULT Stat(STATSTG* pstatstg, uint grfStatFlag)
+ {
+ pstatstg->cbSize = (ulong)stream.Length;
+ return default;
+ }
+
+ public HRESULT Seek(long dlibMove, SeekOrigin dwOrigin, [Optional] ulong* plibNewPosition)
+ {
+ var newPosition = stream.Seek(dlibMove, dwOrigin);
+ if (plibNewPosition != null)
+ *plibNewPosition = (ulong)newPosition;
+ return default;
+ }
+
+ public HRESULT Read(void* pv, uint cb, [Optional] uint* pcbRead)
+ {
+ var buffer = new Span(pv, (int)cb);
+ *pcbRead = (uint)stream.Read(buffer);
+ return default;
+ }
+
+ public HRESULT Write(void* pv, uint cb, [Optional] uint* pcbWritten)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT SetSize(ulong libNewSize)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT CopyTo(IStream* pstm, ulong cb, [Optional] ulong* pcbRead, [Optional] ulong* pcbWritten)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT Commit(uint grfCommitFlags)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT Revert()
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT LockRegion(ulong libOffset, ulong cb, uint dwLockType)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT UnlockRegion(ulong libOffset, ulong cb, uint dwLockType)
+ {
+ throw new NotImplementedException();
+ }
+
+ public HRESULT Clone(IStream** ppstm)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
+
+namespace Windows.Win32
+{
+ static unsafe partial class ComHelpers
+ {
+ public static MFComPtr CreateCCW(object instance) where T : unmanaged, IVTable, IComIID
+ {
+ var unknown = (IUnknown*)MFComWrappers.Instance.GetOrCreateComInterfaceForObject(instance, CreateComInterfaceFlags.None);
+ unknown->QueryInterface(out var ppv);
+ unknown->Release();
+ return new MFComPtr(ppv);
+ }
+
+ public static unsafe MFComPtr AsPtr(T* ptr) where T : unmanaged, IComIID => new MFComPtr(ptr);
+
+ public static BSTRPtr AsBSTR(string str)
+ {
+ var bstr = Marshal.StringToBSTR(str);
+ return new BSTRPtr(bstr);
+ }
+
+ // Called by CsWin32
+ static partial void PopulateIUnknownImpl(IUnknown.Vtbl* vtable)
+ where TComInterface : unmanaged
+ {
+ ComWrappers.GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease);
+ vtable->QueryInterface_1 = (delegate* unmanaged[Stdcall])fpQueryInterface;
+ vtable->AddRef_2 = (delegate* unmanaged[Stdcall])fpAddRef;
+ vtable->Release_3 = (delegate* unmanaged[Stdcall])fpRelease;
+ }
+
+ // https://github.com/microsoft/CsWin32/issues/751#issuecomment-1304268295
+ private sealed class MFComWrappers : ComWrappers
+ where T : IVTable, IComIID
+ {
+ public static readonly ComWrappers Instance = new MFComWrappers();
+
+ private static readonly ComInterfaceEntry* s_comInterfaceEntries = CreateComInterfaceEntries();
+
+ private static ComInterfaceEntry* CreateComInterfaceEntries()
+ {
+ var comInterfaceEntries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(T), sizeof(ComInterfaceEntry));
+ comInterfaceEntries->IID = T.Guid;
+ comInterfaceEntries->Vtable = new IntPtr(T.VTable);
+ return comInterfaceEntries;
+ }
+
+ protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
+ {
+ count = 1;
+ return s_comInterfaceEntries;
+ }
+
+ protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void ReleaseObjects(IEnumerable objects)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ // Little helper to ensure Release is called
+ public ref struct MFComPtr : IDisposable where T : unmanaged, IComIID
+ {
+ public readonly T* Ptr;
+ public MFComPtr(T* ptr)
+ {
+ Ptr = ptr;
+ }
+
+ public void Dispose() => ((IUnknown*)Ptr)->Release();
+ public static implicit operator T*(in MFComPtr comPtr) => comPtr.Ptr;
+ public static implicit operator IUnknown*(in MFComPtr comPtr) => (IUnknown*)comPtr.Ptr;
+ public static explicit operator MFComPtr(T* ptr) => new MFComPtr(ptr);
+ }
+
+ // Little helper to ensure FreeBSTR is called
+ public ref struct BSTRPtr : IDisposable
+ {
+ public readonly nint Ptr;
+ public BSTRPtr(nint ptr)
+ {
+ Ptr = ptr;
+ }
+ public void Dispose() => Marshal.FreeBSTR(Ptr);
+ public static implicit operator BSTR(in BSTRPtr bstrPtr) => new BSTR(bstrPtr.Ptr);
+ }
}
}
diff --git a/sources/engine/Stride.Video/VideoSystem.Direct3D.cs b/sources/engine/Stride.Video/VideoSystem.Direct3D.cs
index 63e0a67374..1796e50461 100644
--- a/sources/engine/Stride.Video/VideoSystem.Direct3D.cs
+++ b/sources/engine/Stride.Video/VideoSystem.Direct3D.cs
@@ -2,18 +2,21 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
#if STRIDE_GRAPHICS_API_DIRECT3D11
+#pragma warning disable CA1416 // Validate platform compatibility (no need, we check for Windows already at a higher level)
using System;
-using SharpDX.MediaFoundation;
using Silk.NET.Core.Native;
using Silk.NET.Direct3D11;
using Stride.Games;
+using Windows.Win32.Media.MediaFoundation;
+using static Windows.Win32.PInvoke;
+using IUnknown = Windows.Win32.System.Com.IUnknown;
namespace Stride.Video;
-public partial class VideoSystem
+public unsafe partial class VideoSystem
{
- public DXGIDeviceManager DxgiDeviceManager; // TODO: Remove when Silk includes Media Foundation
+ internal IMFDXGIDeviceManager* DxgiDeviceManager;
public override unsafe void Initialize()
{
@@ -22,10 +25,11 @@ public override unsafe void Initialize()
var graphicsDevice = Services.GetService().GraphicsDevice;
var d3d11Device = graphicsDevice.NativeDevice;
- var d3d11DeviceSharpDX = new SharpDX.ComObject((IntPtr) d3d11Device.Handle);
- DxgiDeviceManager = new DXGIDeviceManager();
- DxgiDeviceManager.ResetDevice(d3d11DeviceSharpDX);
+ IMFDXGIDeviceManager* manager;
+ MFCreateDXGIDeviceManager(out var resetToken, &manager).ThrowOnFailure();
+ DxgiDeviceManager = manager;
+ DxgiDeviceManager->ResetDevice((IUnknown*)(d3d11Device.Handle), resetToken);
// Add multi-thread protection on the device
HResult result = d3d11Device.QueryInterface(out ComPtr multiThread);
@@ -36,7 +40,15 @@ public override unsafe void Initialize()
multiThread.SetMultithreadProtected(true);
multiThread.Dispose();
- MediaManager.Startup();
+ MFStartup(MF_VERSION, 0).ThrowOnFailure();
+ }
+
+ protected override void Destroy()
+ {
+ DxgiDeviceManager->Release();
+ MFShutdown().ThrowOnFailure();
+
+ base.Destroy();
}
}