using System;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media;
using Intromat.Graphics;
using SharpDX.DXGI;
using Splat;
using D3D9 = SharpDX.Direct3D9;
using D3D11 = SharpDX.Direct3D11;
namespace Intromat.Views
{
///
/// Interaction logic for DxView.xaml
///
public partial class DxView
{
private readonly D3DImage _d3dImage;
private readonly DxHost _dxHost;
private int _backBufferHeight = -1;
private bool _backBufferMultiSample;
private D3D11.Texture2D? _backBufferTexture;
private D3D11.RenderTargetView? _backBufferView;
private int _backBufferWidth = -1;
private D3D9.Surface? _d3d9Surface;
private D3D9.Texture? _d3d9Texture;
private D3D11.Texture2D? _depthBufferTexture;
private D3D11.DepthStencilView? _depthBufferView;
private TimeSpan _lastRender;
private volatile IntPtr _nextFrame = IntPtr.Zero;
private bool _supportRemoteDesktop;
private D3D11.Texture2D? _transferBuffer;
private D3D11.Texture2D? _transferBuffer2;
private D3D11.Query? _transferCompleteQuery;
private DxHost.RenderDelegate? _updateHandler;
public DxView()
{
InitializeComponent();
_dxHost = Locator.Current.GetService()!;
_d3dImage = new D3DImage();
_swapChainImage.Source = _d3dImage;
SizeChanged += HostSizeChanged;
Loaded += OnLoaded;
Unloaded += OnUnloaded;
Dispatcher.ShutdownStarted += OnUnloaded;
}
public int ViewHeight { get; private set; }
public int ViewWidth { get; private set; }
private void OnLoaded(object? sender, RoutedEventArgs e)
{
CompositionTarget.Rendering += CompositionTargetRendering;
CheckHostSize();
}
private void OnUnloaded(object? sender, EventArgs e)
{
CompositionTarget.Rendering -= CompositionTargetRendering;
}
public void SetUpdateHandler(DxHost.RenderDelegate? handler)
{
_updateHandler = handler;
}
private void CompositionTargetRendering(object? sender, EventArgs e)
{
lock (this)
{
var args = (RenderingEventArgs)e;
if (_lastRender == args.RenderingTime)
return;
_lastRender = args.RenderingTime;
var nextFrame = _nextFrame;
if (nextFrame != IntPtr.Zero)
{
_nextFrame = IntPtr.Zero;
var image = _d3dImage;
var hasFrontBuffer = image.IsFrontBufferAvailable;
if (_supportRemoteDesktop || hasFrontBuffer)
{
image.Lock();
image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, nextFrame, _supportRemoteDesktop);
var targetWidth = image.PixelWidth;
var targetHeight = image.PixelHeight;
image.AddDirtyRect(new Int32Rect(0, 0, targetWidth, targetHeight));
image.Unlock();
if (hasFrontBuffer)
_supportRemoteDesktop = false;
}
else
{
_supportRemoteDesktop = SystemInformation.TerminalServerSession;
}
}
}
}
public void Render()
{
_dxHost.EnqueueAction(Render);
}
public void Render(D3D11.Device device, D3D11.DeviceContext context)
{
lock (this)
{
EnsureBuffers();
if (_backBufferTexture == null || _transferBuffer == null || _updateHandler == null)
return;
context.OutputMerger.SetTargets(_depthBufferView, _backBufferView);
context.Rasterizer.SetViewport(0, 0, ViewWidth, ViewHeight);
try
{
_updateHandler(device, context);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
if (_backBufferMultiSample)
{
context.ResolveSubresource(_backBufferTexture, 0, _transferBuffer2, 0, Format.B8G8R8A8_UNorm_SRgb);
context.CopyResource(_transferBuffer2, _transferBuffer);
}
else
{
context.CopyResource(_backBufferTexture, _transferBuffer);
}
context.End(_transferCompleteQuery);
while (!context.GetData(_transferCompleteQuery, out int completed) || completed == 0)
Thread.Yield();
_nextFrame = _d3d9Surface!.NativePointer;
}
}
private void HostSizeChanged(object sender, SizeChangedEventArgs e)
{
CheckHostSize();
}
private void CheckHostSize()
{
lock (this)
{
double scaleX = 1.0f;
double scaleY = 1.0f;
if (PresentationSource.FromVisual(this)?.CompositionTarget is HwndTarget hwndTarget)
{
scaleX = hwndTarget.TransformToDevice.M11;
scaleY = hwndTarget.TransformToDevice.M22;
}
var width = (int)Math.Ceiling(ActualWidth);
var height = (int)Math.Ceiling(ActualHeight);
width = width > 1 ? width : 1;
height = height > 1 ? height : 1;
if (ViewWidth == width && ViewHeight == height)
return;
ViewWidth = width;
ViewHeight = height;
Render();
}
}
private void EnsureBuffers()
{
lock (this)
{
if (_backBufferHeight != ViewHeight || _backBufferWidth != ViewWidth)
{
_backBufferHeight = ViewHeight;
_backBufferWidth = ViewWidth;
if (_d3d9Surface != null)
{
_d3d9Surface.Dispose();
_d3d9Surface = null;
}
if (_d3d9Texture != null)
{
_d3d9Texture.Dispose();
_d3d9Texture = null;
}
if (_transferBuffer != null)
{
_transferBuffer.Dispose();
_transferBuffer = null;
}
if (_transferBuffer2 != null)
{
_transferBuffer2.Dispose();
_transferBuffer2 = null;
}
if (_backBufferTexture != null) // TODO: test
{
_backBufferTexture.Dispose();
_backBufferTexture = null;
}
if (_backBufferView != null)
{
_backBufferView.Dispose();
_backBufferView = null;
}
if (_depthBufferTexture != null) // TODO: test
{
_depthBufferTexture.Dispose();
_depthBufferTexture = null;
}
if (_depthBufferView != null)
{
_depthBufferView.Dispose();
_depthBufferView = null;
}
if (_transferCompleteQuery != null)
{
_transferCompleteQuery.Dispose();
_transferCompleteQuery = null;
}
}
if (_transferBuffer == null && _dxHost.D3D9Device != null)
{
var device = _dxHost.Device;
var textureDesc = new D3D11.Texture2DDescription
{
ArraySize = 1,
MipLevels = 1,
Width = _backBufferWidth,
Height = _backBufferHeight,
SampleDescription = new SampleDescription(1, 0), // TAM
Usage = D3D11.ResourceUsage.Default,
CpuAccessFlags = D3D11.CpuAccessFlags.None,
OptionFlags = D3D11.ResourceOptionFlags.Shared,
BindFlags = D3D11.BindFlags.RenderTarget | D3D11.BindFlags.ShaderResource,
Format = Format.B8G8R8A8_UNorm
};
_transferBuffer = new D3D11.Texture2D(device, textureDesc);
textureDesc.Format = Format.B8G8R8A8_UNorm_SRgb;
textureDesc.OptionFlags = D3D11.ResourceOptionFlags.None;
_transferBuffer2 = new D3D11.Texture2D(device, textureDesc);
_backBufferMultiSample = true;
textureDesc.SampleDescription = new SampleDescription(4, 0);
textureDesc.BindFlags = D3D11.BindFlags.RenderTarget;
_backBufferTexture = new D3D11.Texture2D(device, textureDesc);
_backBufferView = new D3D11.RenderTargetView(device, _backBufferTexture);
textureDesc.Format = Format.R32_Typeless;
textureDesc.BindFlags = D3D11.BindFlags.DepthStencil;
textureDesc.OptionFlags = D3D11.ResourceOptionFlags.None;
var depthDesc = new D3D11.DepthStencilViewDescription
{
Format = Format.D32_Float,
Flags = D3D11.DepthStencilViewFlags.None,
Dimension = D3D11.DepthStencilViewDimension.Texture2DMultisampled,
Texture2DMS = new D3D11.DepthStencilViewDescription.Texture2DMultisampledResource()
};
_depthBufferTexture = new D3D11.Texture2D(device, textureDesc);
_depthBufferView = new D3D11.DepthStencilView(device, _depthBufferTexture, depthDesc);
var dxgiRes = _transferBuffer.QueryInterface();
var handle = dxgiRes.SharedHandle;
_d3d9Texture = new D3D9.Texture(_dxHost.D3D9Device, _backBufferWidth, _backBufferHeight, 1, D3D9.Usage.RenderTarget, D3D9.Format.A8R8G8B8, D3D9.Pool.Default, ref handle);
_d3d9Surface = _d3d9Texture.GetSurfaceLevel(0);
var queryDesc = new D3D11.QueryDescription { Type = D3D11.QueryType.Event };
_transferCompleteQuery = new D3D11.Query(device, queryDesc);
}
}
}
}
}