//-------------------------------------------------------------------------------------- // File: GeometricPrimitive.cpp // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248929 //-------------------------------------------------------------------------------------- #include "pch.h" #include "GeometricPrimitive.h" #include "Effects.h" #include "CommonStates.h" #include "DirectXHelpers.h" #include "SharedResourcePool.h" #include "Geometry.h" using namespace DirectX; using Microsoft::WRL::ComPtr; namespace { // Helper for creating a D3D vertex or index buffer. template static void CreateBuffer(_In_ ID3D11Device* device, T const& data, D3D11_BIND_FLAG bindFlags, _Outptr_ ID3D11Buffer** pBuffer) { assert(pBuffer != 0); D3D11_BUFFER_DESC bufferDesc = {}; bufferDesc.ByteWidth = (UINT)data.size() * sizeof(typename T::value_type); bufferDesc.BindFlags = bindFlags; bufferDesc.Usage = D3D11_USAGE_DEFAULT; D3D11_SUBRESOURCE_DATA dataDesc = {}; dataDesc.pSysMem = data.data(); ThrowIfFailed( device->CreateBuffer(&bufferDesc, &dataDesc, pBuffer) ); _Analysis_assume_(*pBuffer != 0); SetDebugObjectName(*pBuffer, "DirectXTK:GeometricPrimitive"); } // Helper for creating a D3D input layout. void CreateInputLayout(_In_ ID3D11Device* device, IEffect* effect, _Outptr_ ID3D11InputLayout** pInputLayout) { assert(pInputLayout != 0); void const* shaderByteCode; size_t byteCodeLength; effect->GetVertexShaderBytecode(&shaderByteCode, &byteCodeLength); ThrowIfFailed( device->CreateInputLayout(VertexPositionNormalTexture::InputElements, VertexPositionNormalTexture::InputElementCount, shaderByteCode, byteCodeLength, pInputLayout) ); _Analysis_assume_(*pInputLayout != 0); SetDebugObjectName(*pInputLayout, "DirectXTK:GeometricPrimitive"); } } // Internal GeometricPrimitive implementation class. class GeometricPrimitive::Impl { public: void Initialize(_In_ ID3D11DeviceContext* deviceContext, const VertexCollection& vertices, const IndexCollection& indices); void XM_CALLCONV Draw(FXMMATRIX world, CXMMATRIX view, CXMMATRIX projection, FXMVECTOR color, _In_opt_ ID3D11ShaderResourceView* texture, bool wireframe, std::function& setCustomState) const; void Draw(_In_ IEffect* effect, _In_ ID3D11InputLayout* inputLayout, bool alpha, bool wireframe, std::function& setCustomState) const; void CreateInputLayout(_In_ IEffect* effect, _Outptr_ ID3D11InputLayout** inputLayout) const; private: ComPtr mVertexBuffer; ComPtr mIndexBuffer; UINT mIndexCount; // Only one of these helpers is allocated per D3D device context, even if there are multiple GeometricPrimitive instances. class SharedResources { public: SharedResources(_In_ ID3D11DeviceContext* deviceContext); void PrepareForRendering(bool alpha, bool wireframe) const; ComPtr deviceContext; std::unique_ptr effect; ComPtr inputLayoutTextured; ComPtr inputLayoutUntextured; std::unique_ptr stateObjects; }; // Per-device-context data. std::shared_ptr mResources; static SharedResourcePool sharedResourcesPool; }; // Global pool of per-device-context GeometricPrimitive resources. SharedResourcePool GeometricPrimitive::Impl::sharedResourcesPool; // Per-device-context constructor. GeometricPrimitive::Impl::SharedResources::SharedResources(_In_ ID3D11DeviceContext* deviceContext) : deviceContext(deviceContext) { ComPtr device; deviceContext->GetDevice(&device); // Create the BasicEffect. effect = std::make_unique(device.Get()); effect->EnableDefaultLighting(); // Create state objects. stateObjects = std::make_unique(device.Get()); // Create input layouts. effect->SetTextureEnabled(true); ::CreateInputLayout(device.Get(), effect.get(), &inputLayoutTextured); effect->SetTextureEnabled(false); ::CreateInputLayout(device.Get(), effect.get(), &inputLayoutUntextured); } // Sets up D3D device state ready for drawing a primitive. void GeometricPrimitive::Impl::SharedResources::PrepareForRendering(bool alpha, bool wireframe) const { // Set the blend and depth stencil state. ID3D11BlendState* blendState; ID3D11DepthStencilState* depthStencilState; if (alpha) { // Alpha blended rendering. blendState = stateObjects->AlphaBlend(); depthStencilState = stateObjects->DepthRead(); } else { // Opaque rendering. blendState = stateObjects->Opaque(); depthStencilState = stateObjects->DepthDefault(); } deviceContext->OMSetBlendState(blendState, nullptr, 0xFFFFFFFF); deviceContext->OMSetDepthStencilState(depthStencilState, 0); // Set the rasterizer state. if (wireframe) deviceContext->RSSetState(stateObjects->Wireframe()); else deviceContext->RSSetState(stateObjects->CullCounterClockwise()); ID3D11SamplerState* samplerState = stateObjects->LinearWrap(); deviceContext->PSSetSamplers(0, 1, &samplerState); } // Initializes a geometric primitive instance that will draw the specified vertex and index data. _Use_decl_annotations_ void GeometricPrimitive::Impl::Initialize(ID3D11DeviceContext* deviceContext, const VertexCollection& vertices, const IndexCollection& indices) { if (vertices.size() >= USHRT_MAX) throw std::exception("Too many vertices for 16-bit index buffer"); mResources = sharedResourcesPool.DemandCreate(deviceContext); ComPtr device; deviceContext->GetDevice(&device); CreateBuffer(device.Get(), vertices, D3D11_BIND_VERTEX_BUFFER, &mVertexBuffer); CreateBuffer(device.Get(), indices, D3D11_BIND_INDEX_BUFFER, &mIndexBuffer); mIndexCount = static_cast(indices.size()); } // Draws the primitive. _Use_decl_annotations_ void XM_CALLCONV GeometricPrimitive::Impl::Draw( FXMMATRIX world, CXMMATRIX view, CXMMATRIX projection, FXMVECTOR color, ID3D11ShaderResourceView* texture, bool wireframe, std::function& setCustomState) const { assert(mResources != 0); auto effect = mResources->effect.get(); assert(effect != 0); ID3D11InputLayout *inputLayout; if (texture) { effect->SetTextureEnabled(true); effect->SetTexture(texture); inputLayout = mResources->inputLayoutTextured.Get(); } else { effect->SetTextureEnabled(false); inputLayout = mResources->inputLayoutUntextured.Get(); } // Set effect parameters. effect->SetMatrices(world, view, projection); effect->SetColorAndAlpha(color); float alpha = XMVectorGetW(color); Draw(effect, inputLayout, (alpha < 1.f), wireframe, setCustomState); } // Draw the primitive using a custom effect. _Use_decl_annotations_ void GeometricPrimitive::Impl::Draw( IEffect* effect, ID3D11InputLayout* inputLayout, bool alpha, bool wireframe, std::function& setCustomState) const { assert(mResources != 0); auto deviceContext = mResources->deviceContext.Get(); assert(deviceContext != 0); // Set state objects. mResources->PrepareForRendering(alpha, wireframe); // Set input layout. assert(inputLayout != 0); deviceContext->IASetInputLayout(inputLayout); // Activate our shaders, constant buffers, texture, etc. assert(effect != 0); effect->Apply(deviceContext); // Set the vertex and index buffer. auto vertexBuffer = mVertexBuffer.Get(); UINT vertexStride = sizeof(VertexPositionNormalTexture); UINT vertexOffset = 0; deviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &vertexStride, &vertexOffset); deviceContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); // Hook lets the caller replace our shaders or state settings with whatever else they see fit. if (setCustomState) { setCustomState(); } // Draw the primitive. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->DrawIndexed(mIndexCount, 0, 0); } // Create input layout for drawing with a custom effect. _Use_decl_annotations_ void GeometricPrimitive::Impl::CreateInputLayout(IEffect* effect, ID3D11InputLayout** inputLayout) const { assert(effect != 0); assert(inputLayout != 0); assert(mResources != 0); auto deviceContext = mResources->deviceContext.Get(); assert(deviceContext != 0); ComPtr device; deviceContext->GetDevice(&device); ::CreateInputLayout(device.Get(), effect, inputLayout); } //-------------------------------------------------------------------------------------- // GeometricPrimitive //-------------------------------------------------------------------------------------- // Constructor. GeometricPrimitive::GeometricPrimitive() : pImpl(new Impl()) { } // Destructor. GeometricPrimitive::~GeometricPrimitive() { } // Public entrypoints. _Use_decl_annotations_ void XM_CALLCONV GeometricPrimitive::Draw( FXMMATRIX world, CXMMATRIX view, CXMMATRIX projection, FXMVECTOR color, ID3D11ShaderResourceView* texture, bool wireframe, std::function setCustomState) const { pImpl->Draw(world, view, projection, color, texture, wireframe, setCustomState); } _Use_decl_annotations_ void GeometricPrimitive::Draw( IEffect* effect, ID3D11InputLayout* inputLayout, bool alpha, bool wireframe, std::function setCustomState) const { pImpl->Draw(effect, inputLayout, alpha, wireframe, setCustomState); } _Use_decl_annotations_ void GeometricPrimitive::CreateInputLayout(IEffect* effect, ID3D11InputLayout** inputLayout) const { pImpl->CreateInputLayout(effect, inputLayout); } //-------------------------------------------------------------------------------------- // Cube (aka a Hexahedron) or Box //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateCube( ID3D11DeviceContext* deviceContext, float size, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeBox(vertices, indices, XMFLOAT3(size, size, size), rhcoords, false); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateCube( std::vector& vertices, std::vector& indices, float size, bool rhcoords) { ComputeBox(vertices, indices, XMFLOAT3(size, size, size), rhcoords, false); } // Creates a box primitive. _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateBox( ID3D11DeviceContext* deviceContext, const XMFLOAT3& size, bool rhcoords, bool invertn) { VertexCollection vertices; IndexCollection indices; ComputeBox(vertices, indices, size, rhcoords, invertn); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateBox( std::vector& vertices, std::vector& indices, const XMFLOAT3& size, bool rhcoords, bool invertn) { ComputeBox(vertices, indices, size, rhcoords, invertn); } //-------------------------------------------------------------------------------------- // Sphere //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateSphere( ID3D11DeviceContext* deviceContext, float diameter, size_t tessellation, bool rhcoords, bool invertn) { VertexCollection vertices; IndexCollection indices; ComputeSphere(vertices, indices, diameter, tessellation, rhcoords, invertn); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateSphere( std::vector& vertices, std::vector& indices, float diameter, size_t tessellation, bool rhcoords, bool invertn) { ComputeSphere(vertices, indices, diameter, tessellation, rhcoords, invertn); } //-------------------------------------------------------------------------------------- // Geodesic sphere //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateGeoSphere( ID3D11DeviceContext* deviceContext, float diameter, size_t tessellation, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeGeoSphere(vertices, indices, diameter, tessellation, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateGeoSphere( std::vector& vertices, std::vector& indices, float diameter, size_t tessellation, bool rhcoords) { ComputeGeoSphere(vertices, indices, diameter, tessellation, rhcoords); } //-------------------------------------------------------------------------------------- // Cylinder / Cone //-------------------------------------------------------------------------------------- // Creates a cylinder primitive. _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateCylinder( ID3D11DeviceContext* deviceContext, float height, float diameter, size_t tessellation, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeCylinder(vertices, indices, height, diameter, tessellation, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateCylinder( std::vector& vertices, std::vector& indices, float height, float diameter, size_t tessellation, bool rhcoords) { ComputeCylinder(vertices, indices, height, diameter, tessellation, rhcoords); } // Creates a cone primitive. _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateCone( ID3D11DeviceContext* deviceContext, float diameter, float height, size_t tessellation, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeCone(vertices, indices, diameter, height, tessellation, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateCone( std::vector& vertices, std::vector& indices, float diameter, float height, size_t tessellation, bool rhcoords) { ComputeCone(vertices, indices, diameter, height, tessellation, rhcoords); } //-------------------------------------------------------------------------------------- // Torus //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateTorus( ID3D11DeviceContext* deviceContext, float diameter, float thickness, size_t tessellation, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeTorus(vertices, indices, diameter, thickness, tessellation, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateTorus( std::vector& vertices, std::vector& indices, float diameter, float thickness, size_t tessellation, bool rhcoords) { ComputeTorus(vertices, indices, diameter, thickness, tessellation, rhcoords); } //-------------------------------------------------------------------------------------- // Tetrahedron //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateTetrahedron( ID3D11DeviceContext* deviceContext, float size, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeTetrahedron(vertices, indices, size, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateTetrahedron( std::vector& vertices, std::vector& indices, float size, bool rhcoords) { ComputeTetrahedron(vertices, indices, size, rhcoords); } //-------------------------------------------------------------------------------------- // Octahedron //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateOctahedron( ID3D11DeviceContext* deviceContext, float size, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeOctahedron(vertices, indices, size, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateOctahedron( std::vector& vertices, std::vector& indices, float size, bool rhcoords) { ComputeOctahedron(vertices, indices, size, rhcoords); } //-------------------------------------------------------------------------------------- // Dodecahedron //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateDodecahedron( ID3D11DeviceContext* deviceContext, float size, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeDodecahedron(vertices, indices, size, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateDodecahedron( std::vector& vertices, std::vector& indices, float size, bool rhcoords) { ComputeDodecahedron(vertices, indices, size, rhcoords); } //-------------------------------------------------------------------------------------- // Icosahedron //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateIcosahedron( ID3D11DeviceContext* deviceContext, float size, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeIcosahedron(vertices, indices, size, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateIcosahedron( std::vector& vertices, std::vector& indices, float size, bool rhcoords) { ComputeIcosahedron(vertices, indices, size, rhcoords); } //-------------------------------------------------------------------------------------- // Teapot //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateTeapot( ID3D11DeviceContext* deviceContext, float size, size_t tessellation, bool rhcoords) { VertexCollection vertices; IndexCollection indices; ComputeTeapot(vertices, indices, size, tessellation, rhcoords); // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; } void GeometricPrimitive::CreateTeapot( std::vector& vertices, std::vector& indices, float size, size_t tessellation, bool rhcoords) { ComputeTeapot(vertices, indices, size, tessellation, rhcoords); } //-------------------------------------------------------------------------------------- // Custom //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr GeometricPrimitive::CreateCustom( ID3D11DeviceContext* deviceContext, const std::vector& vertices, const std::vector& indices) { // Extra validation if (vertices.empty() || indices.empty()) throw std::exception("Requires both vertices and indices"); if (indices.size() % 3) throw std::exception("Expected triangular faces"); size_t nVerts = vertices.size(); if (nVerts >= USHRT_MAX) throw std::exception("Too many vertices for 16-bit index buffer"); for (auto it = indices.cbegin(); it != indices.cend(); ++it) { if (*it >= nVerts) { throw std::exception("Index not in vertices list"); } } // Create the primitive object. std::unique_ptr primitive(new GeometricPrimitive()); primitive->pImpl->Initialize(deviceContext, vertices, indices); return primitive; }