Acquisizione schermo per la registrazione in video
Questo articolo descrive come codificare i fotogrammi acquisiti dallo schermo con le API Windows.Graphics.Capture in un file video. Per informazioni sull'acquisizione dello schermo delle immagini, vedere Acquisizione dello schermo. Per una semplice app di esempio end-to-end che usa i concetti e le tecniche illustrati in questo articolo, vedere SimpleRecorder.
Panoramica del processo di acquisizione video
Questo articolo fornisce una procedura dettagliata di un'app di esempio che registra il contenuto di una finestra in un file video. Anche se potrebbe sembrare che ci sia un sacco di codice necessario per implementare questo scenario, la struttura generale di un'app di registrazione dello schermo è piuttosto semplice. Il processo di acquisizione dello schermo usa tre funzionalità UWP principali:
- Le API Windows.GraphicsCapture eseguono il lavoro di afferrare effettivamente i pixel dallo schermo. La classe GraphicsCaptureItem rappresenta la finestra o la visualizzazione acquisita. GraphicsCaptureSession viene usato per avviare e arrestare l'operazione di acquisizione. La classe Direct3D11CaptureFramePool mantiene un buffer di fotogrammi in cui vengono copiati i contenuti dello schermo.
- La classe MediaStreamSource riceve i fotogrammi acquisiti e genera un flusso video.
- La classe MediaTranscoder riceve il flusso prodotto da MediaStreamSource e lo codifica in un file video.
Il codice di esempio illustrato in questo articolo può essere categorizzato in alcune attività diverse:
- Inizializzazione: include la configurazione delle classi UWP descritte in precedenza, l'inizializzazione delle interfacce del dispositivo grafico, la selezione di una finestra da acquisire e la configurazione dei parametri di codifica, ad esempio risoluzione e frequenza dei fotogrammi.
- Gestori eventi e threading : il driver principale del ciclo di acquisizione principale è MediaStreamSource che richiede periodicamente fotogrammi tramite l'evento SampleRequested. In questo esempio vengono usati eventi per coordinare le richieste di nuovi frame tra i diversi componenti dell'esempio. La sincronizzazione è importante per consentire l'acquisizione e la codifica simultanea dei fotogrammi.
- Copia di frame: i frame vengono copiati dal buffer dei frame acquisiti in una superficie Direct3D separata che può essere passata a MediaStreamSource in modo che la risorsa non venga sovrascritta durante la codifica. Le API Direct3D vengono usate per eseguire rapidamente questa operazione di copia.
Informazioni sulle API Direct3D
Come indicato in precedenza, la copia di ogni frame acquisito è probabilmente la parte più complessa dell'implementazione illustrata in questo articolo. A un livello basso, questa operazione viene eseguita usando Direct3D. Per questo esempio viene usata la libreria SharpDX per eseguire le operazioni Direct3D da C#. Questa libreria non è più supportata ufficialmente, ma è stata scelta perché le prestazioni delle operazioni di copia di basso livello sono particolarmente adatte per questo scenario. Abbiamo cercato di mantenere le operazioni Direct3D il più discrete possibile per semplificare la sostituzione del codice o di altre librerie per queste attività.
Impostazione del progetto
Il codice di esempio in questa procedura dettagliata è stato creato usando il modello di progetto C# App vuota (Universal Windows) in Visual Studio 2019. Per usare le API Windows.Graphics.Capture nell'app, si deve includere la funzionalità Acquisizione grafica nel file Package.appxmanifest per il progetto. Questo esempio salva i file video generati nella raccolta video nel dispositivo. Per accedere a questa cartella, è necessario includere la funzionalità Raccolta video.
Per installare il pacchetto NuGet SharpDX, in Visual Studio selezionare Gestire i pacchetti NuGet. Nella scheda Sfoglia cercare il pacchetto "SharpDX.Direct3D11" e fare clic su Installa.
Si noti che per ridurre le dimensioni degli elenchi di codice in questo articolo, il codice nella procedura dettagliata seguente omette i riferimenti espliciti allo spazio dei nomi e la dichiarazione di variabili membro della classe MainPage denominate con un carattere di sottolineatura iniziale, "_".
Configurazione per la codifica
Il metodo SetupEncoding descritto in questa sezione inizializza alcuni degli oggetti principali che verranno usati per acquisire e codificare fotogrammi video e configurare i parametri di codifica per il video acquisito. Questo metodo può essere chiamato a livello di codice o in risposta a un'interazione dell'utente, ad esempio un clic su un pulsante. Il listato di codice per SetupEncoding viene illustrato di seguito dopo le descrizioni dei passaggi di inizializzazione.
Verificare la disponibilità del supporto per l'acquisizione. Prima di iniziare il processo di acquisizione, è necessario chiamare GraphicsCaptureSession.IsSupported per assicurarsi che la funzionalità di acquisizione dello schermo sia supportata nel dispositivo corrente.
Inizializzare le interfacce Direct3D. Questo esempio usa Direct3D per copiare i pixel acquisiti dallo schermo in una trama codificata come fotogramma video. I metodi helper usati per inizializzare le interfacce Direct3D, CreateD3DDevice e CreateSharpDXDevice, vengono illustrati più avanti in questo articolo.
Inizializzare un oggetto GraphicsCaptureItem. GraphicsCaptureItem rappresenta un elemento sullo schermo che verrà acquisito, ovvero una finestra o l'intera schermata. Consentire all'utente di selezionare un elemento da acquisire creando un GraphicsCapturePicker e chiamando PickSingleItemAsync.
Creare una trama di composizione. Creare una risorsa trama e una visualizzazione di destinazione di rendering associata che verrà usata per copiare ogni frame video. Questa trama non può essere creata fino a quando non viene creata l'oggetto GraphicsCaptureItem e le relative dimensioni. Vedere la descrizione di WaitForNewFrame per vedere come viene usata questa trama di composizione. Il metodo helper per la creazione di questa trama viene illustrato anche più avanti in questo articolo.
Creare un MediaEncodingProfile e VideoStreamDescriptor. Un'istanza della classe MediaStreamSource acquisisce le immagini acquisite dallo schermo e le codifica in un flusso video. Il flusso video verrà quindi transcodificato in un file video dalla classe MediaTranscoder. VideoStreamDecriptor fornisce parametri di codifica, ad esempio risoluzione e frequenza dei fotogrammi, per MediaStreamSource. I parametri di codifica dei file video per MediaTranscoder vengono specificati con un MediaEncodingProfile. Si noti che le dimensioni usate per la codifica video non devono corrispondere alle dimensioni della finestra acquisita, ma per mantenere semplice questo esempio, le impostazioni di codifica sono hardcoded per usare le dimensioni effettive dell'elemento di acquisizione.
Creare gli oggetti MediaStreamSource e MediaTranscoder. Come accennato in precedenza, l'oggetto MediaStreamSource codifica singoli fotogrammi in un flusso video. Chiamare il costruttore per questa classe passando MediaEncodingProfile creato nel passaggio precedente. Impostare il tempo del buffer su zero e registrare i gestori per gli eventi Starting e SampleRequested, che verranno visualizzati più avanti in questo articolo. Creare quindi una nuova istanza della classe MediaTranscoder e abilitare l'accelerazione hardware.
Creare un file di output Il passaggio finale di questo metodo consiste nel creare un file in cui il video verrà transcodificato. Per questo esempio, creeremo semplicemente un file denominato in modo univoco nella cartella Raccolta video nel dispositivo. Si noti che per accedere a questa cartella, l'app deve specificare la funzionalità "Raccolta video" nel manifesto dell'app. Dopo aver creato il file, aprirlo per la lettura e la scrittura e passare il flusso risultante nel metodo EncodeAsync che verrà visualizzato successivamente.
private async Task SetupEncoding()
{
if (!GraphicsCaptureSession.IsSupported())
{
// Show message to user that screen capture is unsupported
return;
}
// Create the D3D device and SharpDX device
if (_device == null)
{
_device = Direct3D11Helpers.CreateD3DDevice();
}
if (_sharpDxD3dDevice == null)
{
_sharpDxD3dDevice = Direct3D11Helpers.CreateSharpDXDevice(_device);
}
try
{
// Let the user pick an item to capture
var picker = new GraphicsCapturePicker();
_captureItem = await picker.PickSingleItemAsync();
if (_captureItem == null)
{
return;
}
// Initialize a blank texture and render target view for copying frames, using the same size as the capture item
_composeTexture = Direct3D11Helpers.InitializeComposeTexture(_sharpDxD3dDevice, _captureItem.Size);
_composeRenderTargetView = new SharpDX.Direct3D11.RenderTargetView(_sharpDxD3dDevice, _composeTexture);
// This example encodes video using the item's actual size.
var width = (uint)_captureItem.Size.Width;
var height = (uint)_captureItem.Size.Height;
// Make sure the dimensions are are even. Required by some encoders.
width = (width % 2 == 0) ? width : width + 1;
height = (height % 2 == 0) ? height : height + 1;
var temp = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
var bitrate = temp.Video.Bitrate;
uint framerate = 30;
_encodingProfile = new MediaEncodingProfile();
_encodingProfile.Container.Subtype = "MPEG4";
_encodingProfile.Video.Subtype = "H264";
_encodingProfile.Video.Width = width;
_encodingProfile.Video.Height = height;
_encodingProfile.Video.Bitrate = bitrate;
_encodingProfile.Video.FrameRate.Numerator = framerate;
_encodingProfile.Video.FrameRate.Denominator = 1;
_encodingProfile.Video.PixelAspectRatio.Numerator = 1;
_encodingProfile.Video.PixelAspectRatio.Denominator = 1;
var videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, width, height);
_videoDescriptor = new VideoStreamDescriptor(videoProperties);
// Create our MediaStreamSource
_mediaStreamSource = new MediaStreamSource(_videoDescriptor);
_mediaStreamSource.BufferTime = TimeSpan.FromSeconds(0);
_mediaStreamSource.Starting += OnMediaStreamSourceStarting;
_mediaStreamSource.SampleRequested += OnMediaStreamSourceSampleRequested;
// Create our transcoder
_transcoder = new MediaTranscoder();
_transcoder.HardwareAccelerationEnabled = true;
// Create a destination file - Access to the VideosLibrary requires the "Videos Library" capability
var folder = KnownFolders.VideosLibrary;
var name = DateTime.Now.ToString("yyyyMMdd-HHmm-ss");
var file = await folder.CreateFileAsync($"{name}.mp4");
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
await EncodeAsync(stream);
}
catch (Exception ex)
{
return;
}
}
Avviare la codifica
Ora che gli oggetti principali sono stati inizializzati, il metodo EncodeAsync viene implementato per avviare l'operazione di acquisizione. Questo metodo verifica innanzitutto di non essere già registrato e, in caso contrario, chiama il metodo helper StartCapture per iniziare l'acquisizione dei fotogrammi dallo schermo. Questo metodo viene illustrato più avanti in questo articolo. Viene quindi chiamato PrepareMediaStreamSourceTranscodeAsync per preparare MediaTranscoder per transcodificare il flusso video prodotto dall'oggetto MediaStreamSource al flusso di file di output, usando il profilo di codifica creato nella sezione precedente. Dopo aver preparato il transcoder, chiamare TranscodeAsync per avviare la transcodifica. Per altre informazioni sull'uso di MediaTranscoder, vedere Transcode media files.
private async Task EncodeAsync(IRandomAccessStream stream)
{
if (!_isRecording)
{
_isRecording = true;
StartCapture();
var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, _encodingProfile);
await transcode.TranscodeAsync();
}
}
Gestire gli eventi MediaStreamSource
L'oggetto MediaStreamSource acquisisce fotogrammi acquisiti dallo schermo e li trasforma in un flusso video che può essere salvato in un file usando MediaTranscoder. I fotogrammi vengono passati a MediaStreamSource tramite gestori per gli eventi dell'oggetto.
L'evento SampleRequested viene generato quando MediaStreamSource è pronto per un nuovo fotogramma video. Dopo aver verificato che stiamo registrando, viene chiamato il metodo helper WaitForNewFrame per ottenere un nuovo fotogramma acquisito dallo schermo. Questo metodo, illustrato più avanti in questo articolo, restituisce un oggetto ID3D11Surface contenente il frame acquisito. Per questo esempio, si esegue il wrapping dell'interfaccia IDirect3DSurface in una classe helper che archivia anche l'ora di sistema in cui è stato acquisito il frame. Sia il fotogramma che l'ora di sistema vengono passate al metodo factory MediaStreamSample.CreateFromDirect3D11Surface e l'oggetto MediaStreamSample risultante viene impostato sulla proprietà MediaStreamSourceSampleRequest.Sample di MediaStreamSourceSampleRequestedEventArgs. Questo è il modo in cui il frame acquisito viene fornito a MediaStreamSource.
private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
if (_isRecording && !_closed)
{
try
{
using (var frame = WaitForNewFrame())
{
if (frame == null)
{
args.Request.Sample = null;
Stop();
Cleanup();
return;
}
var timeStamp = frame.SystemRelativeTime;
var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
args.Request.Sample = sample;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
Debug.WriteLine(e.StackTrace);
Debug.WriteLine(e);
args.Request.Sample = null;
Stop();
Cleanup();
}
}
else
{
args.Request.Sample = null;
Stop();
Cleanup();
}
}
Nel gestore per l'evento Starting chiamiamo WaitForNewFrame, ma passiamo solo l'ora di sistema in cui è stato acquisito il fotogramma al metodo MediaStreamSourceStartingRequest.SetActualStartPosition, usato da MediaStreamSource per codificare correttamente la tempistica dei fotogrammi successivi.
private void OnMediaStreamSourceStarting(MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)
{
using (var frame = WaitForNewFrame())
{
args.Request.SetActualStartPosition(frame.SystemRelativeTime);
}
}
Avviare l'acquisizione
Il metodo StartCapture illustrato in questo passaggio viene chiamato dal metodo helper EncodeAsync illustrato in un passaggio precedente. In primo luogo, questo metodo inizializza un set di oggetti evento utilizzati per controllare il flusso dell'operazione di acquisizione.
- _multithread è una classe helper che esegue il wrapping dell'oggetto Multithread della libreria SharpDX che verrà usato per assicurarsi che nessun altro thread abbia accesso alla trama SharpDX mentre viene copiato.
- _frameEvent viene usato per segnalare che un nuovo fotogramma è stato acquisito e può essere passato a MediaStreamSource
- _closedEvent segnala che la registrazione è stata arrestata e che non dovremmo aspettare nuovi fotogrammi.
L'evento frame e l'evento chiuso vengono aggiunti a una matrice in modo da poter attendere uno di essi nel ciclo di acquisizione.
Il resto del metodo StartCapture configura le API Windows.Graphics.Capture che eseguiranno l'acquisizione dello schermo effettiva. Prima di tutto, viene registrato un evento per l'evento CaptureItem.Closed. Viene quindi creato un oggetto Direct3D11CaptureFramePool, che consente di memorizzare nel buffer più fotogrammi acquisiti alla volta. Il metodo CreateFreeThreaded viene usato per creare il pool di frame in modo che l'evento FrameArrived venga chiamato nel thread di lavoro del pool anziché nel thread. Viene quindi registrato un gestore per l'evento FrameArrived. Infine, viene creato un elemento GraphicsCaptureSession per l'oggetto CaptureItem selezionato e l'acquisizione di fotogrammi viene avviata chiamando StartCapture.
public void StartCapture()
{
_multithread = _sharpDxD3dDevice.QueryInterface<SharpDX.Direct3D11.Multithread>();
_multithread.SetMultithreadProtected(true);
_frameEvent = new ManualResetEvent(false);
_closedEvent = new ManualResetEvent(false);
_events = new[] { _closedEvent, _frameEvent };
_captureItem.Closed += OnClosed;
_framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
_device,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
1,
_captureItem.Size);
_framePool.FrameArrived += OnFrameArrived;
_session = _framePool.CreateCaptureSession(_captureItem);
_session.StartCapture();
}
Gestire gli eventi di acquisizione grafica
Nel passaggio precedente sono stati registrati due gestori per gli eventi di acquisizione grafica e sono stati configurati alcuni eventi per gestire il flusso del ciclo di acquisizione.
L'evento FrameArrived viene generato quando Direct3D11CaptureFramePool ha un nuovo frame acquisito disponibile. Nel gestore per questo evento chiamare TryGetNextFrame sul mittente per ottenere il frame acquisito successivo. Dopo aver recuperato il fotogramma, impostiamo il _frameEvent in modo che il ciclo di acquisizione sappia che è disponibile un nuovo fotogramma.
private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
_currentFrame = sender.TryGetNextFrame();
_frameEvent.Set();
}
Nel gestore eventi Closed segnaliamo il _closedEvent in modo che il ciclo di acquisizione sappia quando arrestarsi.
private void OnClosed(GraphicsCaptureItem sender, object args)
{
_closedEvent.Set();
}
Attendere nuovi frame
Il metodo helper WaitForNewFrame descritto in questa sezione è la posizione in cui si verifica il sovraccarico del ciclo di acquisizione. Tenere presente che questo metodo viene chiamato dal gestore eventi OnMediaStreamSourceSampleRequested ogni volta che MediaStreamSource è pronto per l'aggiunta di un nuovo fotogramma al flusso video. A livello generale, questa funzione copia semplicemente ogni fotogramma video acquisito dallo schermo da una superficie Direct3D a un'altra in modo che possa essere passata a MediaStreamSource per la codifica mentre viene acquisito un nuovo fotogramma. In questo esempio viene utilizzata la libreria SharpDX per eseguire l'operazione di copia effettiva.
Prima di attendere un nuovo frame, il metodo elimina qualsiasi frame precedente archiviato nella variabile di classe, _currentFrame e reimposta il _frameEvent. Il metodo attende quindi che venga segnalato il _frameEvent o il _closedEvent. Se l'evento chiuso è impostato, l'app chiama un metodo helper per pulire le risorse di acquisizione. Questo metodo viene illustrato più avanti in questo articolo.
Se l'evento frame è impostato, sappiamo che è stato chiamato il gestore eventi FrameArrived definito nel passaggio precedente e iniziamo il processo di copia dei dati dei frame acquisiti in una superficie Direct3D 11 che verrà passata a MediaStreamSource.
Questo esempio usa una classe helper, SurfaceWithInfo, che consente semplicemente di passare il fotogramma video e l'ora di sistema del fotogramma, entrambi richiesti da MediaStreamSource, come singolo oggetto. Il primo passaggio del processo di copia dei fotogrammi consiste nel creare un'istanza di questa classe e impostare l'ora di sistema.
I passaggi successivi sono la parte di questo esempio che si basa specificamente sulla libreria SharpDX. Le funzioni helper usate qui sono definite alla fine di questo articolo. Prima di tutto si usa MultiThreadLock per assicurarsi che nessun altro thread abbia accesso al buffer dei fotogrammi video mentre si esegue la copia. Successivamente, chiamiamo il metodo helper CreateSharpDXTexture2D per creare un oggetto SharpDX Texture2D dal fotogramma video. Questa sarà la trama di origine per l'operazione di copia.
Successivamente, si copia dall'oggetto Texture2D creato nel passaggio precedente nella trama di composizione creata in precedenza nel processo. Questa trama di composizione funge da buffer di scambio in modo che il processo di codifica possa operare sui pixel mentre viene acquisito il frame successivo. Per eseguire la copia, si cancella la visualizzazione di destinazione di rendering associata alla trama di composizione, quindi si definisce l'area all'interno della trama da copiare, l'intera trama in questo caso e quindi si chiama CopySubresourceRegion per copiare effettivamente i pixel nella trama di composizione.
Creiamo una copia della descrizione della trama da usare quando creiamo la trama di destinazione, ma la descrizione viene modificata, impostando BindFlags su RenderTarget in modo che la nuova trama abbia accesso in scrittura. L'impostazione di CpuAccessFlags su Nessuno consente al sistema di ottimizzare l'operazione di copia. La descrizione della trama viene usata per creare una nuova risorsa trama e la risorsa trama di composizione viene copiata in questa nuova risorsa con una chiamata a CopyResource. Viene infine chiamato CreateDirect3DSurfaceFromSharpDXTexture per creare l'oggetto IDirect3DSurface restituito da questo metodo.
public SurfaceWithInfo WaitForNewFrame()
{
// Let's get a fresh one.
_currentFrame?.Dispose();
_frameEvent.Reset();
var signaledEvent = _events[WaitHandle.WaitAny(_events)];
if (signaledEvent == _closedEvent)
{
Cleanup();
return null;
}
var result = new SurfaceWithInfo();
result.SystemRelativeTime = _currentFrame.SystemRelativeTime;
using (var multithreadLock = new MultithreadLock(_multithread))
using (var sourceTexture = Direct3D11Helpers.CreateSharpDXTexture2D(_currentFrame.Surface))
{
_sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(_composeRenderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
var width = Math.Clamp(_currentFrame.ContentSize.Width, 0, _currentFrame.Surface.Description.Width);
var height = Math.Clamp(_currentFrame.ContentSize.Height, 0, _currentFrame.Surface.Description.Height);
var region = new SharpDX.Direct3D11.ResourceRegion(0, 0, 0, width, height, 1);
_sharpDxD3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, _composeTexture, 0);
var description = sourceTexture.Description;
description.Usage = SharpDX.Direct3D11.ResourceUsage.Default;
description.BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget;
description.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None;
description.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;
using (var copyTexture = new SharpDX.Direct3D11.Texture2D(_sharpDxD3dDevice, description))
{
_sharpDxD3dDevice.ImmediateContext.CopyResource(_composeTexture, copyTexture);
result.Surface = Direct3D11Helpers.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture);
}
}
return result;
}
Arrestare l'acquisizione e la pulizia delle risorse
Il metodo Stop consente di arrestare l'operazione di acquisizione. L'app può chiamare questa operazione a livello di codice o in risposta a un'interazione dell'utente, ad esempio un clic su un pulsante. Questo metodo imposta semplicemente il _closedEvent. Il metodo WaitForNewFrame definito nei passaggi precedenti cerca questo evento e, se impostato, arresta l'operazione di acquisizione.
private void Stop()
{
_closedEvent.Set();
}
Il metodo Cleanup viene usato per eliminare correttamente le risorse create durante l'operazione di copia. Valuta gli ambiti seguenti:
- L'oggetto Direct3D11CaptureFramePool utilizzato dalla sessione di acquisizione
- GraphicsCaptureSession e GraphicsCaptureItem
- Dispositivi Direct3D e SharpDX
- La trama SharpDX e la visualizzazione di destinazione di rendering usata nell'operazione di copia.
- Direct3D11CaptureFrame usato per archiviare il frame corrente.
private void Cleanup()
{
_framePool?.Dispose();
_session?.Dispose();
if (_captureItem != null)
{
_captureItem.Closed -= OnClosed;
}
_captureItem = null;
_device = null;
_sharpDxD3dDevice = null;
_composeTexture?.Dispose();
_composeTexture = null;
_composeRenderTargetView?.Dispose();
_composeRenderTargetView = null;
_currentFrame?.Dispose();
}
Classi wrapper helper
Le classi helper seguenti sono state definite per facilitare l'uso del codice di esempio in questo articolo.
La classe helper MultithreadLock esegue il wrapping della classe SharpDX Multithread che assicura che gli altri thread non accingono alle risorse della trama durante la copia.
class MultithreadLock : IDisposable
{
public MultithreadLock(SharpDX.Direct3D11.Multithread multithread)
{
_multithread = multithread;
_multithread?.Enter();
}
public void Dispose()
{
_multithread?.Leave();
_multithread = null;
}
private SharpDX.Direct3D11.Multithread _multithread;
}
SurfaceWithInfo viene usato per associare un oggetto IDirect3DSurface a un Oggetto SystemRelativeTime che rappresenta un frame acquisito e l'ora in cui è stata acquisita, rispettivamente.
public sealed class SurfaceWithInfo : IDisposable
{
public IDirect3DSurface Surface { get; internal set; }
public TimeSpan SystemRelativeTime { get; internal set; }
public void Dispose()
{
Surface?.Dispose();
Surface = null;
}
}
API helper Direct3D e SharpDX
Le API helper seguenti sono definite per astrarre la creazione di risorse Direct3D e SharpDX. Una spiegazione dettagliata di queste tecnologie non rientra nell'ambito di questo articolo, ma il codice viene fornito qui per consentire di implementare il codice di esempio illustrato nella procedura dettagliata.
[ComImport]
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
interface IDirect3DDxgiInterfaceAccess
{
IntPtr GetInterface([In] ref Guid iid);
};
public static class Direct3D11Helpers
{
internal static Guid IInspectable = new Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
internal static Guid ID3D11Resource = new Guid("dc8e63f3-d12b-4952-b47b-5e45026a862d");
internal static Guid IDXGIAdapter3 = new Guid("645967A4-1392-4310-A798-8053CE3E93FD");
internal static Guid ID3D11Device = new Guid("db6f6ddb-ac77-4e88-8253-819df9bbf140");
internal static Guid ID3D11Texture2D = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
[DllImport(
"d3d11.dll",
EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
SetLastError = true,
CharSet = CharSet.Unicode,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall
)]
internal static extern UInt32 CreateDirect3D11DeviceFromDXGIDevice(IntPtr dxgiDevice, out IntPtr graphicsDevice);
[DllImport(
"d3d11.dll",
EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
SetLastError = true,
CharSet = CharSet.Unicode,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall
)]
internal static extern UInt32 CreateDirect3D11SurfaceFromDXGISurface(IntPtr dxgiSurface, out IntPtr graphicsSurface);
public static IDirect3DDevice CreateD3DDevice()
{
return CreateD3DDevice(false);
}
public static IDirect3DDevice CreateD3DDevice(bool useWARP)
{
var d3dDevice = new SharpDX.Direct3D11.Device(
useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
IDirect3DDevice device = null;
// Acquire the DXGI interface for the Direct3D device.
using (var dxgiDevice = d3dDevice.QueryInterface<SharpDX.DXGI.Device3>())
{
// Wrap the native device using a WinRT interop object.
uint hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.NativePointer, out IntPtr pUnknown);
if (hr == 0)
{
device = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DDevice;
Marshal.Release(pUnknown);
}
}
return device;
}
internal static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
{
IDirect3DSurface surface = null;
// Acquire the DXGI interface for the Direct3D surface.
using (var dxgiSurface = texture.QueryInterface<SharpDX.DXGI.Surface>())
{
// Wrap the native device using a WinRT interop object.
uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out IntPtr pUnknown);
if (hr == 0)
{
surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
Marshal.Release(pUnknown);
}
}
return surface;
}
internal static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
{
var access = (IDirect3DDxgiInterfaceAccess)device;
var d3dPointer = access.GetInterface(ID3D11Device);
var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
return d3dDevice;
}
internal static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
{
var access = (IDirect3DDxgiInterfaceAccess)surface;
var d3dPointer = access.GetInterface(ID3D11Texture2D);
var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
return d3dSurface;
}
public static SharpDX.Direct3D11.Texture2D InitializeComposeTexture(
SharpDX.Direct3D11.Device sharpDxD3dDevice,
SizeInt32 size)
{
var description = new SharpDX.Direct3D11.Texture2DDescription
{
Width = size.Width,
Height = size.Height,
MipLevels = 1,
ArraySize = 1,
Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
SampleDescription = new SharpDX.DXGI.SampleDescription()
{
Count = 1,
Quality = 0
},
Usage = SharpDX.Direct3D11.ResourceUsage.Default,
BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget,
CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None
};
var composeTexture = new SharpDX.Direct3D11.Texture2D(sharpDxD3dDevice, description);
using (var renderTargetView = new SharpDX.Direct3D11.RenderTargetView(sharpDxD3dDevice, composeTexture))
{
sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(renderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
}
return composeTexture;
}
}