Skip to content

Support for multi-adapter D3D + fix closing lid issue #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions src/GLWpfControl/AdapterMonitorNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace OpenTK.Wpf
{

[Serializable]
class AdapterMonitorNotFoundException : Exception
{
public AdapterMonitorNotFoundException() { }
public AdapterMonitorNotFoundException(string message) : base(message) { }
public AdapterMonitorNotFoundException(string message, Exception inner) : base(message, inner) { }
protected AdapterMonitorNotFoundException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
}
108 changes: 65 additions & 43 deletions src/GLWpfControl/DXGLContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
Expand All @@ -18,16 +20,16 @@ internal sealed class DxGlContext : IDisposable {

/// The directX context. This is basically the root of all DirectX state.
public IntPtr DxContextHandle { get; }

/// The directX device handle. This is the graphics card we're running on.
public IntPtr DxDeviceHandle { get; }

public D3DDevice Device { get; private set; }

private readonly uint _adapterCount;

private readonly List<D3DDevice> _devices;

/// The OpenGL Context. This is basically the root of all OpenGL state.
public IGraphicsContext GraphicsContext { get; }

/// An OpenGL handle to the DirectX device. Created and used by the WGL_dx_interop extension.
public IntPtr GlDeviceHandle { get; }

/// The shared context we (may) want to lazily create/use.
private static IGraphicsContext _sharedContext;
private static GLWpfControlSettings _sharedContextSettings;
Expand All @@ -38,49 +40,27 @@ internal sealed class DxGlContext : IDisposable {


public DxGlContext([NotNull] GLWpfControlSettings settings) {
DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle);
DxContextHandle = dxContextHandle;

var deviceParameters = new PresentationParameters
{
Windowed = 1,
SwapEffect = SwapEffect.Discard,
DeviceWindowHandle = IntPtr.Zero,
PresentationInterval = 0,
BackBufferFormat = Format.X8R8G8B8, // this is like A8 R8 G8 B8, but avoids issues with Gamma correction being applied twice.
BackBufferWidth = 1,
BackBufferHeight = 1,
AutoDepthStencilFormat = Format.Unknown,
BackBufferCount = 1,
EnableAutoDepthStencil = 0,
Flags = 0,
FullScreen_RefreshRateInHz = 0,
MultiSampleQuality = 0,
MultiSampleType = MultisampleType.None
};

DXInterop.CreateDeviceEx(
dxContextHandle,
0,
DeviceType.HAL, // use hardware rasterization
IntPtr.Zero,
CreateFlags.HardwareVertexProcessing |
CreateFlags.Multithreaded |
CreateFlags.PureDevice,
ref deviceParameters,
IntPtr.Zero,
out var dxDeviceHandle);
DxDeviceHandle = dxDeviceHandle;


// if the graphics context is null, we use the shared context.
if (settings.ContextToUse != null) {
if (settings.ContextToUse != null)
{
GraphicsContext = settings.ContextToUse;
}
else {
else
{
GraphicsContext = GetOrCreateSharedOpenGLContext(settings);
}

GlDeviceHandle = Wgl.DXOpenDeviceNV(dxDeviceHandle);
DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle);
DxContextHandle = dxContextHandle;

_adapterCount = DXInterop.GetAdapterCount(dxContextHandle);

_devices = Enumerable.Range(0, (int)_adapterCount)
.Select(i => D3DDevice.CreateDevice(dxContextHandle, i))
.ToList();

Device = _devices.First();
}

private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSettings settings) {
Expand Down Expand Up @@ -130,7 +110,49 @@ private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSetti
return _sharedContext;
}

public void SetDeviceFromMonitor(IntPtr monitor)
{
// Keep the default adapter device if monitor is null.
// In this (worst and unlikely) case, nothing will be drawn, but it won't lead in
// NullReference exceptions for null devices.
if(monitor == IntPtr.Zero)
{
Device = _devices[0];
return;
}

D3DDevice dev = null;

for (int i = 0; i < _adapterCount; i++)
{
var d3dMonitor = DXInterop.GetAdapterMonitor(DxContextHandle, (uint)i);
if (d3dMonitor == monitor)
{
dev = _devices[i];
break;
}
}

if (dev == null)
{
// This can happen when the control runs on a laptop with an external display in duplicated mode
// and the user closes the lid (see issue #39).
// In this particular case, only recreating the context will be useful.

throw new AdapterMonitorNotFoundException("Adapter was not found for given monitor handle");
}

Device = dev;
}

public void Dispose() {

Device = null;
foreach(var dev in _devices)
{
dev.Dispose();
}

// we only dispose of the graphics context if we're using the shared one.
if (ReferenceEquals(_sharedContext, GraphicsContext)) {
if (Interlocked.Decrement(ref _sharedContextReferenceCount) == 0) {
Expand Down
23 changes: 17 additions & 6 deletions src/GLWpfControl/DxGLFramebuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace OpenTK.Wpf {
/// Prior to releasing references.
internal sealed class DxGLFramebuffer : IDisposable {

private DxGlContext DxGlContext { get; }
public D3DDevice Device { get; }

/// The width of this buffer in pixels
public int FramebufferWidth { get; }
Expand Down Expand Up @@ -46,23 +46,30 @@ internal sealed class DxGLFramebuffer : IDisposable {
/// Specific wgl_dx_interop handle that marks the framebuffer as ready for interop.
public IntPtr DxInteropRegisteredHandle { get; }

public double DpiScaleX { get; }

public double DpiScaleY { get; }


public D3DImage D3dImage { get; }

public TranslateTransform TranslateTransform { get; }
public ScaleTransform FlipYTransform { get; }


public DxGLFramebuffer([NotNull] DxGlContext context, int width, int height, double dpiScaleX, double dpiScaleY) {
DxGlContext = context;
public DxGLFramebuffer([NotNull] D3DDevice device, int width, int height, double dpiScaleX, double dpiScaleY) {
Device = device;
Width = width;
Height = height;
DpiScaleX = dpiScaleX;
DpiScaleY = dpiScaleY;

FramebufferWidth = (int)Math.Ceiling(width * dpiScaleX);
FramebufferHeight = (int)Math.Ceiling(height * dpiScaleY);

var dxSharedHandle = IntPtr.Zero; // Unused windows-vista legacy sharing handle. Must always be null.
DXInterop.CreateRenderTarget(
context.DxDeviceHandle,
device.Handle,
FramebufferWidth,
FramebufferHeight,
Format.X8R8G8B8,// this is like A8 R8 G8 B8, but avoids issues with Gamma correction being applied twice.
Expand All @@ -80,7 +87,7 @@ public DxGLFramebuffer([NotNull] DxGlContext context, int width, int height, dou
GLSharedTextureHandle = GL.GenTexture();

var genHandle = Wgl.DXRegisterObjectNV(
context.GlDeviceHandle,
device.GLDeviceHandle,
dxRenderTargetHandle,
(uint)GLSharedTextureHandle,
(uint)TextureTarget.Texture2D,
Expand Down Expand Up @@ -118,8 +125,12 @@ public void Dispose() {
GL.DeleteFramebuffer(GLFramebufferHandle);
GL.DeleteRenderbuffer(GLDepthRenderBufferHandle);
GL.DeleteTexture(GLSharedTextureHandle);
Wgl.DXUnregisterObjectNV(DxGlContext.GlDeviceHandle, DxInteropRegisteredHandle);
Wgl.DXUnregisterObjectNV(Device.GLDeviceHandle, DxInteropRegisteredHandle);
DXInterop.Release(DxRenderTargetHandle);

D3dImage.Lock();
D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero);
D3dImage.Unlock();
}
}
}
24 changes: 23 additions & 1 deletion src/GLWpfControl/GLWpfControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using JetBrains.Annotations;

namespace OpenTK.Wpf
Expand Down Expand Up @@ -73,6 +74,8 @@ public bool RenderContinuously {

private TimeSpan _lastRenderTime = TimeSpan.FromSeconds(-1);

private DispatcherTimer _controlLocationCheckTimer;

/// <summary>
/// Used to create a new control. Before rendering can take place, <see cref="Start(GLWpfControlSettings)"/> must be called.
/// </summary>
Expand Down Expand Up @@ -103,9 +106,27 @@ public void Start(GLWpfControlSettings settings)
InvalidateVisual();
};
Unloaded += (a, b) => OnUnloaded();

_controlLocationCheckTimer = new DispatcherTimer()
{
Interval = TimeSpan.FromMilliseconds(500)
};
_controlLocationCheckTimer.Tick += OnLocationCheckTimerTick;
_controlLocationCheckTimer.Start();

Ready?.Invoke();
}


private void OnLocationCheckTimerTick(object sender, EventArgs e)
{
var presentationSource = PresentationSource.FromVisual(this);

if(presentationSource != null)
{
_renderer?.SetMonitorFromPoint(PointToScreen(new Point(0, 0)));
}
}

private void SetupRenderSize() {
if (_renderer == null || _settings == null) {
return;
Expand All @@ -125,6 +146,7 @@ private void SetupRenderSize() {
dpiScaleY = transformToDevice.M22;
}
}

_renderer?.SetSize((int) RenderSize.Width, (int) RenderSize.Height, dpiScaleX, dpiScaleY);
}

Expand Down
Loading