// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 #if USE_DX && !ANDROID using System; using System.Drawing; using System.Drawing.Imaging; using System.Threading; using OpenTK; using SharpDX; using SharpDX.Direct3D9; using D3D = SharpDX.Direct3D9; using WinWindowInfo = OpenTK.Platform.Windows.WinWindowInfo; namespace ClassicalSharp.GraphicsAPI { /// Implements IGraphicsAPI using Direct3D 9. public class Direct3D9Api : IGraphicsApi { Device device; Direct3D d3d; Capabilities caps; const int texBufferSize = 512, iBufferSize = 32, vBufferSize = 2048; D3D.Texture[] textures = new D3D.Texture[texBufferSize]; DataBuffer[] vBuffers = new DataBuffer[vBufferSize]; DataBuffer[] iBuffers = new DataBuffer[iBufferSize]; MatrixStack viewStack, projStack, texStack, curStack; PrimitiveType[] modeMappings; Format[] depthFormats, viewFormats; Format depthFormat, viewFormat; CreateFlags createFlags = CreateFlags.HardwareVertexProcessing; public Direct3D9Api(Game game) { MinZNear = 0.05f; IntPtr windowHandle = ((WinWindowInfo)game.window.WindowInfo).WindowHandle; d3d = new Direct3D(); int adapter = d3d.Adapters[0].Adapter; InitFields(); FindCompatibleFormat(adapter); PresentParameters args = GetPresentArgs(640, 480); try { device = d3d.CreateDevice(adapter, DeviceType.Hardware, windowHandle, createFlags, args); } catch (SharpDXException) { createFlags = CreateFlags.MixedVertexProcessing; try { device = d3d.CreateDevice(adapter, DeviceType.Hardware, windowHandle, createFlags, args); } catch (SharpDXException) { createFlags = CreateFlags.SoftwareVertexProcessing; device = d3d.CreateDevice(adapter, DeviceType.Hardware, windowHandle, createFlags, args); } } caps = device.Capabilities; viewStack = new MatrixStack(32, device, TransformState.View); projStack = new MatrixStack(4, device, TransformState.Projection); texStack = new MatrixStack(4, device, TransformState.Texture0); SetDefaultRenderStates(); InitDynamicBuffers(); } void FindCompatibleFormat(int adapter) { for (int i = 0; i < viewFormats.Length; i++) { viewFormat = viewFormats[i]; if (d3d.CheckDeviceType(adapter, DeviceType.Hardware, viewFormat, viewFormat, true)) break; if (i == viewFormats.Length - 1) throw new InvalidOperationException("Unable to create a back buffer with sufficient precision."); } for (int i = 0; i < depthFormats.Length; i++) { depthFormat = depthFormats[i]; if (d3d.CheckDepthStencilMatch(adapter, DeviceType.Hardware, viewFormat, viewFormat, depthFormat)) break; if (i == depthFormats.Length - 1) throw new InvalidOperationException("Unable to create a depth buffer with sufficient precision."); } } bool alphaTest, alphaBlend; public override bool AlphaTest { set { if (value == alphaTest) return; alphaTest = value; device.SetRenderState(RenderState.AlphaTestEnable, value); } } public override bool AlphaBlending { set { if (value == alphaBlend) return; alphaBlend = value; device.SetRenderState(RenderState.AlphaBlendEnable, value); } } Compare[] compareFuncs; Compare alphaTestFunc; int alphaTestRef; public override void AlphaTestFunc(CompareFunc func, float value) { alphaTestFunc = compareFuncs[(int)func]; device.SetRenderState(RenderState.AlphaFunc, (int)alphaTestFunc); alphaTestRef = (int)(value * 255); device.SetRenderState(RenderState.AlphaRef, alphaTestRef); } Blend[] blendFuncs; Blend srcBlendFunc, dstBlendFunc; public override void AlphaBlendFunc(BlendFunc srcFunc, BlendFunc dstFunc) { srcBlendFunc = blendFuncs[(int)srcFunc]; dstBlendFunc = blendFuncs[(int)dstFunc]; device.SetRenderState(RenderState.SourceBlend, (int)srcBlendFunc); device.SetRenderState(RenderState.DestinationBlend, (int)dstBlendFunc); } bool fogEnable; public override bool Fog { set { if (value == fogEnable) return; fogEnable = value; device.SetRenderState(RenderState.FogEnable, value); } } int fogCol, lastFogCol = FastColour.BlackPacked; public override void SetFogColour(FastColour col) { fogCol = col.ToArgb(); if (fogCol == lastFogCol) return; device.SetRenderState(RenderState.FogColor, fogCol); lastFogCol = fogCol; } float fogDensity = -1, fogStart = -1, fogEnd = -1; public override void SetFogDensity(float value) { if (value == fogDensity) return; fogDensity = value; device.SetRenderState(RenderState.FogDensity, value); } public override void SetFogStart(float value) { device.SetRenderState(RenderState.FogStart, value); } public override void SetFogEnd(float value) { if (value == fogEnd) return; fogEnd = value; device.SetRenderState(RenderState.FogEnd, value); } FogMode[] modes; FogMode fogTableMode; public override void SetFogMode(Fog fogMode) { FogMode newMode = modes[(int)fogMode]; if (newMode == fogTableMode) return; fogTableMode = newMode; device.SetRenderState(RenderState.FogTableMode, (int)fogTableMode); } public override bool FaceCulling { set { Cull mode = value ? Cull.Clockwise : Cull.None; device.SetRenderState(RenderState.CullMode, (int)mode); } } public override int MaxTextureDimensions { get { return Math.Min(caps.MaxTextureHeight, caps.MaxTextureWidth); } } public override bool Texturing { set { if (!value) device.SetTexture(0, null); } } protected override int CreateTexture(int width, int height, IntPtr scan0, bool managedPool) { D3D.Texture texture = null; if (managedPool) { texture = device.CreateTexture(width, height, 0, Usage.None, Format.A8R8G8B8, Pool.Managed); texture.SetData(0, LockFlags.None, scan0, width * height * 4); } else { D3D.Texture sys = device.CreateTexture(width, height, 0, Usage.None, Format.A8R8G8B8, Pool.SystemMemory); sys.SetData(0, LockFlags.None, scan0, width * height * 4); texture = device.CreateTexture(width, height, 0, Usage.None, Format.A8R8G8B8, Pool.Default); device.UpdateTexture(sys, texture); sys.Dispose(); } return GetOrExpand(ref textures, texture, texBufferSize); } public override void UpdateTexturePart(int texId, int texX, int texY, FastBitmap part) { D3D.Texture texture = textures[texId]; texture.SetPartData(0, LockFlags.None, part.Scan0, texX, texY, part.Width, part.Height); } public override void BindTexture(int texId) { device.SetTexture(0, textures[texId]); } public override void DeleteTexture(ref int texId) { Delete(textures, ref texId); } int lastClearCol; public override void Clear() { device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, lastClearCol, 1f, 0); } public override void ClearColour(FastColour col) { lastClearCol = col.ToArgb(); } public override bool ColourWrite { set { device.SetRenderState(RenderState.ColorWriteEnable, value ? 0xF : 0x0); } } Compare depthTestFunc; public override void DepthTestFunc(CompareFunc func) { depthTestFunc = compareFuncs[(int)func]; device.SetRenderState(RenderState.ZFunc, (int)depthTestFunc); } bool depthTest, depthWrite; public override bool DepthTest { set { depthTest = value; device.SetRenderState(RenderState.ZEnable, value); } } public override bool DepthWrite { set { depthWrite = value; device.SetRenderState(RenderState.ZWriteEnable, value); } } public override bool AlphaArgBlend { set { TextureOp op = value ? TextureOp.Modulate : TextureOp.SelectArg1; device.SetTextureStageState(0, TextureStage.AlphaOperation, (int)op); } } #region Vertex buffers public override int CreateDynamicVb(VertexFormat format, int maxVertices) { int size = maxVertices * strideSizes[(int)format]; DataBuffer buffer = device.CreateVertexBuffer(size, Usage.Dynamic, formatMapping[(int)format], Pool.Default); return GetOrExpand(ref vBuffers, buffer, iBufferSize); } public override void SetDynamicVbData(int vb, T[] vertices, int count) { int size = count * batchStride; DataBuffer buffer = vBuffers[vb]; buffer.SetData(vertices, size, LockFlags.Discard); device.SetStreamSource(0, buffer, 0, batchStride); } D3D.VertexFormat[] formatMapping; public override int CreateVb(T[] vertices, VertexFormat format, int count) { int size = count * strideSizes[(int)format]; DataBuffer buffer = device.CreateVertexBuffer(size, Usage.None, formatMapping[(int)format], Pool.Default); buffer.SetData(vertices, size, LockFlags.None); return GetOrExpand(ref vBuffers, buffer, vBufferSize); } public override int CreateVb(IntPtr vertices, VertexFormat format, int count) { int size = count * strideSizes[(int)format]; DataBuffer buffer = device.CreateVertexBuffer(size, Usage.None, formatMapping[(int)format], Pool.Default); buffer.SetData(vertices, size, LockFlags.None); return GetOrExpand(ref vBuffers, buffer, vBufferSize); } public override int CreateIb(IntPtr indices, int indicesCount) { int size = indicesCount * sizeof(ushort); DataBuffer buffer = device.CreateIndexBuffer(size, Usage.None, Format.Index16, Pool.Managed); buffer.SetData(indices, size, LockFlags.None); return GetOrExpand(ref iBuffers, buffer, iBufferSize); } public override void DeleteVb(ref int vb) { Delete(vBuffers, ref vb); } public override void DeleteIb(ref int ib) { Delete(iBuffers, ref ib); } int batchStride; public override void SetBatchFormat(VertexFormat format) { device.SetVertexFormat(formatMapping[(int)format]); batchStride = strideSizes[(int)format]; } public override void BindVb(int vb) { device.SetStreamSource(0, vBuffers[vb], 0, batchStride); } public override void BindIb(int ib) { device.SetIndices(iBuffers[ib]); } public override void DrawVb(DrawMode mode, int startVertex, int verticesCount) { device.DrawPrimitives(modeMappings[(int)mode], startVertex, NumPrimitives(verticesCount, mode)); } public override void DrawIndexedVb(DrawMode mode, int indicesCount, int startIndex) { device.DrawIndexedPrimitives(modeMappings[(int)mode], 0, startIndex / 6 * 4, indicesCount / 6 * 4, startIndex, NumPrimitives(indicesCount, mode)); } internal override void DrawIndexedVb_TrisT2fC4b(int indicesCount, int startIndex) { device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, startIndex / 6 * 4, indicesCount / 6 * 4, startIndex, indicesCount / 3); } internal override void DrawIndexedVb_TrisT2fC4b(int indicesCount, int startVertex, int startIndex) { device.DrawIndexedPrimitives(PrimitiveType.TriangleList, startVertex, 0, indicesCount / 6 * 4, startIndex, indicesCount / 3); } #endregion #region Matrix manipulation public override void SetMatrixMode(MatrixType mode) { if (mode == MatrixType.Modelview) { curStack = viewStack; } else if (mode == MatrixType.Projection) { curStack = projStack; } else if (mode == MatrixType.Texture) { curStack = texStack; } } public unsafe override void LoadMatrix(ref Matrix4 matrix) { if (curStack == texStack) { matrix.M31 = matrix.M41; // NOTE: this hack fixes the texture movements. device.SetTextureStageState(0, TextureStage.TextureTransformFlags, (int)TextureTransform.Count2); } curStack.SetTop(ref matrix); } Matrix4 identity = Matrix4.Identity; public override void LoadIdentityMatrix() { if (curStack == texStack) { device.SetTextureStageState(0, TextureStage.TextureTransformFlags, (int)TextureTransform.Disable); } curStack.SetTop(ref identity); } public override void PushMatrix() { curStack.Push(); } public override void PopMatrix() { curStack.Pop(); } public unsafe override void MultiplyMatrix(ref Matrix4 matrix) { curStack.MultiplyTop(ref matrix); } class MatrixStack { Matrix4[] stack; int stackIndex; Device device; TransformState matrixType; public MatrixStack(int capacity, Device device, TransformState matrixType) { stack = new Matrix4[capacity]; stack[0] = Matrix4.Identity; this.device = device; this.matrixType = matrixType; } public void Push() { stack[stackIndex + 1] = stack[stackIndex]; // mimic GL behaviour stackIndex++; // exact same, we don't need to update DirectX state. } public void SetTop(ref Matrix4 matrix) { stack[stackIndex] = matrix; device.SetTransform(matrixType, ref stack[stackIndex]); } public void MultiplyTop(ref Matrix4 matrix) { Matrix4.Mult(ref matrix, ref stack[stackIndex], out stack[stackIndex]); // top = matrix * top device.SetTransform(matrixType, ref stack[stackIndex]); } public void Pop() { stackIndex--; device.SetTransform(matrixType, ref stack[stackIndex]); } } #endregion public override void BeginFrame(Game game) { device.BeginScene(); } public override void EndFrame(Game game) { device.EndScene(); int code = device.Present(); if (code >= 0) return; if ((uint)code != (uint)Direct3DError.DeviceLost) throw new SharpDXException(code); // TODO: Make sure this actually works on all graphics cards. LoseContext(" (Direct3D9 device lost)"); LoopUntilRetrieved(); RecreateDevice(game); } void LoopUntilRetrieved() { ScheduledTask task = new ScheduledTask(); task.Interval = 1.0 / 20; task.Callback = LostContextFunction; while (true) { Thread.Sleep(50); uint code = (uint)device.TestCooperativeLevel(); if ((uint)code == (uint)Direct3DError.DeviceNotReset) return; task.Callback(task); } } bool vsync = false; public override void SetVSync(Game game, bool value) { vsync = value; game.VSync = value; LoseContext(" (toggling VSync)"); RecreateDevice(game); } public override void OnWindowResize(Game game) { LoseContext(" (resizing window)"); RecreateDevice(game); } void RecreateDevice(Game game) { PresentParameters args = GetPresentArgs(game.Width, game.Height); while ((uint)device.Reset(args) == (uint)Direct3DError.DeviceLost) LoopUntilRetrieved(); SetDefaultRenderStates(); RestoreRenderStates(); RecreateContext(); } void SetDefaultRenderStates() { FaceCulling = false; device.SetRenderState(RenderState.ColorVertex, false); device.SetRenderState(RenderState.Lighting, false); device.SetRenderState(RenderState.SpecularEnable, false); device.SetRenderState(RenderState.LocalViewer, false); device.SetRenderState(RenderState.DebugMonitorToken, false); } void RestoreRenderStates() { device.SetRenderState(RenderState.AlphaTestEnable, alphaTest); device.SetRenderState(RenderState.AlphaBlendEnable, alphaBlend); device.SetRenderState(RenderState.AlphaFunc, (int)alphaTestFunc); device.SetRenderState(RenderState.AlphaRef, alphaTestRef); device.SetRenderState(RenderState.SourceBlend, (int)srcBlendFunc); device.SetRenderState(RenderState.DestinationBlend, (int)dstBlendFunc); device.SetRenderState(RenderState.FogEnable, fogEnable); device.SetRenderState(RenderState.FogColor, fogCol); device.SetRenderState(RenderState.FogDensity, fogDensity); device.SetRenderState(RenderState.FogStart, fogStart); device.SetRenderState(RenderState.FogEnd, fogEnd); device.SetRenderState(RenderState.FogTableMode, (int)fogTableMode); device.SetRenderState(RenderState.ZFunc, (int)depthTestFunc); device.SetRenderState(RenderState.ZEnable, depthTest); device.SetRenderState(RenderState.ZWriteEnable, depthWrite); } PresentParameters GetPresentArgs(int width, int height) { PresentParameters args = new PresentParameters(); args.AutoDepthStencilFormat = depthFormat; args.BackBufferWidth = width; args.BackBufferHeight = height; args.BackBufferFormat = viewFormat; args.BackBufferCount = 1; args.EnableAutoDepthStencil = true; args.PresentationInterval = vsync ? PresentInterval.One : PresentInterval.Immediate; args.SwapEffect = SwapEffect.Discard; args.Windowed = true; return args; } static int GetOrExpand(ref T[] array, T value, int expSize) { // Find first free slot for (int i = 1; i < array.Length; i++) { if (array[i] == null) { array[i] = value; return i; } } // Otherwise resize and add more elements int oldLength = array.Length; Array.Resize(ref array, array.Length + expSize); array[oldLength] = value; return oldLength; } static void Delete(T[] array, ref int id) where T : class, IDisposable { if (id <= 0 || id >= array.Length) return; T value = array[id]; if (value != null) { value.Dispose(); } array[id] = null; id = -1; } static int NumPrimitives(int vertices, DrawMode mode) { return mode == DrawMode.Triangles ? vertices / 3 : vertices / 2; } protected unsafe override void LoadOrthoMatrix(float width, float height) { Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(0, width, height, 0, -10000, 10000); const float zN = -10000, zF = 10000; matrix.M33 = 1 / (zN - zF); matrix.M43 = zN / (zN - zF); matrix.M44 = 1; curStack.SetTop(ref matrix); } public override void Dispose() { base.Dispose(); device.Dispose(); d3d.Dispose(); } internal override void MakeApiInfo() { string adapter = d3d.Adapters[0].Details.Description; float texMem = device.AvailableTextureMemory / 1024f / 1024f; ApiInfo = new string[] { "-- Using Direct3D9 api --", "Adapter: " + adapter, "Mode: " + createFlags, "Texture memory: " + texMem + " MB", "Max 2D texture dimensions: " + MaxTextureDimensions, "Depth buffer format: " + depthFormat, "Back buffer format: " + viewFormat, }; } public override void TakeScreenshot(string output, int width, int height) { using (Surface backbuffer = device.GetBackBuffer(0, 0, BackBufferType.Mono), tempSurface = device.CreateOffscreenPlainSurface(width, height, Format.X8R8G8B8, Pool.SystemMemory)) { // For DX 8 use IDirect3DDevice8::CreateImageSurface device.GetRenderTargetData(backbuffer, tempSurface); LockedRectangle rect = tempSurface.LockRectangle(LockFlags.ReadOnly | LockFlags.NoDirtyUpdate); using (Bitmap bmp = new Bitmap(width, height, width * sizeof(int), PixelFormat.Format32bppRgb, rect.DataPointer)) { bmp.Save(output, ImageFormat.Png); } tempSurface.UnlockRectangle(); } } void InitFields() { // See comment in KeyMap() constructor for why this is necessary. modeMappings = new PrimitiveType[2]; modeMappings[0] = PrimitiveType.TriangleList; modeMappings[1] = PrimitiveType.LineList; depthFormats = new Format[6]; depthFormats[0] = Format.D32; depthFormats[1] = Format.D24X8; depthFormats[2] = Format.D24S8; depthFormats[3] = Format.D24X4S4; depthFormats[4] = Format.D16; depthFormats[5] = Format.D15S1; viewFormats = new Format[4]; viewFormats[0] = Format.X8R8G8B8; viewFormats[1] = Format.R8G8B8; viewFormats[2] = Format.R5G6B5; viewFormats[3] = Format.X1R5G5B5; blendFuncs = new Blend[6]; blendFuncs[0] = Blend.Zero; blendFuncs[1] = Blend.One; blendFuncs[2] = Blend.SourceAlpha; blendFuncs[3] = Blend.InverseSourceAlpha; blendFuncs[4] = Blend.DestinationAlpha; blendFuncs[5] = Blend.InverseDestinationAlpha; compareFuncs = new Compare[8]; compareFuncs[0] = Compare.Always; compareFuncs[1] = Compare.NotEqual; compareFuncs[2] = Compare.Never; compareFuncs[3] = Compare.Less; compareFuncs[4] = Compare.LessEqual; compareFuncs[5] = Compare.Equal; compareFuncs[6] = Compare.GreaterEqual; compareFuncs[7] = Compare.Greater; formatMapping = new D3D.VertexFormat[2]; formatMapping[0] = D3D.VertexFormat.Position | D3D.VertexFormat.Diffuse; formatMapping[1] = D3D.VertexFormat.Position | D3D.VertexFormat.Texture2 | D3D.VertexFormat.Diffuse; modes = new FogMode[3]; modes[0] = FogMode.Linear; modes[1] = FogMode.Exponential; modes[2] = FogMode.ExponentialSquared; } } } #endif