I've been learning my way about the DirectX API using SlimDX and the help of some useful resources like Frank D. Luna's book on programming with DirectX 11 and this site . (The book and site go very well together, especially for someone learning to use DirectX with C#).
Anyhow, I've recently finished chapter 6 of Luna's book and decided to try and make a personal DirectX framework of sorts for learning purposes, but now that I've got a sort of skeleton of the thing going, it throws weird errors at me occasionally or it just doesn't do what I want it to (display a cube in this case).
So, here it is:
Window.cs
[spoiler]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SlimDX;
using SlimDX.Windows;
using System.Drawing;
using System.Windows.Forms;
using SlimDX.Direct3D11;
using Device = SlimDX.Direct3D11.Device;
using Debug = System.Diagnostics.Debug;
using SlimDX.DXGI;
namespace MyFramework
{
public delegate void Renderer();
public delegate void Updater(float dt);
public delegate void Disposer();
public class Window : RenderForm
{
public Event windowsEvent;
public Renderer OnRender;
public Updater OnUpdate;
public Disposer OnDispose;
public Timer timer;
private Device device;
private SwapChain swapChain;
private DeviceContext immediateContext;
private RenderTargetView renderTargetView;
private DepthStencilView depthStencilView;
private Texture2D depthStencilBuffer;
private Viewport viewport;
private Rectangle scissorRect;
private Shader shader;
private EffectMatrixVariable fxWVP;
private Matrix proj;
private RasterizerStateDescription rsd;
private RasterizerState rs;
private int msaa4XQuality;
private bool enableMsaa4X;
private float viewportX;
private float viewportY;
private float viewportWidth;
private float viewportHeight;
public bool ToggleMsaa4XEnabled
{
get { return enableMsaa4X; }
set { enableMsaa4X = value; }
}
public bool ToggleWireframe
{
get
{
if (rsd.FillMode == FillMode.Wireframe)
return true;
else
return false;
}
set
{
if (value)
rsd.FillMode = FillMode.Wireframe;
else
rsd.FillMode = FillMode.Solid;
}
}
public bool ToggleScissorMode
{
get { return rsd.IsScissorEnabled; }
set { rsd.IsScissorEnabled = value; }
}
public Device Device
{
get { return device; }
}
public void SetScissorRect(int x, int y, int width, int height)
{
scissorRect = new Rectangle(x, y, width, height);
}
public Window(string title, Size size, float vX = 0, float vY = 0, float vWidth = -1, float vHeight = -1)
: base(title)
{
this.Size = size;
this.Icon = null;
viewportX = vX;
viewportY = vY;
viewportWidth = vWidth >= 0 ? vWidth : size.Width;
viewportHeight = vHeight >= 0 ? vHeight : size.Height;
Init();
}
private void Init()
{
device = null;
swapChain = null;
immediateContext = null;
renderTargetView = null;
depthStencilView = null;
depthStencilBuffer = null;
rs = null;
shader = null;
fxWVP = null;
msaa4XQuality = 0;
enableMsaa4X = true;
scissorRect = new Rectangle();
windowsEvent = new Event();
timer = new Timer();
rsd = new RasterizerStateDescription();
viewport = new Viewport();
OnRender += () => { };
OnUpdate += (dt) => { };
OnDispose += () => { };
this.ResizeBegin += (sender, args) =>
{
timer.Stop();
};
this.ResizeEnd += (sender, args) =>
{
timer.Start();
OnResize();
};
this.FormClosing += (sender, args) =>
{
DisposeWindow();
OnDispose();
Environment.Exit(0);
};
this.Show();
this.Update();
InitD3D();
}
private void InitD3D()
{
try
{
device = new Device(DriverType.Hardware, DeviceCreationFlags.None);
}
catch (Exception ex)
{
MessageBox.Show("D3D11Device creation failed\n" + ex.Message + "\n" + ex.StackTrace, "Error");
}
Debug.Assert((msaa4XQuality = device.CheckMultisampleQualityLevels(Format.R8G8B8A8_UNorm, 4)) > 0);
try
{
var description = new SwapChainDescription()
{
BufferCount = 1,
Usage = Usage.RenderTargetOutput,
OutputHandle = this.Handle,
IsWindowed = true,
ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
SampleDescription = enableMsaa4X ? new SampleDescription(4, msaa4XQuality - 1) : new SampleDescription(1, 0),
Flags = SwapChainFlags.AllowModeSwitch,
SwapEffect = SwapEffect.Discard
};
Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, description, out device, out swapChain);
immediateContext = device.ImmediateContext;
rsd = new RasterizerStateDescription()
{
FillMode = FillMode.Solid,
CullMode = CullMode.Back,
IsAntialiasedLineEnabled = true,
IsFrontCounterclockwise = false,
IsDepthClipEnabled = true,
IsScissorEnabled = false
};
rs = RasterizerState.FromDescription(device, rsd);
using (var factory = swapChain.GetParent<SlimDX.DXGI.Factory>())
factory.SetWindowAssociation(this.Handle, WindowAssociationFlags.IgnoreAltEnter);
this.KeyDown += (o, e) =>
{
if (e.Alt && e.KeyCode == Keys.Enter)
{
swapChain.IsFullScreen = !swapChain.IsFullScreen;
}
};
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n" + ex.StackTrace, "Error");
}
OnResize();
}
public void InitShader(string filePath)
{
shader = new Shader(ref device, filePath);
fxWVP = shader.fx.GetVariableByName("worldViewProj").AsMatrix();
}
public void InitCustomShader(string filePath, string[] inputElements, Format[] iEFormats, int[] iEStrides)
{
shader = new Shader(ref device, filePath, inputElements, iEFormats, iEStrides);
}
private void DisposeWindow()
{
Util.ReleaseCom(ref renderTargetView);
Util.ReleaseCom(ref depthStencilView);
Util.ReleaseCom(ref depthStencilBuffer);
if (shader != null) shader.Dispose();
if (immediateContext != null) immediateContext.ClearState();
if (swapChain.IsFullScreen) swapChain.SetFullScreenState(false, null);
Util.ReleaseCom(ref swapChain);
Util.ReleaseCom(ref immediateContext);
Util.ReleaseCom(ref device);
}
private void OnResize()
{
Util.ReleaseCom(ref renderTargetView);
Util.ReleaseCom(ref depthStencilView);
Util.ReleaseCom(ref depthStencilBuffer);
swapChain.ResizeBuffers(1, this.Width, this.Height, Format.R8G8B8A8_UNorm, SwapChainFlags.None);
using (var resource = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(swapChain, 0))
{
renderTargetView = new RenderTargetView(device, resource);
}
var depthStencilDesc = new Texture2DDescription()
{
Width = this.Width,
Height = this.Height,
MipLevels = 1,
ArraySize = 1,
Format = Format.D24_UNorm_S8_UInt,
SampleDescription = enableMsaa4X ? new SampleDescription(4, msaa4XQuality - 1) : new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
};
depthStencilBuffer = new Texture2D(device, depthStencilDesc);
depthStencilView = new DepthStencilView(device, depthStencilBuffer);
immediateContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);
viewport = new Viewport(viewportX, viewportY, viewportWidth, viewportHeight, 0.0f, 1.0f);
immediateContext.Rasterizer.SetViewports(viewport);
proj = Matrix.PerspectiveFovLH(0.25f * (float)Math.PI, this.Width / this.Height, 0.01f, 500.0f);
}
public Viewport Viewport
{
get { return viewport; }
set
{
viewportX = value.X;
viewportY = value.Y;
viewportWidth = value.Width;
viewportHeight = value.Height;
OnResize();
}
}
public void Run()
{
MessagePump.Run(() =>
{
timer.Tick();
OnRender();
OnUpdate(timer.DeltaTime);
});
}
public void ClearScreen(Color4 clearColor)
{
immediateContext.ClearRenderTargetView(renderTargetView, clearColor);
immediateContext.ClearDepthStencilView(depthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
immediateContext.Rasterizer.State = rs;
}
public void SetPrimitiveTobology(PrimitiveTopology topology)
{
immediateContext.InputAssembler.PrimitiveTopology = topology;
}
public void SwapBuffers()
{
swapChain.Present(0, PresentFlags.None);
}
public void Draw<T>(ref T entity) where T : Entity3D
{
immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(entity.vb, Vertex.stride, 0));
immediateContext.InputAssembler.SetIndexBuffer(entity.ib, Format.R32_UInt, 0);
if (shader != null)
{
Vector3 pos = new Vector3(3, 3, 3);
Vector3 target = new Vector3(0);
Vector3 up = new Vector3(0, 1, 0);
Matrix view = Matrix.LookAtLH(pos, target, up);
fxWVP.SetMatrix(entity.shapeWorld * view * proj);
for (int p = 0; p < shader.tech.Description.PassCount; p++)
{
shader.tech.GetPassByIndex(p).Apply(immediateContext);
immediateContext.DrawIndexed(entity.indexCount, 0, 0);
}
}
else
{
immediateContext.DrawIndexed(entity.indexCount, 0, 0);
}
}
protected override void WndProc(ref Message m)
{
windowsEvent.SetType(m);
base.WndProc(ref m);
}
}
}
[/spoiler]Sahder.cs
[spoiler]
using SlimDX.D3DCompiler;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Device = SlimDX.Direct3D11.Device;
namespace MyFramework
{
class Shader
{
public InputLayout inputLayout;
public Effect fx;
public EffectTechnique tech;
public Shader(ref Device device, string filePath, string[] inputElements, Format[] iEformats, int[] iEStrides)
{
CompileShaders(ref device, filePath);
CreateInputLayout(ref device, inputElements, iEformats, iEStrides);
}
public Shader(ref Device device, string filePath)
{
CompileShaders(ref device, filePath);
string[] inputElements = new string[] { "POSITION", "COLOR" };
Format[] iEformats = new Format[] { Format.R32G32B32_Float, Format.R32G32B32A32_Float };
int[] iEStrides = new int[] { 12 };
CreateInputLayout(ref device, inputElements, iEformats, iEStrides);
}
private void CompileShaders(ref Device device, string filePath)
{
string errors = null;
ShaderBytecode compiledShader = null;
try
{
compiledShader = ShaderBytecode.CompileFromFile(
filePath,
null,
"fx_5_0",
ShaderFlags.None,
EffectFlags.None,
null,
null,
out errors);
fx = new Effect(device, compiledShader);
}
catch (Exception ex)
{
if (!string.IsNullOrEmpty(errors))
{
MessageBox.Show(errors);
}
MessageBox.Show(ex.Message);
return;
}
finally
{
Util.ReleaseCom(ref compiledShader);
}
tech = fx.GetTechniqueByName("Tech");
}
private void CreateInputLayout(ref Device device, string[] inputElements, Format[] iEformats, int[] iEStrides)
{
InputElement[] vertexDesc = new InputElement[inputElements.Length];
for (int i = 0; i < vertexDesc.Length; i++)
{
vertexDesc[i] = new InputElement(inputElements[i], 0, iEformats[i], i > 0 ? iEStrides[i - 1] : 0, 0, InputClassification.PerVertexData, 0);
}
var passDesc = tech.GetPassByIndex(0).Description;
inputLayout = new InputLayout(device, passDesc.Signature, vertexDesc);
}
public void Dispose()
{
Util.ReleaseCom(ref inputLayout);
Util.ReleaseCom(ref fx);
}
}
}
[/spoiler]Entity3D.cs
[spoiler]
using SlimDX;
using SlimDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Buffer = SlimDX.Direct3D11.Buffer;
namespace MyFramework
{
public class Entity3D
{
public int vertexCount;
public int indexCount;
public Vertex[] vertices;
public uint[] indices;
public Matrix shapeWorld;
public Buffer vb;
public Buffer ib;
public Entity3D()
{
vertexCount = 0;
indexCount = 0;
vertices = null;
indices = null;
vb = null;
ib = null;
shapeWorld = Matrix.Identity;
}
public virtual void Dispose()
{
Util.ReleaseCom(ref vb);
Util.ReleaseCom(ref ib);
}
}
}
[/spoiler]Cube.cs
[spoiler]
using SlimDX;
using SlimDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Buffer = SlimDX.Direct3D11.Buffer;
namespace MyFramework
{
public class Cube : Entity3D
{
public Cube(Device device, float width, float height, float depth, Color4 color)
: base()
{
float x = 0.5f * width;
float y = 0.5f * height;
float z = 0.5f * depth;
vertexCount = 8;
indexCount = 36;
vertices = new Vertex[] {
new Vertex(-x,-y,-z, color), // 0
new Vertex( x,-y,-z, color), // 1
new Vertex( x, y,-z, color), // 2
new Vertex(-x, y,-z, color), // 3
new Vertex(-x,-y, z, color), // 4
new Vertex( x,-y, z, color), // 5
new Vertex( x, y, z, color), // 6
new Vertex(-x, y, z, color), // 7
};
indices = new uint[] {
0,3,2,
0,2,1,
2,6,1,
6,5,1,
6,7,5,
7,4,5,
7,3,4,
3,0,4,
7,6,3,
3,6,2,
0,1,4,
1,5,4
};
BufferDescription vbd = new BufferDescription(Vertex.stride * vertexCount, ResourceUsage.Immutable, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
vb = new Buffer(device, new DataStream(vertices, false, false), vbd);
BufferDescription ibd = new BufferDescription(sizeof(uint) * indexCount, ResourceUsage.Immutable, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
ib = new Buffer(device, new DataStream(indices, false, false), ibd);
}
}
}
[/spoiler]Vertex.cs
[spoiler]
using SlimDX;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFramework
{
public struct Vertex
{
public Vector3 position;
public Color4 color;
public const int stride = 28;
public Vertex(Vector3 pos, Color4 col)
{
position = pos;
color = col;
}
public Vertex(float x, float y, float z, Color4 col)
{
position = new Vector3(x, y, z);
color = col;
}
}
}
[/spoiler]Program.cs //Main entry point
[spoiler]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MyFramework;
using System.Drawing;
using SlimDX;
namespace FrameworkTester
{
class Program
{
static void Main(string[] args)
{
SlimDX.Configuration.EnableObjectTracking = true;
Window window = new Window("Test", new Size(1280, 720));
window.InitShader("FX/shader.fx");
window.OnUpdate += (dt) => {
};
Cube cube = new Cube(window.Device, 0.5f, 0.5f, 0.5f, Color.White);
window.OnRender += () => {
window.ClearScreen(Color.CornflowerBlue);
window.SetPrimitiveTobology(SlimDX.Direct3D11.PrimitiveTopology.TriangleList);
window.Draw(ref cube);
window.SwapBuffers();
};
window.OnDispose += () => {
cube.Dispose();
};
window.Run();
}
}
}
[/spoiler]And lastly, my shader, "shader.fx"
[spoiler]
cbuffer wvpm{
float4x4 worldViewProj;
};
struct VertexIn {
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut {
float4 PosH : SV_POSITION;
float4 Color: COLOR;
};
RasterizerState rsWireframe{
FillMode = Wireframe;
CullMode = Back;
FrontCounterClockwise = false;
};
VertexOut VS(VertexIn vin){
VertexOut vout;
vout.PosH = mul(float4(vin.PosL, 1.0f), worldViewProj);
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) :SV_Target{
return pin.Color;
}
technique11 Tech {
pass P0{
SetVertexShader(CompileShader(vs_4_0, VS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_4_0, PS()));
}
}
[/spoiler]I'd like to leave you with a screenshot of what I get when I run Program.cs:
Also, please don't bash the hard-coded stuff too much, everything so far is mostly just placeholder code for me to get my bearings.