Skip to content
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