Add OpenGL <-> D3D11 interop handling
Lets D3D11 display method take a wrapping GL tex id fast path, avoiding a CPU readback for GL cores. Requires the WGL_NV_DX_interop2 extension (which is probably somewhat well supported by GPUs?)
This commit is contained in:
parent
d9ac6fc455
commit
ef05b6ec2f
|
@ -23,6 +23,7 @@
|
|||
<PackageVersion Include="Silk.NET.OpenAL.Extensions.Enumeration" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenAL.Extensions.EXT" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenGL" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.WGL.Extensions.NV" Version="2.21.0" />
|
||||
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.8" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" /><!-- don't forget to update .stylecop.json at the same time -->
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<PackageReference Include="Cyotek.Drawing.BitmapFont" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="Silk.NET.OpenGL" />
|
||||
<PackageReference Include="Silk.NET.WGL.Extensions.NV" />
|
||||
<PackageReference Include="ppy.SDL2-CS" ExcludeAssets="native;contentFiles" />
|
||||
<PackageReference Include="Vortice.Direct3D11" />
|
||||
<PackageReference Include="Vortice.D3DCompiler" />
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
using System;
|
||||
|
||||
using Silk.NET.Core.Contexts;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.WGL.Extensions.NV;
|
||||
|
||||
using Vortice.Direct3D;
|
||||
using Vortice.Direct3D11;
|
||||
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
internal sealed class D3D11GLInterop : IDisposable
|
||||
{
|
||||
public static readonly bool IsAvailable;
|
||||
|
||||
// we use glCopyImageSubData in order to copy an external gl texture to our wrapped gl texture
|
||||
// this is only guaranteed for GL 4.3 however, so we want to handle grabbing the ARB_copy_image or NV_copy_image extension variants
|
||||
private static IntPtr GetGLProcAddress(string proc)
|
||||
{
|
||||
var ret = SDL2OpenGLContext.GetGLProcAddress(proc);
|
||||
if (ret == IntPtr.Zero && proc == "glCopyImageSubData")
|
||||
{
|
||||
// note that both core and ARB_copy_image use the same proc name
|
||||
// however, NV_copy_image uses a different name
|
||||
ret = SDL2OpenGLContext.GetGLProcAddress("glCopyImageSubDataNV");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static readonly GL GL;
|
||||
private static readonly NVDXInterop NVDXInterop;
|
||||
|
||||
static D3D11GLInterop()
|
||||
{
|
||||
using (new SavedOpenGLContext())
|
||||
{
|
||||
try
|
||||
{
|
||||
// from Kronos:
|
||||
// "WGL function retrieval does require an active, current context. However, the use of WGL functions do not.
|
||||
// Therefore, you can destroy the context after querying all of the WGL extension functions."
|
||||
|
||||
// from Microsoft:
|
||||
// "The extension function addresses are unique for each pixel format.
|
||||
// All rendering contexts of a given pixel format share the same extension function addresses."
|
||||
|
||||
var (majorVersion, minorVersion) = OpenGLVersion.SupportsVersion(4, 3) ? (4, 3) : (2, 1);
|
||||
using var glContext = new SDL2OpenGLContext(majorVersion, minorVersion, true, false);
|
||||
|
||||
GL = GL.GetApi(GetGLProcAddress);
|
||||
if (GL.CurrentVTable.Load("glCopyImageSubData") == IntPtr.Zero
|
||||
|| GL.CurrentVTable.Load("glGenTextures") == IntPtr.Zero
|
||||
|| GL.CurrentVTable.Load("glDeleteTextures") == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// note: Silk.NET's WGL.TryGetExtension function seemed to be bugged and just result in NREs...
|
||||
NVDXInterop = new(new LamdaNativeContext(SDL2OpenGLContext.GetGLProcAddress));
|
||||
if (NVDXInterop.CurrentVTable.Load("wglDXOpenDeviceNV") == IntPtr.Zero
|
||||
|| NVDXInterop.CurrentVTable.Load("wglDXCloseDeviceNV") == IntPtr.Zero
|
||||
|| NVDXInterop.CurrentVTable.Load("wglDXRegisterObjectNV") == IntPtr.Zero
|
||||
|| NVDXInterop.CurrentVTable.Load("wglDXUnregisterObjectNV") == IntPtr.Zero
|
||||
|| NVDXInterop.CurrentVTable.Load("wglDXLockObjectsNV") == IntPtr.Zero
|
||||
|| NVDXInterop.CurrentVTable.Load("wglDXUnlockObjectsNV") == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ID3D11Device device = null;
|
||||
var dxInteropDevice = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
D3D11.D3D11CreateDevice(
|
||||
adapter: null,
|
||||
DriverType.Hardware,
|
||||
DeviceCreationFlags.BgraSupport,
|
||||
null!,
|
||||
out device,
|
||||
out var context).CheckError();
|
||||
context.Dispose();
|
||||
|
||||
unsafe
|
||||
{
|
||||
dxInteropDevice = NVDXInterop.DxopenDevice((void*)device!.NativePointer);
|
||||
}
|
||||
|
||||
// TODO: test interop harder?
|
||||
IsAvailable = dxInteropDevice != IntPtr.Zero;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (dxInteropDevice != IntPtr.Zero)
|
||||
{
|
||||
NVDXInterop.DxcloseDevice(dxInteropDevice);
|
||||
}
|
||||
|
||||
device?.Dispose();
|
||||
|
||||
if (!IsAvailable)
|
||||
{
|
||||
GL?.Dispose();
|
||||
GL = null;
|
||||
NVDXInterop?.Dispose();
|
||||
NVDXInterop = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly D3D11Resources _resources;
|
||||
|
||||
private IntPtr _dxInteropDevice;
|
||||
private IntPtr _lastGLContext;
|
||||
private D3D11Texture2D _wrappedGLTexture;
|
||||
private IntPtr _wrappedGLTexInteropHandle;
|
||||
private uint _wrappedGLTexID;
|
||||
|
||||
public D3D11GLInterop(D3D11Resources resources)
|
||||
{
|
||||
using (new SavedOpenGLContext())
|
||||
{
|
||||
_resources = resources;
|
||||
try
|
||||
{
|
||||
OpenInteropDevice();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenInteropDevice()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
_dxInteropDevice = NVDXInterop.DxopenDevice((void*)_resources.Device.NativePointer);
|
||||
}
|
||||
|
||||
if (_dxInteropDevice == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to open DX interop device");
|
||||
}
|
||||
}
|
||||
|
||||
public D3D11Texture2D WrapGLTexture(int glTexId, int width, int height)
|
||||
{
|
||||
var glContext = SDL_GL_GetCurrentContext();
|
||||
if (glContext == IntPtr.Zero)
|
||||
{
|
||||
// can't do much without a context...
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_lastGLContext != glContext)
|
||||
{
|
||||
DestroyWrappedTexture(hasActiveContext: false);
|
||||
_lastGLContext = glContext;
|
||||
}
|
||||
|
||||
if (_wrappedGLTexture == null || _wrappedGLTexture.Width != width || _wrappedGLTexture.Height != height)
|
||||
{
|
||||
DestroyWrappedTexture(hasActiveContext: true);
|
||||
CreateWrappedTexture(width, height);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var wrappedGLTexInteropHandle = _wrappedGLTexInteropHandle;
|
||||
NVDXInterop.DxlockObjects(hDevice: _dxInteropDevice, 1, &wrappedGLTexInteropHandle);
|
||||
|
||||
GL.CopyImageSubData((uint)glTexId, CopyImageSubDataTarget.Texture2D, 0, 0, 0, 0,
|
||||
_wrappedGLTexID, CopyImageSubDataTarget.Texture2D, 0, 0, 0, 0, (uint)width, (uint)height, 1);
|
||||
|
||||
NVDXInterop.DxunlockObjects(hDevice: _dxInteropDevice, 1, &wrappedGLTexInteropHandle);
|
||||
}
|
||||
|
||||
return _wrappedGLTexture;
|
||||
}
|
||||
|
||||
private void CreateWrappedTexture(int width, int height)
|
||||
{
|
||||
_wrappedGLTexID = GL.GenTexture();
|
||||
_wrappedGLTexture = new(_resources, BindFlags.ShaderResource, ResourceUsage.Default, CpuAccessFlags.None, width, height, wrapped: true);
|
||||
unsafe
|
||||
{
|
||||
_wrappedGLTexInteropHandle = NVDXInterop.DxregisterObject(
|
||||
hDevice: _dxInteropDevice,
|
||||
dxObject: (void*)_wrappedGLTexture.Texture.NativePointer,
|
||||
name: _wrappedGLTexID,
|
||||
type: (NV)GLEnum.Texture2D,
|
||||
access: NV.AccessWriteDiscardNV);
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyWrappedTexture(bool hasActiveContext = false)
|
||||
{
|
||||
if (_wrappedGLTexInteropHandle != IntPtr.Zero)
|
||||
{
|
||||
NVDXInterop.DxunregisterObject(_dxInteropDevice, _wrappedGLTexInteropHandle);
|
||||
_wrappedGLTexInteropHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
// this gl tex id is owned by the external context, which might be unavailable
|
||||
// if it's unavailable, we assume that context was just destroyed
|
||||
// therefore, assume the texture was already destroy in that case
|
||||
if (hasActiveContext && _wrappedGLTexID != 0)
|
||||
{
|
||||
GL.DeleteTexture(_wrappedGLTexID);
|
||||
}
|
||||
|
||||
_wrappedGLTexID = 0;
|
||||
|
||||
_wrappedGLTexture?.Dispose();
|
||||
_wrappedGLTexture = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DestroyWrappedTexture(hasActiveContext: false);
|
||||
|
||||
if (_dxInteropDevice != IntPtr.Zero)
|
||||
{
|
||||
NVDXInterop.DxcloseDevice(_dxInteropDevice);
|
||||
_dxInteropDevice = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_lastGLContext = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,11 +44,18 @@ namespace BizHawk.Bizware.Graphics
|
|||
// use this to debug D3D11 calls
|
||||
// note debug layer requires extra steps to use: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-layers#debug-layer
|
||||
// also debug output will only be present with a "native debugger" attached (pure managed debugger can't see this output)
|
||||
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport | DeviceCreationFlags.Debug;
|
||||
var creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport | DeviceCreationFlags.Debug;
|
||||
#else
|
||||
// IGL is not thread safe, so let's not bother making this implementation thread safe
|
||||
const DeviceCreationFlags creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport;
|
||||
var creationFlags = DeviceCreationFlags.Singlethreaded | DeviceCreationFlags.BgraSupport;
|
||||
#endif
|
||||
|
||||
// GL interop doesn't support the single threaded flag
|
||||
if (D3D11GLInterop.IsAvailable)
|
||||
{
|
||||
creationFlags &= ~DeviceCreationFlags.Singlethreaded;
|
||||
}
|
||||
|
||||
D3D11.D3D11CreateDevice(
|
||||
adapter: null,
|
||||
DriverType.Hardware,
|
||||
|
|
|
@ -21,15 +21,15 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
private ID3D11Texture2D StagingTexture;
|
||||
|
||||
protected ID3D11Texture2D Texture;
|
||||
public ID3D11Texture2D Texture;
|
||||
public ID3D11ShaderResourceView SRV;
|
||||
public bool LinearFiltering;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public bool IsUpsideDown => false;
|
||||
public bool IsUpsideDown { get; }
|
||||
|
||||
public D3D11Texture2D(D3D11Resources resources, BindFlags bindFlags, ResourceUsage usage, CpuAccessFlags cpuAccessFlags, int width, int height)
|
||||
public D3D11Texture2D(D3D11Resources resources, BindFlags bindFlags, ResourceUsage usage, CpuAccessFlags cpuAccessFlags, int width, int height, bool wrapped = false)
|
||||
{
|
||||
_resources = resources;
|
||||
_bindFlags = bindFlags;
|
||||
|
@ -37,6 +37,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
_cpuAccessFlags = cpuAccessFlags;
|
||||
Width = width;
|
||||
Height = height;
|
||||
IsUpsideDown = wrapped;
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
CreateTexture();
|
||||
Textures.Add(this);
|
||||
|
@ -50,8 +51,10 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
public virtual void CreateTexture()
|
||||
{
|
||||
// wrapped textures are R8G8B8A8 rather than B8G8R8A8...
|
||||
var format = IsUpsideDown ? Format.R8G8B8A8_UNorm : Format.B8G8R8A8_UNorm;
|
||||
Texture = Device.CreateTexture2D(
|
||||
Format.B8G8R8A8_UNorm,
|
||||
format,
|
||||
Width,
|
||||
Height,
|
||||
mipLevels: 1,
|
||||
|
@ -59,7 +62,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
usage: _usage,
|
||||
cpuAccessFlags: _cpuAccessFlags);
|
||||
|
||||
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, Format.B8G8R8A8_UNorm, mostDetailedMip: 0, mipLevels: 1);
|
||||
var srvd = new ShaderResourceViewDescription(ShaderResourceViewDimension.Texture2D, format, mostDetailedMip: 0, mipLevels: 1);
|
||||
SRV = Device.CreateShaderResourceView(Texture, srvd);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
|
||||
using Vortice.D3DCompiler;
|
||||
using Vortice.Direct3D;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.Direct3D11.Shader;
|
||||
using Vortice.DXGI;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
|
@ -39,6 +32,7 @@ namespace BizHawk.Bizware.Graphics
|
|||
private D3D11Pipeline CurPipeline => _resources.CurPipeline;
|
||||
|
||||
private D3D11SwapChain.SwapChainResources _controlSwapChain;
|
||||
private readonly D3D11GLInterop _glInterop;
|
||||
|
||||
public IGL_D3D11()
|
||||
{
|
||||
|
@ -49,6 +43,11 @@ namespace BizHawk.Bizware.Graphics
|
|||
|
||||
_resources = new();
|
||||
_resources.CreateResources();
|
||||
|
||||
if (D3D11GLInterop.IsAvailable)
|
||||
{
|
||||
_glInterop = new(_resources);
|
||||
}
|
||||
}
|
||||
|
||||
private IDXGISwapChain CreateDXGISwapChain(D3D11SwapChain.ControlParameters cp)
|
||||
|
@ -115,8 +114,10 @@ namespace BizHawk.Bizware.Graphics
|
|||
_controlSwapChain.Dispose();
|
||||
Context.Flush(); // important to immediately dispose of the swapchain (if it's still around, we can't recreate it)
|
||||
|
||||
_glInterop?.Dispose();
|
||||
_resources.DestroyResources();
|
||||
_resources.CreateResources();
|
||||
_glInterop?.OpenInteropDevice();
|
||||
|
||||
var swapChain = CreateDXGISwapChain(cp);
|
||||
var bbTex = swapChain.GetBuffer<ID3D11Texture2D>(0);
|
||||
|
@ -209,9 +210,8 @@ namespace BizHawk.Bizware.Graphics
|
|||
public ITexture2D CreateTexture(int width, int height)
|
||||
=> new D3D11Texture2D(_resources, BindFlags.ShaderResource, ResourceUsage.Dynamic, CpuAccessFlags.Write, width, height);
|
||||
|
||||
// not used for non-GL backends
|
||||
public ITexture2D WrapGLTexture2D(int glTexId, int width, int height)
|
||||
=> null;
|
||||
=> _glInterop?.WrapGLTexture(glTexId, width, height);
|
||||
|
||||
public Matrix4x4 CreateGuiProjectionMatrix(int width, int height)
|
||||
{
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
using static SDL2.SDL;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
|
@ -13,22 +11,6 @@ namespace BizHawk.Bizware.Graphics
|
|||
/// </summary>
|
||||
public static class OpenGLVersion
|
||||
{
|
||||
private readonly ref struct SavedOpenGLContext
|
||||
{
|
||||
private readonly IntPtr _sdlWindow, _glContext;
|
||||
|
||||
public SavedOpenGLContext()
|
||||
{
|
||||
_sdlWindow = SDL_GL_GetCurrentWindow();
|
||||
_glContext = SDL_GL_GetCurrentContext();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ = SDL_GL_MakeCurrent(_sdlWindow, _glContext);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IDictionary<int, bool> _glSupport = new Dictionary<int, bool>();
|
||||
|
||||
private static int PackGLVersion(int major, int minor)
|
||||
|
@ -61,4 +43,4 @@ namespace BizHawk.Bizware.Graphics
|
|||
=> _glSupport.GetValueOrPut(PackGLVersion(major, minor),
|
||||
static version => CheckVersion(version / 10, version % 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace BizHawk.Bizware.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper ref struct for tempoarily saving the current OpenGL context and restoring it
|
||||
/// </summary>
|
||||
public readonly ref struct SavedOpenGLContext
|
||||
{
|
||||
private readonly IntPtr _sdlWindow, _glContext;
|
||||
|
||||
public SavedOpenGLContext()
|
||||
{
|
||||
_sdlWindow = SDL_GL_GetCurrentWindow();
|
||||
_glContext = SDL_GL_GetCurrentContext();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _ = SDL_GL_MakeCurrent(_sdlWindow, _glContext);
|
||||
}
|
||||
}
|
|
@ -798,12 +798,13 @@ namespace BizHawk.Client.Common
|
|||
ITexture2D videoTexture = null;
|
||||
if (!simulate)
|
||||
{
|
||||
if (videoProvider is IGLTextureProvider glTextureProvider && _gl.DispMethodEnum == EDispMethod.OpenGL)
|
||||
if (videoProvider is IGLTextureProvider glTextureProvider)
|
||||
{
|
||||
// FYI: this is a million years from happening on n64, since it's all geriatric non-FBO code
|
||||
videoTexture = _gl.WrapGLTexture2D(glTextureProvider.GetGLTexture(), bufferWidth, bufferHeight);
|
||||
}
|
||||
else
|
||||
|
||||
if (videoTexture == null)
|
||||
{
|
||||
// wrap the VideoProvider data in a BitmapBuffer (no point to refactoring that many IVideoProviders)
|
||||
bb = new(bufferWidth, bufferHeight, videoProvider.GetVideoBuffer());
|
||||
|
@ -811,9 +812,6 @@ namespace BizHawk.Client.Common
|
|||
|
||||
//now, acquire the data sent from the videoProvider into a texture
|
||||
videoTexture = _videoTextureFrugalizer.Get(bb);
|
||||
|
||||
// lets not use this. lets define BizwareGL to make clamp by default (TBD: check opengl)
|
||||
// _gl.SetTextureWrapMode(videoTexture, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -446,7 +446,7 @@
|
|||
//
|
||||
this.label5.Location = new System.Drawing.Point(21, 123);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Text = " • May malfunction on some systems.\r\n • Will have increased performance for OpenGL" +
|
||||
this.label5.Text = " • May malfunction on some systems.\r\n • May have increased performance for OpenGL" +
|
||||
"-based emulation cores.\r\n • May have reduced performance on some systems.\r\n";
|
||||
//
|
||||
// tabControl1
|
||||
|
@ -667,7 +667,7 @@
|
|||
//
|
||||
this.label8.Location = new System.Drawing.Point(21, 30);
|
||||
this.label8.Name = "label8";
|
||||
this.label8.Text = " • Best compatibility\r\n • Decreased performance for OpenGL-based cores (NDS, 3DS)\r\n";
|
||||
this.label8.Text = " • Best compatibility\r\n • May have decreased performance for OpenGL-based cores (NDS, 3DS)\r\n";
|
||||
//
|
||||
// rbD3D11
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue