Building a SlimDX MiniTriangle sample with Direct3D11 and IronPython

I generally don't post huge code dumps, mainly because I find them more annoying and less helpful than some books/authors might. But you know, I've been playing with IronPython/SlimDX recently and decided to do up another SlimDX Sample (demonstrating DX11), except in IronPython this time. This will be in the SlimDX samples sometime soon!

import clr  
clr.AddReference('System.Windows.Forms')  
clr.AddReference('System.Drawing')  
clr.AddReference('SlimDX')

from System import *  
from System.Drawing import Size  
from System.Windows.Forms import Form, Application, MessageBox, FormBorderStyle  
from SlimDX import *  
from SlimDX.Direct3D11 import *  
from SlimDX.DXGI import SwapChainDescription, SwapChainFlags, ModeDescription, SampleDescription, Usage, SwapEffect, Format, PresentFlags, Factory, WindowAssociationFlags  
from SlimDX.D3DCompiler import *  
from SlimDX.Windows import MessagePump

class GameObject:  
    def Render(self):
        pass
    def Tick(self):
        pass

class GraphicsDevice(IDisposable):  
    Context = property(lambda self: self.context)
    Device = property(lambda self: self.device)
    SwapChain = property(lambda self: self.swapChain)

    def __init__(self, control, fullscreen):
        self.fullscreen = fullscreen
        self.control = control

        control.Resize += lambda sender, args: self.Resize()

        swapChainDesc = self.CreateSwapChainDescription();
        success,self.device,self.swapChain = Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, Array[FeatureLevel]([FeatureLevel.Level_11_0, FeatureLevel.Level_10_1, FeatureLevel.Level_10_0]), swapChainDesc)
        self.context = self.Device.ImmediateContext

        with self.swapChain.GetParent[Factory]() as factory:
            factory.SetWindowAssociation(self.control.Handle, WindowAssociationFlags.IgnoreAll)

        with Resource.FromSwapChain[Texture2D](self.swapChain, 0) as backBuffer:
            self.backBufferRTV = RenderTargetView(self.Device, backBuffer)

        self.Resize()        

    def CreateSwapChainDescription(self):
        swapChainDesc = SwapChainDescription()
        swapChainDesc.IsWindowed = not self.fullscreen
        swapChainDesc.BufferCount = 1
        swapChainDesc.ModeDescription = ModeDescription(self.control.ClientSize.Width, self.control.ClientSize.Height, Rational(60, 1), Format.R8G8B8A8_UNorm)
        swapChainDesc.Flags = SwapChainFlags.None
        swapChainDesc.SwapEffect = SwapEffect.Discard
        swapChainDesc.Usage = Usage.RenderTargetOutput
        swapChainDesc.SampleDescription = SampleDescription(1, 0)
        swapChainDesc.OutputHandle = self.control.Handle
        return swapChainDesc

    def Resize(self):
        self.Context.ClearState()
        self.backBufferRTV.Dispose()
        self.swapChain.ResizeBuffers(1, self.control.ClientSize.Width, self.control.ClientSize.Height, Format.R8G8B8A8_UNorm, SwapChainFlags.None)
        with Resource.FromSwapChain[Texture2D](self.swapChain, 0) as backBuffer:
            self.backBufferRTV = RenderTargetView(self.Device, backBuffer)
        self.Context.Rasterizer.SetViewports(Viewport(0, 0, self.control.ClientSize.Width, self.control.ClientSize.Height, 0.0, 1.0))

    def BeginRender(self):
        self.Context.ClearRenderTargetView(self.backBufferRTV, Color4(0, 0, 0, 0))
        self.Context.OutputMerger.SetTargets(self.backBufferRTV)


    def EndRender(self):
        self.swapChain.Present(0, PresentFlags.None)

    def Dispose(self):
        self.backBufferRTV.Dispose()
        self.swapChain.Dispose()
        self.device.Dispose()


class TriangleObject(GameObject):  
    def __init__(self, game):
        self.game = game
        device = game.GraphicsDevice.Device
        context = game.GraphicsDevice.Context

        err = clr.Reference[str]()
        with ShaderBytecode.CompileFromFile("SimpleTriangle10.fx", "fx_5_0", ShaderFlags.None, EffectFlags.None, None, None, err) as shaderByteCode:
            self.effect = Effect(device, shaderByteCode)

        shaderTechnique = self.effect.GetTechniqueByIndex(0)
        self.shaderPass = shaderTechnique.GetPassByIndex(0)

        sig = self.shaderPass.Description.Signature
        self.inputLayout = InputLayout(device, sig, Array[InputElement]([InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)]))

        bufferDesc = BufferDescription(3 * 32, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0)
        self.vertexBuffer = Buffer(device, bufferDesc)

        stream = context.MapSubresource(self.vertexBuffer, 0, 3 * 32, MapMode.WriteDiscard, MapFlags.None).Data
        data = Array[Vector4]([
            Vector4(0.0, 0.5, 0.5, 1.0), Vector4(1.0, 0.0, 0.0, 1.0),
            Vector4(0.5, -0.5, 0.5, 1.0), Vector4(0.0, 1.0, 0.0, 1.0),
            Vector4(-0.5, -0.5, 0.5, 1.0), Vector4(0.0, 0.0, 1.0, 1.0)
        ])
        stream.WriteRange(data)
        context.UnmapSubresource(self.vertexBuffer, 0)

    def Render(self):
        context = self.game.GraphicsDevice.Context
        context.InputAssembler.InputLayout = self.inputLayout
        context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList
        context.InputAssembler.SetVertexBuffers(0, VertexBufferBinding(self.vertexBuffer, 32, 0))
        self.shaderPass.Apply(context)
        context.Draw(3, 0)

    def Dispose(self):
        self.effect.Dispose()
        self.inputLayout.Dispose()
        self.vertexBuffer.Dispose()

class Game(IDisposable):  
    GraphicsDevice = property(lambda self: self.graphicsDevice)

    def __init__(self, width, height, fullscreen = False):
        self.fullscreen = fullscreen
        self.form = GameForm(width, height, fullscreen)
        self.form.Visible = True
        self.graphicsDevice = GraphicsDevice(self.form, self.fullscreen)
        self.gameObjects = [TriangleObject(self)]

    def Run(self):
        Application.Idle += self.OnIdle
        Application.Run(self.form)

    def OnIdle(self, ea, sender):
        while MessagePump.IsApplicationIdle:
            self.Update()
            self.Render()

    def Update(self):
        for i in self.gameObjects:
            i.Tick()

    def Render(self):
        self.GraphicsDevice.BeginRender()
        for i in self.gameObjects:
            i.Render()
        self.GraphicsDevice.EndRender()

    def Dispose(self):
        self.GraphicsDevice.Dispose()
        for i in self.gameObjects:
            if 'Dispose' in dir(i):
                i.Dispose()
        self.form.Dispose(True)

class GameForm(Form):  
    def __init__(self, width, height, fullscreen):
        self.ClientSize = Size(width, height)
        if fullscreen:
            self.FormBorderStyle = FormBorderStyle.None

if __name__ == "__main__":  
    try:
        with Game(640, 480) as game:
            game.Run()
    except Exception as e:
        MessageBox.Show(e.ToString())