Created
September 29, 2012 18:40
-
-
Save technoir42/3804858 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Render.D3D11; | |
import Core; | |
import Core.Logging; | |
import std.array; | |
import std.c.stdlib; | |
import std.comptr; | |
import std.conv; | |
import std.format; | |
import std.string; | |
import win32.directx.dxgi; | |
import win32.directx.d3d11; | |
import win32.directx.d3dcompiler; | |
import win32.windows; | |
pragma(lib, "dxgi.lib"); | |
pragma(lib, "d3d11.lib"); | |
pragma(lib, "d3dcompiler.lib"); | |
public enum BlendArg | |
{ | |
Zero, | |
One, | |
SrcColor, | |
InvSrcColor, | |
SrcAlpha, | |
InvSrcAlpha, | |
DestAlpha, | |
InvDestAlpha, | |
DestColor, | |
InvDestColor, | |
} | |
public enum BlendOp | |
{ | |
Add, | |
Subtract, | |
RevSubtract, | |
Min, | |
Max, | |
} | |
public enum ComparisonFunc | |
{ | |
Never, | |
Less, | |
Equal, | |
LessEqual, | |
Greater, | |
NotEqual, | |
GreaterEqual, | |
Always, | |
} | |
public enum CullMode | |
{ | |
None, | |
Back, | |
Front, | |
} | |
public enum FillMode | |
{ | |
Solid, | |
Wireframe, | |
} | |
public enum TextureAddressMode | |
{ | |
Clamp, | |
Wrap, | |
Mirror, | |
Border, | |
} | |
public enum TextureFilter | |
{ | |
Point, | |
Linear, | |
} | |
public enum IndexFormat | |
{ | |
I16, | |
I32, | |
} | |
public enum PrimitiveType : ubyte | |
{ | |
Unknown, | |
PointList, | |
LineList, | |
LineStrip, | |
TriangleList, | |
TriangleStrip, | |
} | |
public enum ResourceUsage | |
{ | |
Default, | |
Immutable, | |
Dynamic | |
} | |
public enum ShaderType | |
{ | |
VertexShader, | |
FragmentShader, | |
GeometryShader, | |
HullShader, | |
DomainShader, | |
} | |
public enum SurfaceFormat | |
{ | |
Unknown, | |
// Standard formats | |
R8, | |
RG8, | |
RGB8, | |
RGBA8, | |
R16, | |
R16F, | |
RG16, | |
RG16F, | |
RGBA16, | |
RGBA16F, | |
R32F, | |
RG32F, | |
RGB32F, | |
RGBA32F, | |
// Compressed formats | |
DXT1, | |
DXT2, | |
DXT3, | |
DXT4, | |
DXT5, | |
// Depth-Stencil formats | |
D16, | |
D24S8, | |
D32F, | |
} | |
public enum VertexElementType : ubyte | |
{ | |
Float, | |
Float2, | |
Float3, | |
Float4, | |
Half2, | |
Half4, | |
UByte4, | |
UByte4N, | |
} | |
public enum VertexElementUsage : ubyte | |
{ | |
Position, | |
Normal, | |
Tangent, | |
Binormal, | |
TextureCoord, | |
Color, | |
BlendWeight, | |
BlendIndices, | |
} | |
public struct SamplerStateDesc | |
{ | |
TextureAddressMode AddressU = TextureAddressMode.Clamp; | |
TextureAddressMode AddressV = TextureAddressMode.Clamp; | |
TextureAddressMode AddressW = TextureAddressMode.Clamp; | |
TextureFilter MinFilter = TextureFilter.Point; | |
TextureFilter MagFilter = TextureFilter.Point; | |
TextureFilter MipFilter = TextureFilter.Point; | |
ubyte MaxAnisotropy = 1; | |
} | |
public struct DepthStencilStateDesc | |
{ | |
bool DepthEnable = true; | |
bool DepthWriteEnable = true; | |
bool StencilEnable = false; | |
ComparisonFunc DepthFunc = ComparisonFunc.Less; | |
} | |
public struct BlendStateDesc | |
{ | |
bool alphaToCoverageEnable = false; | |
bool blendEnable = false; | |
BlendArg srcBlend = BlendArg.SrcAlpha; | |
BlendArg destBlend = BlendArg.One; | |
BlendOp blendOp = BlendOp.Add; | |
BlendArg srcBlendAlpha = BlendArg.Zero; | |
BlendArg destBlendAlpha = BlendArg.Zero; | |
BlendOp blendOpAlpha = BlendOp.Add; | |
//ubyte RenderTargetWriteMask = 0x0F; | |
} | |
public struct RasterizerStateDesc | |
{ | |
CullMode cullMode = CullMode.Back; | |
FillMode fillMode = FillMode.Solid; | |
} | |
public struct VertexElement | |
{ | |
align(1): | |
VertexElementType type; | |
VertexElementUsage usage; | |
ubyte index; | |
} | |
public struct MultiSampleType | |
{ | |
public dword SampleCount = 1; | |
public dword Quality = 0; | |
public this(dword sampleCount, dword quality) | |
{ | |
this.SampleCount = sampleCount; | |
this.Quality = quality; | |
} | |
public const MultiSampleType None = MultiSampleType(1, 0); | |
} | |
public final class Viewport | |
{ | |
public dword x; | |
public dword y; | |
public dword width; | |
public dword height; | |
public float minDepth; | |
public float maxDepth; | |
public this(dword x, dword y, dword width, dword height, float minDepth = 0.0f, float maxDepth = 1.0f) | |
{ | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
this.minDepth = minDepth; | |
this.maxDepth = maxDepth; | |
} | |
} | |
public class RenderException : Exception | |
{ | |
public this(string msg, string file = __FILE__, size_t line = __LINE__) | |
{ | |
super(msg, file, line); | |
} | |
} | |
public final class DeviceManager | |
{ | |
private IDXGIFactory m_factory; | |
private GraphicsAdapter m_defaultAdapter; | |
private GraphicsAdapter[] m_adapters; | |
public this() | |
{ | |
try | |
{ | |
HRESULT hr = CreateDXGIFactory(&IID_IDXGIFactory, m_factory); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create DXGI factory"); | |
IDXGIAdapter adapter; | |
hr = m_factory.EnumAdapters(0, adapter); | |
if(FAILED(hr)) | |
throw new RenderException("Default graphics adapter is not accessible"); | |
m_defaultAdapter = new GraphicsAdapter(adapter); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Device Manager: %s", e.toString()); | |
throw e; | |
} | |
} | |
public ~this() | |
{ | |
delete m_defaultAdapter; | |
foreach(adapter; m_adapters) | |
delete adapter; | |
m_adapters.length = 0; | |
SafeRelease(m_factory); | |
} | |
@property | |
public GraphicsAdapter[] Adapters() | |
{ | |
if(m_adapters.length == 0) | |
{ | |
try | |
{ | |
dword index = 0; | |
IDXGIAdapter adapter; | |
while(m_factory.EnumAdapters(index++, adapter) != DXGI_ERROR_NOT_FOUND) | |
{ | |
m_adapters.length++; | |
m_adapters[$-1] = new GraphicsAdapter(adapter); | |
} | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Device Manager: %s", e.toString()); | |
throw e; | |
} | |
} | |
return m_adapters; | |
} | |
@property | |
public GraphicsAdapter DefaultAdapter() | |
{ | |
return m_defaultAdapter; | |
} | |
public GraphicsDevice CreateDevice(GraphicsAdapter adapter) | |
{ | |
assert(adapter !is null); | |
Log.Info("Device Manager: Creating device on adapter %s (%u MB RAM)", adapter.Name, adapter.VideoMemory); | |
try | |
{ | |
return new GraphicsDevice(m_factory, adapter.m_adapter, adapter.m_output); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Device Manager: %s", e.toString()); | |
throw e; | |
} | |
} | |
} | |
public struct DisplayMode | |
{ | |
dword width; | |
dword height; | |
dword refreshRate; | |
} | |
public final class GraphicsAdapter | |
{ | |
private IDXGIAdapter m_adapter; | |
private IDXGIOutput m_output; | |
private DisplayMode[] m_modes; | |
private string m_name; | |
private dword m_videoMemory; | |
private this(IDXGIAdapter adapter) | |
{ | |
m_adapter = adapter; | |
HRESULT hr = m_adapter.EnumOutputs(0, m_output); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to obtain primary output for adapter"); | |
DXGI_ADAPTER_DESC desc; | |
hr = m_adapter.GetDesc(desc); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve adapter information"); | |
char[128] buf; | |
size_t len = wcstombs(buf, desc.Description, buf.length); | |
m_name = buf[0..len].idup; | |
m_videoMemory = desc.DedicatedVideoMemory / 1024 / 1024; | |
// round to multiple of 16 | |
if(m_videoMemory > 16 && m_videoMemory % 16 != 0) | |
m_videoMemory = (m_videoMemory / 16 + 1) * 16; | |
} | |
public ~this() | |
{ | |
SafeRelease(m_output); | |
SafeRelease(m_adapter); | |
} | |
@property | |
public string Name() | |
{ | |
return m_name; | |
} | |
@property | |
public dword VideoMemory() | |
{ | |
return m_videoMemory; | |
} | |
@property | |
public const(DisplayMode[]) DisplayModes() | |
{ | |
if(m_modes.length == 0) | |
{ | |
try | |
{ | |
DXGI_MODE_DESC[] modes = GetDXGIModeList(m_output, DXGI_FORMAT_R8G8B8A8_UNORM); | |
m_modes.length = modes.length; | |
for(size_t i = 0; i < modes.length; i++) | |
{ | |
m_modes[i].width = modes[i].Width; | |
m_modes[i].height = modes[i].Height; | |
m_modes[i].refreshRate = toHz(modes[i].RefreshRate); | |
} | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Adapter: %s", e.toString()); | |
throw e; | |
} | |
} | |
return m_modes; | |
} | |
} | |
public final class GraphicsDevice | |
{ | |
private IDXGIFactory m_factory; | |
private IDXGIAdapter m_adapter; | |
private IDXGIOutput m_output; | |
private ID3D11Device m_device; | |
private ID3D11DeviceContext m_mainContext; | |
private this(IDXGIFactory factory, IDXGIAdapter adapter, IDXGIOutput output) | |
{ | |
m_factory = factory; | |
m_adapter = adapter; | |
m_output = output; | |
m_factory.AddRef(); | |
m_adapter.AddRef(); | |
m_output.AddRef(); | |
immutable D3D_FEATURE_LEVEL[3] featureLevels = | |
[ | |
D3D_FEATURE_LEVEL_11_0, | |
D3D_FEATURE_LEVEL_10_1, | |
D3D_FEATURE_LEVEL_10_0 | |
]; | |
dword flags = 0; | |
debug | |
{ | |
flags |= D3D11_CREATE_DEVICE_DEBUG; | |
Log.Info("Graphics Device: Setting debug flag"); | |
} | |
D3D_FEATURE_LEVEL featureLevel; | |
HRESULT hr = D3D11CreateDevice( | |
m_adapter, | |
D3D_DRIVER_TYPE_UNKNOWN, | |
null, | |
flags, | |
featureLevels, | |
featureLevels.length, | |
D3D11_SDK_VERSION, | |
m_device, | |
&featureLevel, | |
m_mainContext); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create Direct3D device"); | |
Log.Info("Graphics Device: Available Direct3D feature level: %s", to!string(featureLevel)); | |
} | |
public ~this() | |
{ | |
if(m_mainContext) | |
m_mainContext.ClearState(); | |
SafeRelease(m_mainContext); | |
SafeRelease(m_device); | |
SafeRelease(m_output); | |
SafeRelease(m_adapter); | |
SafeRelease(m_factory); | |
} | |
public BackBuffer CreateBackBuffer(HWND outputWindow, dword width, dword height) | |
{ | |
return CreateBackBuffer(outputWindow, width, height, MultiSampleType.None); | |
} | |
public BackBuffer CreateBackBuffer(HWND outputWindow, dword width, dword height, ref const(MultiSampleType) multiSampleType) | |
{ | |
DisplayMode mode = { width, height, 0 }; | |
return CreateBackBuffer(outputWindow, mode, false, multiSampleType); | |
} | |
public BackBuffer CreateBackBuffer(HWND outputWindow, ref const(DisplayMode) mode, bool fullscreen) | |
{ | |
return CreateBackBuffer(outputWindow, mode, fullscreen, MultiSampleType.None); | |
} | |
public BackBuffer CreateBackBuffer(HWND outputWindow, ref const(DisplayMode) mode, bool fullscreen, ref const(MultiSampleType) multiSampleType) | |
{ | |
Log.Info("Graphics Device: Creating back buffer: %ux%u %uHz (%s), samples: %u, quality: %u", mode.width, mode.height, mode.refreshRate, fullscreen ? "fullscreen" : "windowed", multiSampleType.SampleCount, multiSampleType.Quality); | |
assert(mode.width > 0 && mode.height > 0); | |
assert(outputWindow != null); | |
try | |
{ | |
SurfaceFormat format = SurfaceFormat.RGBA8; | |
DXGI_SWAP_CHAIN_DESC desc = | |
{ | |
BufferCount : 1, | |
BufferDesc : GetMatchingDXGIMode(m_device, m_output, format, mode, fullscreen), | |
BufferUsage : DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT, | |
OutputWindow : outputWindow, | |
Windowed : !fullscreen, | |
Flags : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH, | |
SwapEffect : DXGI_SWAP_EFFECT_DISCARD, | |
SampleDesc : | |
{ | |
Count : multiSampleType.SampleCount, | |
Quality : multiSampleType.Quality, | |
} | |
}; | |
if(CheckFormatSupport(format, multiSampleType)) | |
{ | |
desc.SampleDesc.Count = multiSampleType.SampleCount; | |
desc.SampleDesc.Quality = multiSampleType.Quality; | |
} | |
else | |
{ | |
desc.SampleDesc.Count = 1; | |
desc.SampleDesc.Quality = 0; | |
Log.Warning("Combination of specified surface format and multisample type is not supported. Falling back to no multisampling"); | |
} | |
return new BackBuffer(m_factory, m_device, m_output, desc, desc.BufferDesc.Width, desc.BufferDesc.Height, format, multiSampleType); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public RenderTarget CreateRenderTarget(SurfaceFormat format, dword width, dword height) | |
{ | |
return CreateRenderTarget(format, width, height, MultiSampleType.None); | |
} | |
public RenderTarget CreateRenderTarget(SurfaceFormat format, dword width, dword height, ref const(MultiSampleType) multiSampleType) | |
{ | |
Log.Info("Graphics Device: Creating render target: %ux%u %s, samples: %u, quality: %u", width, height, to!string(format), multiSampleType.SampleCount, multiSampleType.Quality); | |
assert(width > 0 && height > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
if(!IsColorFormat(format)) | |
throw new RenderException("Specified surface format cannot be used to create render target"); | |
if(!CheckFormatSupport(format, multiSampleType)) | |
throw new RenderException("Combination of specified surface format and multisample type is not supported"); | |
return new RenderTarget(m_device, width, height, format, multiSampleType); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public DepthStencilBuffer CreateDepthStencilBuffer(SurfaceFormat format, dword width, dword height, ref const(MultiSampleType) multiSampleType) | |
{ | |
Log.Info("Graphics Device: Creating depth-stencil buffer: %ux%u %s, samples: %u, quality: %u", width, height, to!string(format), multiSampleType.SampleCount, multiSampleType.Quality); | |
assert(width > 0 && height > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
if(!IsDepthStencilFormat(format)) | |
throw new RenderException("Specified surface format cannot be used to create depth-stencil buffer"); | |
if(!CheckFormatSupport(format, multiSampleType)) | |
throw new RenderException("Combination of specified surface format and multisample type is not supported"); | |
return new DepthStencilBuffer(m_device, width, height, format, multiSampleType); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public RenderContext CreateMainContext() | |
{ | |
Log.Info("Graphics Device: Creating main rendering context"); | |
return new RenderContext(m_mainContext); | |
} | |
public DeferredContext CreateRenderContext() | |
{ | |
Log.Info("Graphics Device: Creating deferred rendering context"); | |
try | |
{ | |
return new DeferredContext(m_device); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
/** | |
* Creates constant buffer and fills it with specified data. | |
* | |
* @param size size of constant buffer in bytes | |
* @param data pointer to data used to fill constant buffer (optional) | |
* | |
* @throws RenderException if any error occurs | |
*/ | |
public ConstantBuffer CreateConstantBuffer(dword size, const void* data) | |
{ | |
Log.Info("Graphics Device: Creating constant buffer (size: %u bytes)", size); | |
try | |
{ | |
return new ConstantBuffer(m_device, size, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
/** | |
* Creates vertex buffer and fills it with specified data. | |
* | |
* @param size number of vertices in vertex buffer | |
* @param vertexSize size of single vertex in bytes | |
* @param data pointer to data used to fill vertex buffer (optional) | |
* | |
* @throws RenderException if any error occurs | |
*/ | |
public VertexBuffer CreateVertexBuffer(dword size, dword vertexSize, const void* data) | |
{ | |
Log.Info("Graphics Device: Creating vertex buffer (size: %u elements, stride: %u bytes)", size, vertexSize); | |
try | |
{ | |
return new VertexBuffer(m_device, size, vertexSize, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
/** | |
* Creates index buffer and fills it with specified data. | |
* | |
* @param size number of indices in index buffer | |
* @param format format of indices in index buffer | |
* @param data pointer to data used to fill index buffer (optional) | |
* | |
* @throws RenderException if any error occurs | |
*/ | |
public IndexBuffer CreateIndexBuffer(dword size, IndexFormat format, const void* data) | |
{ | |
Log.Info("Graphics Device: Creating index buffer (size: %u elements, format: %s)", size, to!string(format)); | |
try | |
{ | |
return new IndexBuffer(m_device, size, format, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public VertexLayout CreateVertexLayout(const VertexElement[] elements) | |
{ | |
Log.Info("Graphics Device: Creating vertex layout (%u elements)", elements.length); | |
assert(elements != null); | |
try | |
{ | |
return new VertexLayout(m_device, elements); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public Texture1D CreateTexture1D(dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data = null) | |
{ | |
Log.Info("Graphics Device: Creating 1D texture (width: %u, format: %s, mip levels: %u)", size, to!string(format), mipLevels); | |
assert(size > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
return new Texture1D(m_device, size, format, mipLevels, usage, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public Texture2D CreateTexture2D(dword width, dword height, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data = null) | |
{ | |
return CreateTexture2D(width, height, format, mipLevels, usage, MultiSampleType.None, data); | |
} | |
public Texture2D CreateTexture2D(dword width, dword height, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType, const void* data = null) | |
{ | |
Log.Info("Graphics Device: Creating 2D texture (width: %u, height: %u, format: %s, mip levels: %u, samples: %u, quality: %u)", width, height, to!string(format), mipLevels, multiSampleType.SampleCount, multiSampleType.Quality); | |
assert(width > 0 && height > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
return new Texture2D(m_device, width, height, format, mipLevels, usage, multiSampleType, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public Texture3D CreateTexture3D(dword width, dword height, dword depth, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data = null) | |
{ | |
Log.Info("Graphics Device: Creating 3D texture (width: %u, height: %u, depth: %u, format: %s, mip levels: %u)", width, height, depth, to!string(format), mipLevels); | |
assert(width > 0 && height > 0 && depth > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
return new Texture3D(m_device, width, height, depth, format, mipLevels, usage, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public TextureCube CreateTextureCube(dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data = null) | |
{ | |
return CreateTextureCube(size, format, mipLevels, usage, MultiSampleType.None, data); | |
} | |
public TextureCube CreateTextureCube(dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType, const void* data = null) | |
{ | |
Log.Info("Graphics Device: Creating cube texture (size: %u, format: %s, mip levels: %u, samples: %u, quality: %u)", size, to!string(format), mipLevels, multiSampleType.SampleCount, multiSampleType.Quality); | |
assert(size > 0); | |
assert(format != SurfaceFormat.Unknown); | |
try | |
{ | |
return new TextureCube(m_device, size, format, mipLevels, usage, multiSampleType, data); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public VertexShader CreateVertexShader(string code) | |
{ | |
Log.Info("Graphics Device: Creating vertex shader"); | |
assert(code != null); | |
try | |
{ | |
ShaderCompiler compiler; | |
if(!compiler.Compile(code, "vs_4_1", "vs_main")) | |
throw new RenderException("Failed to compile vertex shader"); | |
return new VertexShader(m_device, compiler.CompiledCode, compiler.CompiledSize); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public FragmentShader CreateFragmentShader(string code) | |
{ | |
Log.Info("Graphics Device: Creating fragment shader"); | |
assert(code != null); | |
try | |
{ | |
ShaderCompiler compiler; | |
if(!compiler.Compile(code, "ps_4_1", "fs_main")) | |
throw new RenderException("Failed to compile fragment shader"); | |
return new FragmentShader(m_device, compiler.CompiledCode, compiler.CompiledSize); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public GeometryShader CreateGeometryShader(string code) | |
{ | |
Log.Info("Graphics Device: Creating geometry shader"); | |
assert(code != null); | |
try | |
{ | |
ShaderCompiler compiler; | |
if(!compiler.Compile(code, "gs_4_1", "gs_main")) | |
throw new RenderException("Failed to compile geometry shader"); | |
return new GeometryShader(m_device, compiler.CompiledCode, compiler.CompiledSize); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public HullShader CreateHullShader(string code) | |
{ | |
Log.Info("Graphics Device: Creating hull shader"); | |
assert(code != null); | |
try | |
{ | |
ShaderCompiler compiler; | |
if(!compiler.Compile(code, "hs_5_0", "hs_main")) | |
throw new RenderException("Failed to compile hull shader"); | |
return new HullShader(m_device, compiler.CompiledCode, compiler.CompiledSize); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public DomainShader CreateDomainShader(string code) | |
{ | |
Log.Info("Graphics Device: Creating domain shader"); | |
assert(code != null); | |
try | |
{ | |
ShaderCompiler compiler; | |
if(!compiler.Compile(code, "ds_5_0", "ds_main")) | |
throw new RenderException("Failed to compile domain shader"); | |
return new DomainShader(m_device, compiler.CompiledCode, compiler.CompiledSize); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public SamplerState CreateSamplerState(const ref SamplerStateDesc samplerState) | |
{ | |
Log.Info("Graphics Device: Creating sampler state"); | |
try | |
{ | |
return new SamplerState(m_device, samplerState); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public DepthStencilState CreateDepthStencilState(const ref DepthStencilStateDesc desc) | |
{ | |
Log.Info("Graphics Device: Creating depth-stencil state"); | |
try | |
{ | |
return new DepthStencilState(m_device, desc); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public RasterizerState CreateRasterizerState(const ref RasterizerStateDesc desc) | |
{ | |
Log.Info("Graphics Device: Creating rasterizer state"); | |
try | |
{ | |
return new RasterizerState(m_device, desc); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public BlendState CreateBlendState(const ref BlendStateDesc desc) | |
{ | |
Log.Info("Graphics Device: Creating blend state"); | |
try | |
{ | |
return new BlendState(m_device, desc); | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Graphics Device: %s", e.toString()); | |
throw e; | |
} | |
} | |
public bool CheckFormatSupport(SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
return .CheckFormatSupport(m_device, format, multiSampleType); | |
} | |
} | |
public class RenderContext | |
{ | |
private PrimitiveType m_primitiveType; | |
private VertexShader m_vertexShader; | |
private FragmentShader m_fragmentShader; | |
private GeometryShader m_geometryShader; | |
private VertexBuffer m_vertexBuffer; | |
private IndexBuffer m_indexBuffer; | |
private VertexLayout m_layout; | |
private DepthStencilState m_dsState; | |
private RasterizerState m_rsState; | |
private BlendState m_blendState; | |
protected ID3D11DeviceContext m_context; | |
protected this() | |
{ | |
} | |
private this(ID3D11DeviceContext context) | |
{ | |
assert(context !is null); | |
m_context = context; | |
m_context.AddRef(); | |
} | |
public ~this() | |
{ | |
ClearState(); | |
SafeRelease(m_context); | |
} | |
public final void ClearState() | |
{ | |
m_primitiveType = PrimitiveType.Unknown; | |
m_vertexShader = null; | |
m_fragmentShader = null; | |
m_geometryShader = null; | |
m_vertexBuffer = null; | |
m_indexBuffer = null; | |
m_layout = null; | |
m_dsState = null; | |
m_rsState = null; | |
m_blendState = null; | |
if(m_context) | |
m_context.ClearState(); | |
} | |
public final void SetViewport(Viewport viewport) | |
{ | |
D3D11_VIEWPORT vp = | |
{ | |
TopLeftX: viewport.x, | |
TopLeftY: viewport.y, | |
Width: viewport.width, | |
Height: viewport.height, | |
MinDepth: viewport.minDepth, | |
MaxDepth: viewport.maxDepth | |
}; | |
m_context.RSSetViewports(1, &vp); | |
} | |
public final void SetRenderTarget(RenderTarget renderTarget) | |
{ | |
ID3D11DepthStencilView dsView; | |
m_context.OMGetRenderTargets(0, null, &dsView); | |
ID3D11RenderTargetView view = (renderTarget !is null) ? renderTarget.m_rtView : null; | |
m_context.OMSetRenderTargets(1, &view, dsView); | |
SafeRelease(dsView); | |
} | |
public final void SetRenderTarget(RenderTarget renderTarget, DepthStencilBuffer depthStencil) | |
{ | |
ID3D11RenderTargetView view = (renderTarget !is null) ? renderTarget.m_rtView : null; | |
ID3D11DepthStencilView dsView = (depthStencil !is null) ? depthStencil.m_dsView : null; | |
m_context.OMSetRenderTargets(1, &view, dsView); | |
} | |
public final void SetDepthStencilBuffer(DepthStencilBuffer depthStencil) | |
{ | |
ID3D11RenderTargetView[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] rtViews; | |
m_context.OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, rtViews, null); | |
ID3D11DepthStencilView dsView = (depthStencil !is null) ? depthStencil.m_dsView : null; | |
m_context.OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, rtViews, dsView); | |
for(int i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) | |
SafeRelease(rtViews[i]); | |
} | |
public final void DetachRenderTargets() | |
{ | |
m_context.OMSetRenderTargets(0, null, null); | |
} | |
public final void SetVertexShader(VertexShader shader) | |
{ | |
if(m_vertexShader != shader) | |
{ | |
m_vertexShader = shader; | |
m_context.VSSetShader(shader ? shader.m_shader : null, null, 0); | |
} | |
} | |
public final void SetFragmentShader(FragmentShader shader) | |
{ | |
if(m_fragmentShader != shader) | |
{ | |
m_fragmentShader = shader; | |
m_context.PSSetShader(shader ? shader.m_shader : null, null, 0); | |
} | |
} | |
public final void SetGeometryShader(GeometryShader shader) | |
{ | |
if(m_geometryShader != shader) | |
{ | |
m_geometryShader = shader; | |
m_context.GSSetShader(shader ? shader.m_shader : null, null, 0); | |
} | |
} | |
public final void BindResource(ShaderType stage, ubyte slot, ShaderResource resource) | |
{ | |
assert(resource !is null); | |
assert(slot < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT); | |
final switch(stage) | |
{ | |
case ShaderType.VertexShader: | |
m_context.VSSetShaderResources(slot, 1, &resource.m_resourceView); | |
break; | |
case ShaderType.FragmentShader: | |
m_context.PSSetShaderResources(slot, 1, &resource.m_resourceView); | |
break; | |
case ShaderType.GeometryShader: | |
m_context.GSSetShaderResources(slot, 1, &resource.m_resourceView); | |
break; | |
case ShaderType.HullShader: | |
m_context.HSSetShaderResources(slot, 1, &resource.m_resourceView); | |
break; | |
case ShaderType.DomainShader: | |
m_context.DSSetShaderResources(slot, 1, &resource.m_resourceView); | |
break; | |
} | |
} | |
public final void BindConstantBuffer(ShaderType stage, ubyte slot, ConstantBuffer buffer) | |
{ | |
assert(buffer !is null); | |
assert(slot < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT); | |
final switch(stage) | |
{ | |
case ShaderType.VertexShader: | |
m_context.VSSetConstantBuffers(slot, 1, &buffer.m_buffer); | |
break; | |
case ShaderType.FragmentShader: | |
m_context.PSSetConstantBuffers(slot, 1, &buffer.m_buffer); | |
break; | |
case ShaderType.GeometryShader: | |
m_context.GSSetConstantBuffers(slot, 1, &buffer.m_buffer); | |
break; | |
case ShaderType.HullShader: | |
m_context.HSSetConstantBuffers(slot, 1, &buffer.m_buffer); | |
break; | |
case ShaderType.DomainShader: | |
m_context.DSSetConstantBuffers(slot, 1, &buffer.m_buffer); | |
break; | |
} | |
} | |
public final void BindVertexBuffer(VertexBuffer buffer) | |
{ | |
assert(buffer !is null); | |
if(m_vertexBuffer != buffer) | |
{ | |
m_vertexBuffer = buffer; | |
dword vertexSize = buffer.VertexSize; | |
dword offset = 0; | |
m_context.IASetVertexBuffers(0, 1, &buffer.m_buffer, &vertexSize, &offset); | |
} | |
} | |
public final void BindIndexBuffer(IndexBuffer buffer) | |
{ | |
assert(buffer !is null); | |
if(m_indexBuffer != buffer) | |
{ | |
m_indexBuffer = buffer; | |
m_context.IASetIndexBuffer(buffer.m_buffer, type_cast!DXGI_FORMAT(buffer.Format), 0); | |
} | |
} | |
public final void SetPrimitiveType(PrimitiveType primitiveType) | |
{ | |
if(m_primitiveType != primitiveType) | |
{ | |
m_primitiveType = primitiveType; | |
m_context.IASetPrimitiveTopology(type_cast!D3D11_PRIMITIVE_TOPOLOGY(primitiveType)); | |
} | |
} | |
public final void SetVertexLayout(VertexLayout layout) | |
{ | |
assert(layout !is null); | |
if(m_layout != layout) | |
{ | |
m_layout = layout; | |
m_context.IASetInputLayout(layout.m_layout); | |
} | |
} | |
public final void SetSamplerState(ShaderType stage, ubyte slot, SamplerState state) | |
{ | |
assert(state !is null); | |
assert(slot < D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT); | |
final switch(stage) | |
{ | |
case ShaderType.VertexShader: | |
m_context.VSSetSamplers(slot, 1, &state.m_state); | |
break; | |
case ShaderType.FragmentShader: | |
m_context.PSSetSamplers(slot, 1, &state.m_state); | |
break; | |
case ShaderType.GeometryShader: | |
m_context.GSSetSamplers(slot, 1, &state.m_state); | |
break; | |
case ShaderType.HullShader: | |
m_context.HSSetSamplers(slot, 1, &state.m_state); | |
break; | |
case ShaderType.DomainShader: | |
m_context.DSSetSamplers(slot, 1, &state.m_state); | |
break; | |
} | |
} | |
public final void SetDepthStencilState(DepthStencilState state) | |
{ | |
assert(state !is null); | |
if(m_dsState != state) | |
{ | |
m_dsState = state; | |
m_context.OMSetDepthStencilState(state.m_state, 0); | |
} | |
} | |
public final void SetRasterizerState(RasterizerState state) | |
{ | |
assert(state !is null); | |
if(m_rsState != state) | |
{ | |
m_rsState = state; | |
m_context.RSSetState(state.m_state); | |
} | |
} | |
public final void SetBlendState(BlendState state) | |
{ | |
assert(state !is null); | |
if(m_blendState != state) | |
{ | |
m_blendState = state; | |
immutable float[4] coef = [ 1.0f, 1.0f, 1.0f, 1.0f ]; | |
m_context.OMSetBlendState(state.m_state, coef, 0xffffffff); | |
} | |
} | |
public final void ClearRenderTarget(RenderTarget renderTarget, const ref float[4] color) | |
{ | |
assert(renderTarget !is null); | |
m_context.ClearRenderTargetView(renderTarget.m_rtView, color); | |
} | |
public final void ClearDepthStencilBuffer(DepthStencilBuffer depthStencil, float depth) | |
{ | |
assert(depthStencil !is null); | |
m_context.ClearDepthStencilView(depthStencil.m_dsView, D3D11_CLEAR_DEPTH, depth, 0); | |
} | |
public final void Draw(dword vertexCount, dword startVertex) | |
{ | |
m_context.Draw(vertexCount, startVertex); | |
} | |
public final void DrawIndexed(dword indexCount, dword startIndex) | |
{ | |
m_context.DrawIndexed(indexCount, startIndex, 0); | |
} | |
public final void DrawInstances(dword instanceCount, dword vertexCount, dword startVertex) | |
{ | |
m_context.DrawInstanced(vertexCount, instanceCount, startVertex, 0); | |
} | |
public final void DrawIndexedInstances(dword instanceCount, dword indexCount, dword startIndex) | |
{ | |
m_context.DrawIndexedInstanced(indexCount, instanceCount, startIndex, 0, 0); | |
} | |
} | |
public final class DeferredContext : RenderContext | |
{ | |
private this(ID3D11Device device) | |
{ | |
HRESULT hr = device.CreateDeferredContext(0, m_context); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create deferred device context"); | |
} | |
public void Flush() | |
{ | |
com_ptr!ID3D11CommandList commandList; | |
if(FAILED(m_context.FinishCommandList(true, commandList))) | |
return; | |
m_context.ExecuteCommandList(commandList, true); | |
} | |
} | |
public class ShaderResource | |
{ | |
protected ID3D11ShaderResourceView m_resourceView; | |
public ~this() | |
{ | |
SafeRelease(m_resourceView); | |
} | |
} | |
public class Texture1D : ShaderResource | |
{ | |
protected dword m_size; | |
protected dword m_mipLevels; | |
protected SurfaceFormat m_format; | |
protected ResourceUsage m_usage; | |
protected ID3D11Texture1D m_texture; | |
protected this(dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage) | |
{ | |
m_size = size; | |
m_format = format; | |
m_mipLevels = mipLevels; | |
m_usage = usage; | |
} | |
private this(ID3D11Device device, dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data) | |
{ | |
this(size, format, mipLevels, usage); | |
D3D11_TEXTURE1D_DESC desc = | |
{ | |
ArraySize : 1, | |
BindFlags : D3D11_BIND_SHADER_RESOURCE, | |
Width : size, | |
MipLevels : mipLevels, | |
Usage : type_cast!D3D11_USAGE(usage), | |
Format : type_cast!DXGI_FORMAT(format), | |
MiscFlags : 0, | |
CPUAccessFlags : 0 | |
}; | |
D3D11_SUBRESOURCE_DATA[] initData = data ? FillInitialData(size, 1, 1, format, mipLevels, 1, data) : null; | |
HRESULT hr = device.CreateTexture1D(desc, initData ? initData.ptr : null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create 1D texture"); | |
hr = device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_texture); | |
} | |
@property | |
public dword Size() const | |
{ | |
return m_size; | |
} | |
@property | |
public dword MipLevels() const | |
{ | |
return m_mipLevels; | |
} | |
@property | |
public SurfaceFormat Format() const | |
{ | |
return m_format; | |
} | |
@property | |
public ResourceUsage Usage() const | |
{ | |
return m_usage; | |
} | |
public void Update(RenderContext context, const void* data, int mip) | |
{ | |
assert(data != null); | |
assert(m_usage != ResourceUsage.Immutable); | |
dword k = 0; | |
if(mip != -1) | |
k = D3D11CalcSubresource(mip, 0, MipLevels); | |
context.m_context.UpdateSubresource( | |
m_texture, | |
k, | |
null, | |
data, | |
0, | |
0); | |
} | |
} | |
public class Texture2D : ShaderResource | |
{ | |
protected dword m_width; | |
protected dword m_height; | |
protected dword m_mipLevels; | |
protected SurfaceFormat m_format; | |
protected ResourceUsage m_usage; | |
protected MultiSampleType m_multiSampleType; | |
protected ID3D11Texture2D m_texture; | |
protected this(dword width, dword height, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType) | |
{ | |
m_width = width; | |
m_height = height; | |
m_format = format; | |
m_mipLevels = mipLevels; | |
m_usage = usage; | |
m_multiSampleType = multiSampleType; | |
} | |
private this(ID3D11Device device, dword width, dword height, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType, const void* data) | |
{ | |
this(width, height, format, mipLevels, usage, multiSampleType); | |
D3D11_TEXTURE2D_DESC desc = | |
{ | |
ArraySize : 1, | |
BindFlags : D3D11_BIND_SHADER_RESOURCE, | |
Width : width, | |
Height : height, | |
MipLevels : mipLevels, | |
Usage : type_cast!D3D11_USAGE(usage), | |
Format : type_cast!DXGI_FORMAT(format), | |
MiscFlags : 0, | |
CPUAccessFlags : 0, | |
SampleDesc : | |
{ | |
Count : multiSampleType.SampleCount, | |
Quality : multiSampleType.Quality | |
} | |
}; | |
D3D11_SUBRESOURCE_DATA[] initData = data ? FillInitialData(width, height, 1, format, mipLevels, 1, data) : null; | |
HRESULT hr = device.CreateTexture2D(desc, initData ? initData.ptr : null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create 2D texture"); | |
hr = device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_texture); | |
} | |
@property | |
public dword Width() const | |
{ | |
return m_width; | |
} | |
@property | |
public dword Height() const | |
{ | |
return m_height; | |
} | |
@property | |
public dword MipLevels() const | |
{ | |
return m_mipLevels; | |
} | |
@property | |
public SurfaceFormat Format() const | |
{ | |
return m_format; | |
} | |
@property | |
public ref const(MultiSampleType) GetMultiSampleType() const | |
{ | |
return m_multiSampleType; | |
} | |
@property | |
public ResourceUsage Usage() const | |
{ | |
return m_usage; | |
} | |
public void Update(RenderContext context, const void* data, int mip) | |
{ | |
assert(data != null); | |
assert(m_usage != ResourceUsage.Immutable); | |
dword k = 0; | |
if(mip != -1) | |
k = D3D11CalcSubresource(mip, 0, MipLevels); | |
context.m_context.UpdateSubresource( | |
m_texture, | |
k, | |
null, | |
data, | |
CalculateImageSize(Width, 1, 1, Format), | |
0); | |
} | |
} | |
public class Texture3D : ShaderResource | |
{ | |
protected dword m_width; | |
protected dword m_height; | |
protected dword m_depth; | |
protected dword m_mipLevels; | |
protected SurfaceFormat m_format; | |
protected ResourceUsage m_usage; | |
protected ID3D11Texture3D m_texture; | |
protected this(dword width, dword height, dword depth, SurfaceFormat format, dword mipLevels, ResourceUsage usage) | |
{ | |
m_width = width; | |
m_height = height; | |
m_depth = depth; | |
m_format = format; | |
m_mipLevels = mipLevels; | |
m_usage = usage; | |
} | |
private this(ID3D11Device device, dword width, dword height, dword depth, SurfaceFormat format, dword mipLevels, ResourceUsage usage, const void* data) | |
{ | |
this(width, height, depth, format, mipLevels, usage); | |
D3D11_TEXTURE3D_DESC desc = | |
{ | |
BindFlags : D3D11_BIND_SHADER_RESOURCE, | |
Width : width, | |
Height : height, | |
Depth : depth, | |
MipLevels : mipLevels, | |
Usage : type_cast!D3D11_USAGE(usage), | |
Format : type_cast!DXGI_FORMAT(format), | |
MiscFlags : 0, | |
CPUAccessFlags : 0 | |
}; | |
D3D11_SUBRESOURCE_DATA[] initData = data ? FillInitialData(width, height, depth, format, mipLevels, 1, data) : null; | |
HRESULT hr = device.CreateTexture3D(desc, initData ? initData.ptr : null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create 3D texture"); | |
hr = device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_texture); | |
} | |
public dword Width() const | |
{ | |
return m_width; | |
} | |
@property | |
public dword Height() const | |
{ | |
return m_height; | |
} | |
@property | |
public dword Depth() const | |
{ | |
return m_depth; | |
} | |
@property | |
public dword MipLevels() const | |
{ | |
return m_mipLevels; | |
} | |
@property | |
public SurfaceFormat Format() const | |
{ | |
return m_format; | |
} | |
@property | |
public ResourceUsage Usage() const | |
{ | |
return m_usage; | |
} | |
} | |
public class TextureCube : ShaderResource | |
{ | |
protected dword m_size; | |
protected dword m_mipLevels; | |
protected SurfaceFormat m_format; | |
protected ResourceUsage m_usage; | |
protected MultiSampleType m_multiSampleType; | |
protected ID3D11Texture2D m_texture; | |
protected this(dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType) | |
{ | |
m_size = size; | |
m_format = format; | |
m_mipLevels = mipLevels; | |
m_usage = usage; | |
m_multiSampleType = multiSampleType; | |
} | |
private this(ID3D11Device device, dword size, SurfaceFormat format, dword mipLevels, ResourceUsage usage, ref const(MultiSampleType) multiSampleType, const void* data) | |
{ | |
this(size, format, mipLevels, usage, multiSampleType); | |
D3D11_TEXTURE2D_DESC desc = | |
{ | |
ArraySize : 6, | |
BindFlags : D3D11_BIND_SHADER_RESOURCE, | |
Width : size, | |
Height : size, | |
MipLevels : mipLevels, | |
Usage : type_cast!D3D11_USAGE(usage), | |
Format : type_cast!DXGI_FORMAT(format), | |
MiscFlags : D3D11_RESOURCE_MISC_TEXTURECUBE, | |
CPUAccessFlags : 0, | |
SampleDesc : | |
{ | |
Count : multiSampleType.SampleCount, | |
Quality : multiSampleType.Quality | |
} | |
}; | |
D3D11_SUBRESOURCE_DATA[] initData = data ? FillInitialData(size, size, 1, format, mipLevels, 6, data) : null; | |
HRESULT hr = device.CreateTexture2D(desc, initData ? initData.ptr : null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create cube texture"); | |
hr = device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_texture); | |
} | |
@property | |
public dword Size() const | |
{ | |
return m_size; | |
} | |
@property | |
public SurfaceFormat Format() const | |
{ | |
return m_format; | |
} | |
@property | |
public ref const(MultiSampleType) GetMultiSampleType() const | |
{ | |
return m_multiSampleType; | |
} | |
@property | |
public ResourceUsage Usage() const | |
{ | |
return m_usage; | |
} | |
} | |
public class RenderTarget : Texture2D | |
{ | |
private ID3D11RenderTargetView m_rtView; | |
protected this(dword width, dword height, SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
super(width, height, format, 1, ResourceUsage.Default, multiSampleType); | |
} | |
private this(ID3D11Device device, dword width, dword height, SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
this(width, height, format, multiSampleType); | |
D3D11_TEXTURE2D_DESC desc = | |
{ | |
ArraySize : 1, | |
BindFlags : D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE, | |
Width : width, | |
Height : height, | |
MipLevels : 1, | |
Usage : D3D11_USAGE_DEFAULT, | |
Format : type_cast!DXGI_FORMAT(format), | |
CPUAccessFlags : 0, | |
MiscFlags : 0, | |
SampleDesc : | |
{ | |
Count : multiSampleType.SampleCount, | |
Quality : multiSampleType.Quality | |
} | |
}; | |
HRESULT hr = device.CreateTexture2D(desc, null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create 2D texture"); | |
hr = device.CreateRenderTargetView(m_texture, null, m_rtView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create render target view"); | |
hr = device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_rtView); | |
} | |
} | |
public final class BackBuffer : RenderTarget | |
{ | |
private IDXGIFactory m_factory; | |
private IDXGISwapChain m_swapChain; | |
private IDXGIOutput m_output; | |
private ID3D11Device m_device; | |
private bool m_vsync; | |
private this(IDXGIFactory factory, ID3D11Device device, IDXGIOutput output, ref const(DXGI_SWAP_CHAIN_DESC) desc, dword width, dword height, SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
assert(output !is null); | |
super(width, height, format, multiSampleType); | |
m_factory = factory; | |
m_device = device; | |
m_output = output; | |
m_factory.AddRef(); | |
m_device.AddRef(); | |
m_output.AddRef(); | |
HRESULT hr = factory.CreateSwapChain(device, desc, m_swapChain); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create swap chain"); | |
CreateViews(); | |
} | |
public ~this() | |
{ | |
if(m_swapChain) | |
m_swapChain.SetFullscreenState(false, null); | |
SafeRelease(m_swapChain); | |
SafeRelease(m_output); | |
SafeRelease(m_device); | |
SafeRelease(m_factory); | |
} | |
public void Present(bool vsync = false) | |
{ | |
assert(m_swapChain !is null); | |
m_swapChain.Present(vsync ? 1 : 0, 0); | |
} | |
public void ChangeDisplayMode(ref const(DisplayMode) mode, bool fullscreen) | |
{ | |
ChangeDisplayMode(mode, fullscreen, GetMultiSampleType()); | |
} | |
public void ChangeDisplayMode(ref const(DisplayMode) mode, bool fullscreen, ref const(MultiSampleType) multiSampleType) | |
{ | |
try | |
{ | |
Log.Info("Back Buffer: Changing display mode to %ux%u %uHz (%s), samples: %u, quality: %u", mode.width, mode.height, mode.refreshRate, fullscreen ? "fullscreen" : "windowed", multiSampleType.SampleCount, multiSampleType.Quality); | |
DXGI_SWAP_CHAIN_DESC desc = void; | |
HRESULT hr = m_swapChain.GetDesc(desc); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve swap chain information"); | |
desc.Windowed = !fullscreen; | |
desc.BufferDesc = GetMatchingDXGIMode(m_device, m_output, Format, mode, fullscreen); | |
if(CheckFormatSupport(m_device, Format, multiSampleType)) | |
{ | |
desc.SampleDesc.Count = multiSampleType.SampleCount; | |
desc.SampleDesc.Quality = multiSampleType.Quality; | |
} | |
else | |
{ | |
desc.SampleDesc.Count = 1; | |
desc.SampleDesc.Quality = 0; | |
Log.Warning("Combination of specified surface format and multisample type is not supported. Falling back to no multisampling"); | |
} | |
m_swapChain.SetFullscreenState(false, null); | |
SafeRelease(m_rtView); | |
SafeRelease(m_resourceView); | |
SafeRelease(m_texture); | |
SafeRelease(m_swapChain); | |
hr = m_factory.CreateSwapChain(m_device, desc, m_swapChain); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create swap chain"); | |
CreateViews(); | |
m_width = desc.BufferDesc.Width; | |
m_height = desc.BufferDesc.Height; | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Back Buffer: %s", e.toString()); | |
throw e; | |
} | |
} | |
public void Resize(dword width, dword height) | |
{ | |
if(m_width == width && m_height == height) | |
return; | |
Log.Info("Back Buffer: Resizing to %ux%u", width, height); | |
try | |
{ | |
DXGI_SWAP_CHAIN_DESC desc = void; | |
HRESULT hr = m_swapChain.GetDesc(desc); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve swap chain information"); | |
desc.BufferDesc.Width = width; | |
desc.BufferDesc.Height = height; | |
SafeRelease(m_rtView); | |
SafeRelease(m_resourceView); | |
SafeRelease(m_texture); | |
hr = m_swapChain.ResizeBuffers( | |
desc.BufferCount, | |
desc.BufferDesc.Width, | |
desc.BufferDesc.Height, | |
desc.BufferDesc.Format, | |
desc.Flags); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to resize swap chain buffers"); | |
CreateViews(); | |
m_width = desc.BufferDesc.Width; | |
m_height = desc.BufferDesc.Height; | |
} | |
catch(RenderException e) | |
{ | |
Log.Error("Back Buffer: %s", e.toString()); | |
throw e; | |
} | |
} | |
@property | |
public bool VSync() const | |
{ | |
return m_vsync; | |
} | |
@property | |
public bool VSync(bool vsync) | |
{ | |
return m_vsync = vsync; | |
} | |
public void Present() | |
{ | |
m_swapChain.Present(m_vsync ? 1 : 0, 0); | |
} | |
private void CreateViews() | |
{ | |
assert(m_texture is null); | |
assert(m_resourceView is null); | |
assert(m_rtView is null); | |
HRESULT hr = m_swapChain.GetBuffer(0, &IID_ID3D11Texture2D, cast(void**)&m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to obtain back buffer texture"); | |
hr = m_device.CreateRenderTargetView(m_texture, null, m_rtView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create render target view"); | |
hr = m_device.CreateShaderResourceView(m_texture, null, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
} | |
public final class DepthStencilBuffer : public Texture2D | |
{ | |
private ID3D11DepthStencilView m_dsView; | |
private this(ID3D11Device device, dword width, dword height, SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
super(width, height, format, 1, ResourceUsage.Default, multiSampleType); | |
immutable(DepthStencilFormatDesc)* formats; | |
switch(format) | |
{ | |
case SurfaceFormat.D16: formats = &internalFormats[0]; break; | |
case SurfaceFormat.D24S8: formats = &internalFormats[1]; break; | |
case SurfaceFormat.D32F: formats = &internalFormats[2]; break; | |
default: | |
break; | |
} | |
D3D11_TEXTURE2D_DESC desc = | |
{ | |
ArraySize : 1, | |
BindFlags : D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE, | |
Width : width, | |
Height : height, | |
MipLevels : 1, | |
Usage : D3D11_USAGE_DEFAULT, | |
Format : formats.surfaceFormat, | |
CPUAccessFlags : 0, | |
MiscFlags : 0, | |
SampleDesc : | |
{ | |
Count : multiSampleType.SampleCount, | |
Quality : multiSampleType.Quality | |
} | |
}; | |
D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = { Format : formats.DSVFormat }; | |
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = { Format : formats.SRVFormat }; | |
if(multiSampleType != MultiSampleType.None) | |
{ | |
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS; | |
srvDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE2DMS; | |
} | |
else | |
{ | |
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; | |
srvDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE2D; | |
srvDesc.Texture2D.MipLevels = desc.MipLevels; | |
} | |
HRESULT hr = device.CreateTexture2D(desc, null, m_texture); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create 2D texture"); | |
hr = device.CreateDepthStencilView(m_texture, &dsvDesc, m_dsView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create depth-stencil view"); | |
hr = device.CreateShaderResourceView(m_texture, &srvDesc, m_resourceView); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create shader resource view"); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_dsView); | |
} | |
} | |
public final class ConstantBuffer | |
{ | |
private ID3D11Buffer m_buffer; | |
private dword m_size; | |
private this(ID3D11Device device, dword size, const void* data) | |
{ | |
D3D11_BUFFER_DESC desc = | |
{ | |
BindFlags : D3D11_BIND_CONSTANT_BUFFER, | |
ByteWidth : size, | |
CPUAccessFlags : 0, | |
MiscFlags : 0, | |
Usage : D3D11_USAGE_DEFAULT, | |
StructureByteStride : 0 | |
}; | |
D3D11_SUBRESOURCE_DATA dataDesc = { data, 0, 0 }; | |
HRESULT hr = device.CreateBuffer(desc, data ? &dataDesc : null, m_buffer); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create constant buffer"); | |
m_size = size; | |
} | |
public ~this() | |
{ | |
SafeRelease(m_buffer); | |
} | |
@property | |
public dword Size() const | |
{ | |
return m_size; | |
} | |
public void Update(RenderContext context, const void* data) | |
{ | |
assert(context !is null); | |
context.m_context.UpdateSubresource(m_buffer, 0, null, data, 0, 0); | |
} | |
} | |
public final class VertexBuffer | |
{ | |
private ID3D11Buffer m_buffer; | |
private dword m_count; | |
private dword m_vertexSize; | |
private this(ID3D11Device device, dword count, dword vertexSize, const void* data) | |
{ | |
D3D11_BUFFER_DESC desc = | |
{ | |
BindFlags : D3D11_BIND_VERTEX_BUFFER, | |
ByteWidth : count * vertexSize, | |
CPUAccessFlags : 0, | |
MiscFlags : 0, | |
Usage : D3D11_USAGE_DEFAULT, | |
StructureByteStride : 0 | |
}; | |
D3D11_SUBRESOURCE_DATA dataDesc = { data, 0, 0 }; | |
HRESULT hr = device.CreateBuffer(desc, data ? &dataDesc : null, m_buffer); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create vertex buffer"); | |
m_count = count; | |
m_vertexSize = vertexSize; | |
} | |
public ~this() | |
{ | |
SafeRelease(m_buffer); | |
} | |
@property | |
public dword Count() const | |
{ | |
return m_count; | |
} | |
@property | |
public dword VertexSize() const | |
{ | |
return m_vertexSize; | |
} | |
public void Update(RenderContext context, const void* data) | |
{ | |
assert(context !is null); | |
context.m_context.UpdateSubresource(m_buffer, 0, null, data, 0, 0); | |
} | |
} | |
public final class IndexBuffer | |
{ | |
private ID3D11Buffer m_buffer; | |
private dword m_count; | |
private IndexFormat m_format; | |
private this(ID3D11Device device, dword count, IndexFormat format, const void* data) | |
{ | |
D3D11_BUFFER_DESC desc = | |
{ | |
BindFlags : D3D11_BIND_INDEX_BUFFER, | |
ByteWidth : count * GetIndexSize(format), | |
CPUAccessFlags : 0, | |
MiscFlags : 0, | |
Usage : D3D11_USAGE_DEFAULT, | |
StructureByteStride : 0 | |
}; | |
D3D11_SUBRESOURCE_DATA dataDesc = { data, 0, 0 }; | |
HRESULT hr = device.CreateBuffer(desc, data ? &dataDesc : null, m_buffer); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create index buffer"); | |
m_count = count; | |
m_format = format; | |
} | |
public ~this() | |
{ | |
SafeRelease(m_buffer); | |
} | |
@property | |
public dword Count() const | |
{ | |
return m_count; | |
} | |
@property | |
public IndexFormat Format() const | |
{ | |
return m_format; | |
} | |
public void Update(RenderContext context, const void* data) | |
{ | |
assert(context !is null); | |
context.m_context.UpdateSubresource(m_buffer, 0, null, data, 0, 0); | |
} | |
} | |
public enum ShaderVariableType | |
{ | |
CBuffer, | |
Texture, | |
Sampler, | |
} | |
public struct ShaderVariable | |
{ | |
ShaderVariableType type; | |
dword slot; | |
} | |
private class Shader | |
{ | |
protected ShaderVariable[string] m_variables; | |
public ShaderVariable* GetVariable(string name) | |
{ | |
return name in m_variables; | |
} | |
protected final void ExtractVariables(const void* data, size_t size) | |
{ | |
com_ptr!ID3D11ShaderReflection reflector; | |
HRESULT hr = D3DReflect( | |
data, | |
size, | |
&IID_ID3D11ShaderReflection, | |
cast(void**)&reflector); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve shader reflection data"); | |
D3D11_SHADER_DESC desc = void; | |
hr = reflector.GetDesc(desc); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve shader information"); | |
for(dword i = 0; i < desc.BoundResources; i++) | |
{ | |
D3D11_SHADER_INPUT_BIND_DESC resourceDesc = void; | |
hr = reflector.GetResourceBindingDesc(i, resourceDesc); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve shader resource binding information"); | |
ShaderVariableType type; | |
switch(resourceDesc.Type) | |
{ | |
case D3D_SIT_CBUFFER: | |
type = ShaderVariableType.CBuffer; | |
break; | |
case D3D_SIT_TEXTURE: | |
type = ShaderVariableType.Texture; | |
break; | |
case D3D_SIT_SAMPLER: | |
type = ShaderVariableType.Sampler; | |
break; | |
default: | |
continue; | |
} | |
m_variables[to!string(resourceDesc.Name)] = ShaderVariable(type, resourceDesc.BindPoint); | |
} | |
} | |
} | |
public final class VertexShader : Shader | |
{ | |
private ID3D11VertexShader m_shader; | |
private this(ID3D11Device device, const void* data, size_t size) | |
{ | |
assert(data != null); | |
HRESULT hr = device.CreateVertexShader(data, size, null, m_shader); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create vertex shader"); | |
ExtractVariables(data, size); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_shader); | |
} | |
} | |
public final class FragmentShader : Shader | |
{ | |
private ID3D11PixelShader m_shader; | |
private this(ID3D11Device device, const void* data, size_t size) | |
{ | |
assert(data != null); | |
HRESULT hr = device.CreatePixelShader(data, size, null, m_shader); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create fragment shader"); | |
ExtractVariables(data, size); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_shader); | |
} | |
} | |
public final class GeometryShader : Shader | |
{ | |
private ID3D11GeometryShader m_shader; | |
private this(ID3D11Device device, const void* data, size_t size) | |
{ | |
assert(data != null); | |
HRESULT hr = device.CreateGeometryShader(data, size, null, m_shader); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create geometry shader"); | |
ExtractVariables(data, size); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_shader); | |
} | |
} | |
public final class HullShader : Shader | |
{ | |
private ID3D11HullShader m_shader; | |
private this(ID3D11Device device, const void* data, size_t size) | |
{ | |
assert(data != null); | |
HRESULT hr = device.CreateHullShader(data, size, null, m_shader); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create hull shader"); | |
ExtractVariables(data, size); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_shader); | |
} | |
} | |
public final class DomainShader : Shader | |
{ | |
private ID3D11DomainShader m_shader; | |
private this(ID3D11Device device, const void* data, size_t size) | |
{ | |
assert(data != null); | |
HRESULT hr = device.CreateDomainShader(data, size, null, m_shader); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create domain shader"); | |
ExtractVariables(data, size); | |
} | |
public ~this() | |
{ | |
SafeRelease(m_shader); | |
} | |
} | |
public final class VertexLayout | |
{ | |
private dword m_vertexSize; | |
private ID3D11InputLayout m_layout; | |
private this(ID3D11Device device, const VertexElement[] elements) | |
{ | |
D3D11_INPUT_ELEMENT_DESC[] d3dElements; | |
d3dElements.length = elements.length; | |
auto buf = appender!string(); | |
buf.put("struct VS_INPUT\n"); | |
buf.put("{\n"); | |
dword offset = 0; | |
for(dword i = 0; i < elements.length; i++) | |
{ | |
d3dElements[i].AlignedByteOffset = offset; | |
d3dElements[i].Format = type_cast!DXGI_FORMAT(elements[i].type); | |
d3dElements[i].SemanticIndex = elements[i].index; | |
d3dElements[i].SemanticName = GetSemanticName(elements[i].usage); | |
offset += GetSizeInBytes(elements[i].type); | |
formattedWrite(buf, "\t%s element%u : %s%u;\n", GetHLSLTypeName(elements[i].type), i, to!string(d3dElements[i].SemanticName), d3dElements[i].SemanticIndex); | |
} | |
buf.put("};\n\n"); | |
buf.put("void vs_main(VS_INPUT In)\n"); | |
buf.put("{\n"); | |
buf.put("}\n"); | |
ShaderCompiler compiler; | |
if(!compiler.Compile(buf.data, "vs_4_0", "vs_main")) | |
throw new RenderException("Failed to compile vertex shader"); | |
HRESULT hr = device.CreateInputLayout( | |
d3dElements.ptr, | |
d3dElements.length, | |
compiler.InputSignature, | |
compiler.InputSignatureSize, | |
m_layout); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create input layout"); | |
m_vertexSize = offset; | |
} | |
public ~this() | |
{ | |
SafeRelease(m_layout); | |
} | |
@property | |
public dword GetVertexSize() const | |
{ | |
return m_vertexSize; | |
} | |
} | |
/** | |
* Describes a sampler state. | |
*/ | |
public final class SamplerState | |
{ | |
private ID3D11SamplerState m_state; | |
private this(ID3D11Device device, const ref SamplerStateDesc desc) | |
{ | |
D3D11_SAMPLER_DESC desc = | |
{ | |
Filter : D3D11_ENCODE_BASIC_FILTER(type_cast!D3D11_FILTER_TYPE(desc.MinFilter), | |
type_cast!D3D11_FILTER_TYPE(desc.MagFilter), | |
type_cast!D3D11_FILTER_TYPE(desc.MipFilter), | |
false), | |
AddressU : type_cast!D3D11_TEXTURE_ADDRESS_MODE(desc.AddressU), | |
AddressV : type_cast!D3D11_TEXTURE_ADDRESS_MODE(desc.AddressV), | |
AddressW : type_cast!D3D11_TEXTURE_ADDRESS_MODE(desc.AddressW), | |
MipLODBias : 0.0f, | |
MaxAnisotropy : desc.MaxAnisotropy, | |
ComparisonFunc : D3D11_COMPARISON_NEVER, | |
BorderColor : [ 1.0f, 1.0f, 1.0f, 1.0f ], | |
MinLOD : -float.max, | |
MaxLOD : float.max | |
}; | |
HRESULT hr = device.CreateSamplerState(desc, m_state); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create sampler state object"); | |
} | |
} | |
/** | |
* Describes depth-stencil state. | |
*/ | |
public final class DepthStencilState | |
{ | |
private ID3D11DepthStencilState m_state; | |
private this(ID3D11Device device, const ref DepthStencilStateDesc desc) | |
{ | |
D3D11_DEPTH_STENCIL_DESC desc = | |
{ | |
DepthEnable : desc.DepthEnable, | |
DepthWriteMask : desc.DepthWriteEnable ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO, | |
DepthFunc : type_cast!D3D11_COMPARISON_FUNC(desc.DepthFunc), | |
StencilEnable : desc.StencilEnable, | |
StencilReadMask : D3D11_DEFAULT_STENCIL_READ_MASK, | |
StencilWriteMask : D3D11_DEFAULT_STENCIL_WRITE_MASK, | |
FrontFace : | |
{ | |
StencilFailOp : D3D11_STENCIL_OP_KEEP, | |
StencilDepthFailOp : D3D11_STENCIL_OP_KEEP, | |
StencilPassOp : D3D11_STENCIL_OP_KEEP, | |
StencilFunc : D3D11_COMPARISON_ALWAYS | |
}, | |
BackFace : | |
{ | |
StencilFailOp : D3D11_STENCIL_OP_KEEP, | |
StencilDepthFailOp : D3D11_STENCIL_OP_KEEP, | |
StencilPassOp : D3D11_STENCIL_OP_KEEP, | |
StencilFunc : D3D11_COMPARISON_ALWAYS | |
} | |
}; | |
HRESULT hr = device.CreateDepthStencilState(desc, m_state); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create depth-stencil state object"); | |
} | |
} | |
/** | |
* Describes rasterizer state. | |
*/ | |
public final class RasterizerState | |
{ | |
private ID3D11RasterizerState m_state; | |
private this(ID3D11Device device, const ref RasterizerStateDesc desc) | |
{ | |
D3D11_RASTERIZER_DESC desc = | |
{ | |
FillMode : type_cast!D3D11_FILL_MODE(desc.fillMode), | |
CullMode : type_cast!D3D11_CULL_MODE(desc.cullMode), | |
FrontCounterClockwise : false, | |
DepthBias : D3D11_DEFAULT_DEPTH_BIAS, | |
DepthBiasClamp : D3D11_DEFAULT_DEPTH_BIAS_CLAMP, | |
SlopeScaledDepthBias : D3D11_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, | |
DepthClipEnable : true, | |
ScissorEnable : false, | |
MultisampleEnable : false, | |
AntialiasedLineEnable : false | |
}; | |
HRESULT hr = device.CreateRasterizerState(desc, m_state); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create rasterizer state object"); | |
} | |
} | |
/** | |
* Describes blend state. | |
*/ | |
public final class BlendState | |
{ | |
private ID3D11BlendState m_state; | |
private this(ID3D11Device device, const ref BlendStateDesc desc) | |
{ | |
D3D11_BLEND_DESC desc = | |
{ | |
AlphaToCoverageEnable : desc.alphaToCoverageEnable, | |
IndependentBlendEnable : false, | |
RenderTarget : | |
[ | |
{ | |
BlendEnable : desc.blendEnable, | |
SrcBlend : type_cast!D3D11_BLEND(desc.srcBlend), | |
DestBlend : type_cast!D3D11_BLEND(desc.destBlend), | |
BlendOp : type_cast!D3D11_BLEND_OP(desc.blendOp), | |
SrcBlendAlpha : type_cast!D3D11_BLEND(desc.srcBlendAlpha), | |
DestBlendAlpha : type_cast!D3D11_BLEND(desc.destBlendAlpha), | |
BlendOpAlpha : type_cast!D3D11_BLEND_OP(desc.blendOpAlpha), | |
RenderTargetWriteMask : D3D11_COLOR_WRITE_ENABLE_ALL | |
} | |
] | |
}; | |
HRESULT hr = device.CreateBlendState(desc, m_state); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to create blend state object"); | |
} | |
} | |
public dword GetIndexSize(IndexFormat format) | |
{ | |
final switch(format) | |
{ | |
case IndexFormat.I16: return word.sizeof; | |
case IndexFormat.I32: return dword.sizeof; | |
} | |
} | |
public bool IsColorFormat(SurfaceFormat format) | |
{ | |
switch(format) | |
{ | |
case SurfaceFormat.R8: | |
case SurfaceFormat.RG8: | |
case SurfaceFormat.RGB8: | |
case SurfaceFormat.RGBA8: | |
case SurfaceFormat.R16: | |
case SurfaceFormat.R16F: | |
case SurfaceFormat.RG16: | |
case SurfaceFormat.RG16F: | |
case SurfaceFormat.RGBA16: | |
case SurfaceFormat.RGBA16F: | |
case SurfaceFormat.R32F: | |
case SurfaceFormat.RG32F: | |
case SurfaceFormat.RGB32F: | |
case SurfaceFormat.RGBA32F: | |
return true; | |
default: | |
return false; | |
} | |
} | |
public bool IsDepthStencilFormat(SurfaceFormat format) | |
{ | |
switch(format) | |
{ | |
case SurfaceFormat.D16: | |
case SurfaceFormat.D24S8: | |
case SurfaceFormat.D32F: | |
return true; | |
default: | |
return false; | |
} | |
} | |
public bool IsCompressedFormat(SurfaceFormat format) | |
{ | |
switch(format) | |
{ | |
case SurfaceFormat.DXT1: | |
case SurfaceFormat.DXT2: | |
case SurfaceFormat.DXT3: | |
case SurfaceFormat.DXT4: | |
case SurfaceFormat.DXT5: | |
return true; | |
default: | |
return false; | |
} | |
} | |
// Returns pixel size for not compressed formats, block size - for compressed | |
public dword GetNumBytesPerUnit(SurfaceFormat format) | |
{ | |
final switch(format) | |
{ | |
case SurfaceFormat.Unknown: return 0; | |
// Color formats | |
case SurfaceFormat.R8: return 1; | |
case SurfaceFormat.RG8: return 2; | |
case SurfaceFormat.RGB8: return 3; | |
case SurfaceFormat.RGBA8: return 4; | |
case SurfaceFormat.R16: return 2; | |
case SurfaceFormat.R16F: return 2; | |
case SurfaceFormat.RG16: return 4; | |
case SurfaceFormat.RG16F: return 4; | |
case SurfaceFormat.RGBA16: return 8; | |
case SurfaceFormat.RGBA16F: return 8; | |
case SurfaceFormat.R32F: return 4; | |
case SurfaceFormat.RG32F: return 8; | |
case SurfaceFormat.RGB32F: return 12; | |
case SurfaceFormat.RGBA32F: return 16; | |
// Compressed formats | |
case SurfaceFormat.DXT1: return 8; | |
case SurfaceFormat.DXT2: | |
case SurfaceFormat.DXT3: | |
case SurfaceFormat.DXT4: | |
case SurfaceFormat.DXT5: return 16; | |
// Depth-Stencil formats | |
case SurfaceFormat.D16: return 2; | |
case SurfaceFormat.D24S8: return 4; | |
case SurfaceFormat.D32F: return 4; | |
} | |
} | |
public dword CalculateImageSize(dword width, dword height, dword depth, SurfaceFormat format) | |
{ | |
assert(width > 0); | |
assert(height > 0); | |
assert(depth > 0); | |
assert(format != SurfaceFormat.Unknown); | |
dword size; | |
if(IsCompressedFormat(format)) | |
size = ((width + 3) >> 2) * ((height + 3) >> 2) * depth; | |
else | |
size = width * height * depth; | |
return size * GetNumBytesPerUnit(format); | |
} | |
public dword CalculateImageSize(dword width, dword height, dword depth, SurfaceFormat format, dword mipMaps) | |
{ | |
assert(width > 0); | |
assert(height > 0); | |
assert(depth > 0); | |
assert(format != SurfaceFormat.Unknown); | |
dword size = 0; | |
for(dword i = 0; i < mipMaps; ++i) | |
{ | |
size += CalculateImageSize(width, height, depth, format); | |
if(width > 1) width >>= 1; | |
if(height > 1) height >>= 1; | |
if(depth > 1) depth >>= 1; | |
} | |
return size; | |
} | |
public dword CalculateNumMipLevels(dword width, dword height, dword depth) | |
{ | |
assert(width > 0); | |
assert(height > 0); | |
assert(depth > 0); | |
dword count = 1; | |
while(width > 1 || height > 1 || depth > 1) | |
{ | |
++count; | |
if(width > 1) width >>= 1; | |
if(height > 1) height >>= 1; | |
if(depth > 1) depth >>= 1; | |
} | |
return count; | |
} | |
private: | |
struct ShaderCompiler | |
{ | |
private ID3DBlob m_errors; | |
private ID3DBlob m_compiledCode; | |
private ID3DBlob m_inputSignature; | |
public ~this() | |
{ | |
SafeRelease(m_errors); | |
SafeRelease(m_compiledCode); | |
SafeRelease(m_inputSignature); | |
} | |
public bool Compile(string code, string profile, string entryPoint) | |
{ | |
SafeRelease(m_errors); | |
SafeRelease(m_compiledCode); | |
SafeRelease(m_inputSignature); | |
HRESULT hr = D3DCompile( | |
toStringz(code), | |
code.length, | |
null, | |
null, | |
null, | |
toStringz(entryPoint), | |
toStringz(profile), | |
0, | |
0, | |
m_compiledCode, | |
&m_errors); | |
if(FAILED(hr)) | |
{ | |
Log.Error("Shader Compiler: Failed to compile shader using profile \"%s\"", profile); | |
if(m_errors !is null) | |
Log.Error("Shader Compiler: %s", to!string(cast(const char*)m_errors.GetBufferPointer())); | |
return false; | |
} | |
hr = D3DGetInputSignatureBlob( | |
m_compiledCode.GetBufferPointer(), | |
m_compiledCode.GetBufferSize(), | |
m_inputSignature); | |
return SUCCEEDED(hr); | |
} | |
@property | |
public size_t CompiledSize() | |
{ | |
assert(m_compiledCode !is null); | |
return m_compiledCode.GetBufferSize(); | |
} | |
@property | |
public const(void)* CompiledCode() | |
{ | |
assert(m_compiledCode !is null); | |
return m_compiledCode.GetBufferPointer(); | |
} | |
@property | |
public size_t InputSignatureSize() | |
{ | |
assert(m_inputSignature !is null); | |
return m_inputSignature.GetBufferSize(); | |
} | |
@property | |
public const(void)* InputSignature() | |
{ | |
assert(m_inputSignature !is null); | |
return m_inputSignature.GetBufferPointer(); | |
} | |
} | |
bool CheckFormatSupport(ID3D11Device device, SurfaceFormat format, ref const(MultiSampleType) multiSampleType) | |
{ | |
assert(device !is null); | |
DXGI_FORMAT internalFormat = type_cast!DXGI_FORMAT(format); | |
dword supportCaps; | |
HRESULT hr = device.CheckFormatSupport(internalFormat, supportCaps); | |
if(FAILED(hr)) | |
return false; | |
if(!(supportCaps & (D3D11_FORMAT_SUPPORT_RENDER_TARGET | D3D11_FORMAT_SUPPORT_DEPTH_STENCIL))) | |
return false; | |
if(multiSampleType != MultiSampleType.None) | |
{ | |
if(!(supportCaps & (D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET))) | |
return false; | |
dword numLevels; | |
hr = device.CheckMultisampleQualityLevels(internalFormat, multiSampleType.SampleCount, numLevels); | |
if(FAILED(hr)) | |
return false; | |
if(multiSampleType.Quality >= numLevels) | |
return false; | |
} | |
return true; | |
} | |
D3D11_SUBRESOURCE_DATA[] FillInitialData(dword width, dword height, dword depth, SurfaceFormat format, dword mipLevels, dword arraySize, const void* data) | |
{ | |
D3D11_SUBRESOURCE_DATA[] initData; | |
initData.length = arraySize * mipLevels; | |
const(ubyte)* src = cast(const(ubyte)*)data; | |
dword k = 0; | |
for(dword i = 0; i < arraySize; i++) | |
{ | |
dword layerWidth = width; | |
dword layerHeight = height; | |
dword layerDepth = depth; | |
for(dword j = 0; j < mipLevels; j++) | |
{ | |
initData[k].pSysMem = src; | |
initData[k].SysMemPitch = height > 1 ? CalculateImageSize(layerWidth, 1, 1, format) : 0; | |
initData[k].SysMemSlicePitch = depth > 1 ? CalculateImageSize(layerWidth, layerHeight, 1, format) : 0; | |
src += CalculateImageSize(layerWidth, layerHeight, layerDepth, format); | |
if(layerWidth > 1) layerWidth >>= 1; | |
if(layerHeight > 1) layerHeight >>= 1; | |
if(layerDepth > 1) layerDepth >>= 1; | |
k++; | |
} | |
} | |
return initData; | |
} | |
struct DepthStencilFormatDesc | |
{ | |
DXGI_FORMAT surfaceFormat; | |
DXGI_FORMAT DSVFormat; | |
DXGI_FORMAT SRVFormat; | |
} | |
immutable DepthStencilFormatDesc[3] internalFormats = | |
[ | |
// surface format DSV format SRV format | |
{ DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_D16_UNORM, DXGI_FORMAT_R16_UNORM }, | |
{ DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_D24_UNORM_S8_UINT, DXGI_FORMAT_R24_UNORM_X8_TYPELESS }, | |
{ DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_D32_FLOAT, DXGI_FORMAT_R32_FLOAT }, | |
]; | |
dword GetSizeInBytes(VertexElementType type) | |
{ | |
final switch(type) | |
{ | |
case VertexElementType.Float: return float.sizeof; | |
case VertexElementType.Float2: return float.sizeof * 2; | |
case VertexElementType.Float3: return float.sizeof * 3; | |
case VertexElementType.Float4: return float.sizeof * 4; | |
case VertexElementType.Half2: return 2 * 2; | |
case VertexElementType.Half4: return 2 * 4; | |
case VertexElementType.UByte4: return ubyte.sizeof * 4; | |
case VertexElementType.UByte4N: return ubyte.sizeof * 4; | |
} | |
} | |
const(char)* GetSemanticName(VertexElementUsage usage) | |
{ | |
final switch(usage) | |
{ | |
case VertexElementUsage.Position: return "POSITION"; | |
case VertexElementUsage.Normal: return "NORMAL"; | |
case VertexElementUsage.Tangent: return "TANGENT"; | |
case VertexElementUsage.Binormal: return "BINORMAL"; | |
case VertexElementUsage.TextureCoord: return "TEXCOORD"; | |
case VertexElementUsage.Color: return "COLOR"; | |
case VertexElementUsage.BlendWeight: return "BLENDWEIGHT"; | |
case VertexElementUsage.BlendIndices: return "BLENDINDICES"; | |
} | |
} | |
string GetHLSLTypeName(VertexElementType type) | |
{ | |
final switch(type) | |
{ | |
case VertexElementType.Float: return "float"; | |
case VertexElementType.Float2: return "float2"; | |
case VertexElementType.Float3: return "float3"; | |
case VertexElementType.Float4: return "float4"; | |
case VertexElementType.Half2: return "half2"; | |
case VertexElementType.Half4: return "half4"; | |
case VertexElementType.UByte4: return "uint4"; | |
case VertexElementType.UByte4N: return "float4"; | |
} | |
} | |
D3D11_BLEND type_cast(T : D3D11_BLEND)(BlendArg value) | |
{ | |
final switch(value) | |
{ | |
case BlendArg.Zero: return D3D11_BLEND_ZERO; | |
case BlendArg.One: return D3D11_BLEND_ONE; | |
case BlendArg.SrcColor: return D3D11_BLEND_SRC_COLOR; | |
case BlendArg.InvSrcColor: return D3D11_BLEND_INV_SRC_COLOR; | |
case BlendArg.SrcAlpha: return D3D11_BLEND_SRC_ALPHA; | |
case BlendArg.InvSrcAlpha: return D3D11_BLEND_INV_SRC_ALPHA; | |
case BlendArg.DestAlpha: return D3D11_BLEND_DEST_ALPHA; | |
case BlendArg.InvDestAlpha: return D3D11_BLEND_INV_DEST_ALPHA; | |
case BlendArg.DestColor: return D3D11_BLEND_DEST_COLOR; | |
case BlendArg.InvDestColor: return D3D11_BLEND_INV_DEST_COLOR; | |
} | |
} | |
D3D11_BLEND_OP type_cast(T : D3D11_BLEND_OP)(BlendOp value) | |
{ | |
final switch(value) | |
{ | |
case BlendOp.Add: return D3D11_BLEND_OP_ADD; | |
case BlendOp.Subtract: return D3D11_BLEND_OP_SUBTRACT; | |
case BlendOp.RevSubtract: return D3D11_BLEND_OP_REV_SUBTRACT; | |
case BlendOp.Min: return D3D11_BLEND_OP_MIN; | |
case BlendOp.Max: return D3D11_BLEND_OP_MAX; | |
} | |
} | |
D3D11_COMPARISON_FUNC type_cast(T : D3D11_COMPARISON_FUNC)(ComparisonFunc value) | |
{ | |
final switch(value) | |
{ | |
case ComparisonFunc.Never: return D3D11_COMPARISON_NEVER; | |
case ComparisonFunc.Less: return D3D11_COMPARISON_LESS; | |
case ComparisonFunc.Equal: return D3D11_COMPARISON_EQUAL; | |
case ComparisonFunc.LessEqual: return D3D11_COMPARISON_LESS_EQUAL; | |
case ComparisonFunc.Greater: return D3D11_COMPARISON_GREATER; | |
case ComparisonFunc.NotEqual: return D3D11_COMPARISON_NOT_EQUAL; | |
case ComparisonFunc.GreaterEqual: return D3D11_COMPARISON_GREATER_EQUAL; | |
case ComparisonFunc.Always: return D3D11_COMPARISON_ALWAYS; | |
} | |
} | |
D3D11_CULL_MODE type_cast(T : D3D11_CULL_MODE)(CullMode value) | |
{ | |
final switch(value) | |
{ | |
case CullMode.None: return D3D11_CULL_NONE; | |
case CullMode.Back: return D3D11_CULL_BACK; | |
case CullMode.Front: return D3D11_CULL_FRONT; | |
} | |
} | |
D3D11_FILL_MODE type_cast(T : D3D11_FILL_MODE)(FillMode value) | |
{ | |
final switch(value) | |
{ | |
case FillMode.Solid: return D3D11_FILL_SOLID; | |
case FillMode.Wireframe: return D3D11_FILL_WIREFRAME; | |
} | |
} | |
DXGI_FORMAT type_cast(T : DXGI_FORMAT)(IndexFormat value) | |
{ | |
final switch(value) | |
{ | |
case IndexFormat.I16: return DXGI_FORMAT_R16_UINT; | |
case IndexFormat.I32: return DXGI_FORMAT_R32_UINT; | |
} | |
} | |
D3D11_PRIMITIVE_TOPOLOGY type_cast(T : D3D11_PRIMITIVE_TOPOLOGY)(PrimitiveType value) | |
{ | |
final switch(value) | |
{ | |
case PrimitiveType.Unknown: return D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; | |
case PrimitiveType.PointList: return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; | |
case PrimitiveType.LineList: return D3D11_PRIMITIVE_TOPOLOGY_LINELIST; | |
case PrimitiveType.LineStrip: return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP; | |
case PrimitiveType.TriangleList: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; | |
case PrimitiveType.TriangleStrip: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; | |
} | |
} | |
D3D11_USAGE type_cast(T : D3D11_USAGE)(ResourceUsage value) | |
{ | |
final switch(value) | |
{ | |
case ResourceUsage.Default: return D3D11_USAGE_DEFAULT; | |
case ResourceUsage.Immutable: return D3D11_USAGE_IMMUTABLE; | |
case ResourceUsage.Dynamic: return D3D11_USAGE_DYNAMIC; | |
} | |
} | |
DXGI_FORMAT type_cast(T : DXGI_FORMAT)(SurfaceFormat value) | |
{ | |
final switch(value) | |
{ | |
case SurfaceFormat.Unknown: return DXGI_FORMAT_UNKNOWN; | |
// Color formats | |
case SurfaceFormat.R8: return DXGI_FORMAT_R8_UNORM; | |
case SurfaceFormat.RG8: return DXGI_FORMAT_R8G8_UNORM; | |
case SurfaceFormat.RGB8: return DXGI_FORMAT_UNKNOWN; // No equivalent | |
case SurfaceFormat.RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; | |
case SurfaceFormat.R16: return DXGI_FORMAT_R16_UNORM; | |
case SurfaceFormat.R16F: return DXGI_FORMAT_R16_FLOAT; | |
case SurfaceFormat.RG16: return DXGI_FORMAT_R16G16_UNORM; | |
case SurfaceFormat.RG16F: return DXGI_FORMAT_R16G16_FLOAT; | |
case SurfaceFormat.RGBA16: return DXGI_FORMAT_R16G16B16A16_UNORM; | |
case SurfaceFormat.RGBA16F: return DXGI_FORMAT_R16G16B16A16_FLOAT; | |
case SurfaceFormat.R32F: return DXGI_FORMAT_R32_FLOAT; | |
case SurfaceFormat.RG32F: return DXGI_FORMAT_R32G32_FLOAT; | |
case SurfaceFormat.RGB32F: return DXGI_FORMAT_R32G32B32_FLOAT; | |
case SurfaceFormat.RGBA32F: return DXGI_FORMAT_R32G32B32A32_FLOAT; | |
// Compressed formats | |
case SurfaceFormat.DXT1: return DXGI_FORMAT_BC1_UNORM; | |
case SurfaceFormat.DXT2: | |
case SurfaceFormat.DXT3: | |
case SurfaceFormat.DXT4: return DXGI_FORMAT_BC2_UNORM; | |
case SurfaceFormat.DXT5: return DXGI_FORMAT_BC3_UNORM; | |
// Depth-Stencil formats | |
case SurfaceFormat.D16: return DXGI_FORMAT_D16_UNORM; | |
case SurfaceFormat.D24S8: return DXGI_FORMAT_D24_UNORM_S8_UINT; | |
case SurfaceFormat.D32F: return DXGI_FORMAT_D32_FLOAT; | |
} | |
} | |
D3D11_TEXTURE_ADDRESS_MODE type_cast(T : D3D11_TEXTURE_ADDRESS_MODE)(TextureAddressMode value) | |
{ | |
final switch(value) | |
{ | |
case TextureAddressMode.Clamp: return D3D11_TEXTURE_ADDRESS_CLAMP; | |
case TextureAddressMode.Wrap: return D3D11_TEXTURE_ADDRESS_WRAP; | |
case TextureAddressMode.Mirror: return D3D11_TEXTURE_ADDRESS_MIRROR; | |
case TextureAddressMode.Border: return D3D11_TEXTURE_ADDRESS_BORDER; | |
} | |
} | |
D3D11_FILTER_TYPE type_cast(T : D3D11_FILTER_TYPE)(TextureFilter value) | |
{ | |
final switch(value) | |
{ | |
case TextureFilter.Point: return D3D11_FILTER_TYPE_POINT; | |
case TextureFilter.Linear: return D3D11_FILTER_TYPE_LINEAR; | |
} | |
} | |
DXGI_FORMAT type_cast(T : DXGI_FORMAT)(VertexElementType value) | |
{ | |
final switch(value) | |
{ | |
case VertexElementType.Float: return DXGI_FORMAT_R32_FLOAT; | |
case VertexElementType.Float2: return DXGI_FORMAT_R32G32_FLOAT; | |
case VertexElementType.Float3: return DXGI_FORMAT_R32G32B32_FLOAT; | |
case VertexElementType.Float4: return DXGI_FORMAT_R32G32B32A32_FLOAT; | |
case VertexElementType.Half2: return DXGI_FORMAT_R16G16_FLOAT; | |
case VertexElementType.Half4: return DXGI_FORMAT_R16G16B16A16_FLOAT; | |
case VertexElementType.UByte4: return DXGI_FORMAT_R8G8B8A8_UINT; | |
case VertexElementType.UByte4N: return DXGI_FORMAT_R8G8B8A8_UNORM; | |
} | |
} | |
dword toHz(ref const(DXGI_RATIONAL) refreshRate) | |
{ | |
return roundTo!uint(refreshRate.Numerator / cast(float)refreshRate.Denominator); | |
} | |
DXGI_MODE_DESC[] GetDXGIModeList(IDXGIOutput output, DXGI_FORMAT format) | |
{ | |
dword numModes; | |
HRESULT hr = output.GetDisplayModeList(format, 0, numModes, null); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve display mode list"); | |
DXGI_MODE_DESC[] modes; | |
modes.length = numModes; | |
hr = output.GetDisplayModeList(format, 0, numModes, modes.ptr); | |
if(FAILED(hr)) | |
throw new RenderException("Failed to retrieve display mode list"); | |
return modes; | |
} | |
DXGI_MODE_DESC GetMatchingDXGIMode(ID3D11Device device, IDXGIOutput output, SurfaceFormat format, ref const(DisplayMode) mode, bool fullscreen) | |
{ | |
DXGI_FORMAT internalFormat = type_cast!DXGI_FORMAT(format); | |
if(fullscreen) | |
{ | |
DXGI_MODE_DESC[] modes = GetDXGIModeList(output, internalFormat); | |
for(dword i = 0; i < modes.length; i++) | |
{ | |
if(mode.width == modes[i].Width && | |
mode.height == modes[i].Height && | |
mode.refreshRate == toHz(modes[i].RefreshRate)) | |
{ | |
return modes[i]; | |
} | |
} | |
DXGI_MODE_DESC desc = | |
{ | |
Format : internalFormat, | |
Width : mode.width, | |
Height : mode.height, | |
RefreshRate : | |
{ | |
Numerator : mode.refreshRate, | |
Denominator : 1 | |
} | |
}; | |
HRESULT hr = output.FindClosestMatchingMode(desc, desc, device); | |
if(FAILED(hr)) | |
throw new RenderException("Cannot find suitable display mode"); | |
Log.Warning("Requested display mode is not available. Falling back to %ux%u %uHz mode", desc.Width, desc.Height, toHz(desc.RefreshRate)); | |
return desc; | |
} | |
else | |
{ | |
DXGI_MODE_DESC desc = | |
{ | |
Format : internalFormat, | |
Width : mode.width, | |
Height : mode.height | |
}; | |
return desc; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment