using SkiaSharp; using Vortice; using Vortice.Direct3D; using Vortice.Direct3D11; using Vortice.DXGI; using static Vortice.Direct3D11.D3D11; // From: https://github.com/amerkoleci/Vortice.Windows/blob/main/samples/HelloDirect3D11/D3D11GraphicsDevice.cs namespace FenUISharp { public class DirectCompositionContext : IDisposable { private static readonly FeatureLevel[] supportedFeatureLevels = new[] { FeatureLevel.Level_11_1, FeatureLevel.Level_11_0, FeatureLevel.Level_10_1, FeatureLevel.Level_10_0 }; public Func Size { get; protected set; } public IntPtr hWnd { get; protected set; } public Format ColorFormat { get; protected set; } public Format DepthStencilFormat { get; protected set; } public IDXGIFactory2 Factory; public readonly ID3D11Device1 Device; public readonly FeatureLevel FeatureLevel; public readonly ID3D11DeviceContext1 DeviceContext; public IDXGISwapChain1? SwapChain { get; protected set; } public ID3D11Texture2D? BackBufferTexture { get; protected set; } public ID3D11Texture2D? OffscreenTexture { get; protected set; } public ID3D11RenderTargetView RenderTargetView { get; protected set; } public ID3D11Texture2D? DepthStencilTexture { get; protected set; } public ID3D11DepthStencilView? DepthStencilView { get; protected set; } public ColorSpaceType ColorSpace = ColorSpaceType.RgbFullG22NoneP709; public uint BufferCount { get; } = 2; public DirectCompositionContext(IntPtr windowHandle, Func size, Format colorFormat = Format.B8G8R8A8_UNorm, Format depthStencilFormat = Format.D32_Float) { this.Size = size; this.hWnd = windowHandle; this.ColorFormat = colorFormat; this.DepthStencilFormat = depthStencilFormat; // Creating factory Factory = DXGI.CreateDXGIFactory1(); // Getting adapter and trying to create device using (IDXGIAdapter1 adapter = GetHardwareAdapter()) { DeviceCreationFlags creationFlags = DeviceCreationFlags.BgraSupport; if (FenUI.debugEnabled) creationFlags |= DeviceCreationFlags.Debug; if (D3D11CreateDevice( adapter, DriverType.Unknown, /* Try hardware later */ creationFlags, supportedFeatureLevels, out ID3D11Device temporaryDevice, out FeatureLevel, out ID3D11DeviceContext temporaryContext).Failure) { // Initialization fails, create fallback D3D11CreateDevice( IntPtr.Zero, DriverType.Warp, creationFlags, supportedFeatureLevels, out temporaryDevice, out FeatureLevel, out temporaryContext).CheckError(); } // Creating device and context Device = temporaryDevice.QueryInterface(); DeviceContext = temporaryContext.QueryInterface(); // Disposing temp temporaryContext.Dispose(); temporaryDevice.Dispose(); } // Creating swapchain CreateSwapchain(); } public void CreateSwapchain() { SwapChain?.Dispose(); BackBufferTexture?.Dispose(); RenderTargetView?.Dispose(); OffscreenTexture?.Dispose(); DepthStencilTexture?.Dispose(); DepthStencilView?.Dispose(); if (hWnd != IntPtr.Zero) { // Defining swapchain description SwapChainDescription1 swapChainDescription = new() { Width = (uint)Size().x, Height = (uint)Size().y, Format = ColorFormat, BufferCount = BufferCount, BufferUsage = Usage.RenderTargetOutput, SampleDescription = SampleDescription.Default, Scaling = Scaling.Stretch, SwapEffect = SwapEffect.FlipDiscard, AlphaMode = AlphaMode.Ignore // Test if that works }; // Defining fullscreen descriptor SwapChainFullscreenDescription fullscreenDescription = new SwapChainFullscreenDescription { Windowed = true }; // Creating swapchain and linking it to the window SwapChain = Factory.CreateSwapChainForHwnd(Device, hWnd, swapChainDescription, fullscreenDescription); Factory.MakeWindowAssociation(hWnd, WindowAssociationFlags.IgnoreAltEnter); // Making sure colors display properly on HDR screens UpdateColorSpace(); // Set buffers BackBufferTexture = SwapChain.GetBuffer(0); RenderTargetView = Device.CreateRenderTargetView(BackBufferTexture); } else { // Create offscreen texture OffscreenTexture = Device.CreateTexture2D(ColorFormat, (uint)Size().x, (uint)Size().y, mipLevels: 1, bindFlags: BindFlags.ShaderResource | BindFlags.RenderTarget ); RenderTargetView = Device.CreateRenderTargetView(OffscreenTexture); } // Creating depth stencil. Not really needed, still good to have if (DepthStencilFormat != Format.Unknown) { DepthStencilTexture = Device.CreateTexture2D(DepthStencilFormat, (uint)Size().x, (uint)Size().y, mipLevels: 1, bindFlags: BindFlags.DepthStencil ); DepthStencilView = Device.CreateDepthStencilView(DepthStencilTexture!, new DepthStencilViewDescription(DepthStencilTexture, DepthStencilViewDimension.Texture2D)); } } private void UpdateColorSpace() { if (!Factory.IsCurrent) { // Output information is cached on the DXGI Factory. If it is stale we need to create a new factory. Factory.Dispose(); Factory = DXGI.CreateDXGIFactory1(); } bool isDisplayHDR10 = false; // Get the rectangle bounds of the app window. if (FContext.GetCurrentWindow() != null) { SKRect windowBounds = FContext.GetCurrentWindow().Bounds; int ax1 = (int)windowBounds.Left; int ay1 = (int)windowBounds.Top; int ax2 = (int)windowBounds.Right; int ay2 = (int)windowBounds.Bottom; IDXGIOutput? bestOutput = default; long bestIntersectArea = -1; for (uint adapterIndex = 0; Factory.EnumAdapters1(adapterIndex, out IDXGIAdapter1? adapter).Success; adapterIndex++) { for (uint outputIndex = 0; adapter.EnumOutputs(outputIndex, out IDXGIOutput? output).Success; outputIndex++) { // Get the rectangle bounds of current output. OutputDescription outputDesc = output.Description; RawRect r = outputDesc.DesktopCoordinates; // Compute the intersection int intersectArea = ComputeIntersectionArea(ax1, ay1, ax2, ay2, r.Left, r.Top, r.Right, r.Bottom); if (intersectArea > bestIntersectArea) { bestOutput = output; bestIntersectArea = intersectArea; } else { output?.Dispose(); } } adapter.Dispose(); } if (bestOutput != null) { using IDXGIOutput6? output6 = bestOutput.QueryInterfaceOrNull(); if (output6 != null) { OutputDescription1 outputDesc = output6.Description1; if (outputDesc.ColorSpace == ColorSpaceType.RgbFullG2084NoneP2020) { // Display output is HDR10. isDisplayHDR10 = true; } } } } if (isDisplayHDR10) { switch (ColorFormat) { case Format.R10G10B10A2_UNorm: // The application creates the HDR10 signal. ColorSpace = ColorSpaceType.RgbFullG2084NoneP2020; break; case Format.R16G16B16A16_Float: // The system creates the HDR10 signal; application uses linear values. ColorSpace = ColorSpaceType.RgbFullG10NoneP709; break; default: ColorSpace = ColorSpaceType.RgbFullG22NoneP709; break; } } using IDXGISwapChain3? swapChain3 = SwapChain?.QueryInterfaceOrNull(); if (swapChain3 != null) { SwapChainColorSpaceSupportFlags colorSpaceSupport = swapChain3.CheckColorSpaceSupport(ColorSpace); if ((colorSpaceSupport & SwapChainColorSpaceSupportFlags.Present) != SwapChainColorSpaceSupportFlags.None) { swapChain3.SetColorSpace1(ColorSpace); } } } private static int ComputeIntersectionArea( int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) { return Math.Max(0, Math.Min(ax2, bx2) - Math.Max(ax1, bx1)) * Math.Max(0, Math.Min(ay2, by2) - Math.Max(ay1, by1)); } public IDXGIAdapter1 GetHardwareAdapter() { IDXGIFactory6? factory6 = Factory.QueryInterfaceOrNull(); if (factory6 != null) { for (uint adapterIndex = 0; factory6.EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance, out IDXGIAdapter1? adapter).Success; adapterIndex++) { if (adapter == null) { continue; } AdapterDescription1 desc = adapter.Description1; if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None) { // Don't select the Basic Render Driver adapter. adapter.Dispose(); continue; } factory6.Dispose(); return adapter; } factory6.Dispose(); } for (uint adapterIndex = 0; Factory.EnumAdapters1(adapterIndex, out IDXGIAdapter1? adapter).Success; adapterIndex++) { AdapterDescription1 desc = adapter.Description1; if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None) { // Don't select the Basic Render Driver adapter. adapter.Dispose(); continue; } return adapter; } throw new InvalidOperationException("Cannot detect D3D11 adapter"); } public void Dispose() { BackBufferTexture?.Dispose(); OffscreenTexture?.Dispose(); RenderTargetView.Dispose(); DepthStencilTexture?.Dispose(); DepthStencilView?.Dispose(); DeviceContext.ClearState(); DeviceContext.Flush(); DeviceContext.Dispose(); SwapChain?.Dispose(); Device.Dispose(); } } }