#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_GRAPHICS_SOURCE 1 #include "Direct3D11InputLayoutManager.h" #include "Nuclex/Graphics/Rasterization/GenericVertexBuffer.h" #include "Nuclex/Graphics/Rasterization/Shader.h" #include "Nuclex/Graphics/Introspection/ShaderMetadata.h" #include "Nuclex/Graphics/VertexElement.h" #include namespace { // ------------------------------------------------------------------------------------------- // /// Converts a vertex element type into a DXGI_FORMAT /// Vertex element type that will be converted /// The DXGI_FORMAT matching the specified vertex element type DXGI_FORMAT formatFromVertexElementType(Nuclex::Graphics::VertexElementType::Enum type) { using namespace Nuclex::Graphics; switch(type) { 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; } default: { throw std::runtime_error("Unsupported vertex element type"); } } } // ------------------------------------------------------------------------------------------- // /// Locates the vertex element matching the specified input parameter /// Parameter for which a vertex element will be located /// Vertex buffers that will be searched /// Number of vertex buffers to search /// /// Description that will be filled if the element was found /// bool locateVertexElement( const Nuclex::Graphics::Introspection::InputParameter ¶meter, const std::shared_ptr *vertexBuffers, std::size_t vertexBufferCount, D3D11_INPUT_ELEMENT_DESC &inputElementDescription ) { using namespace Nuclex::Graphics; std::size_t semanticIndex = 0; // Search all vertex buffers for the semantic name of the parameter for(std::size_t index = 0; index < vertexBufferCount; ++index) { if(vertexBuffers[index].get() != nullptr) { const VertexElement *elements = vertexBuffers[index]->GetVertexElements(); std::size_t elementOffset = 0; // Search the current vertex buffer's elements for the semantic name std::size_t elementCount = vertexBuffers[index]->CountVertexElements(); for(std::size_t elementIndex = 0; elementIndex < elementCount; ++elementIndex) { if(elements[elementIndex].Semantic == parameter.Semantic) { // If the semantic name matches and this is the nth appearance of that name, // we located the element the shader is requesting if(semanticIndex == parameter.SemanticIndex) { DXGI_FORMAT format = formatFromVertexElementType(elements[elementIndex].Type); inputElementDescription.SemanticName = parameter.Semantic.c_str(); inputElementDescription.SemanticIndex = static_cast(parameter.SemanticIndex); inputElementDescription.Format = format; inputElementDescription.InputSlot = static_cast(index); inputElementDescription.AlignedByteOffset = static_cast(elementOffset); inputElementDescription.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; inputElementDescription.InstanceDataStepRate = 0; return true; } else { // Shader is requesting a later instance of the element ++semanticIndex; } } // if semantic matches // Keep track of the offset the current vertex element has in its vertex buffer elementOffset += GetSize(elements[elementIndex].Type); } } } // We didn't find a matching element return false; } // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Graphics { namespace Rasterization { // ------------------------------------------------------------------------------------------- // ID3D11InputLayoutPtr Direct3D11InputLayoutManager::GetInputLayout( const Shader *shader, const std::shared_ptr *vertexBuffers, std::size_t vertexBufferCount ) { // Get the unique id of this vertex buffer type combination std::size_t combinationId = getOrCreateCombinationID(vertexBuffers, vertexBufferCount); // Look up the input layout for the shader with this vertex element list combination ID3D11InputLayoutPtr inputLayout; bool wasFound = this->inputLayouts.TryGetAndTouch( ShaderVertexCombinationIdMapping(shader, combinationId), inputLayout ); // If no input layout was created yet that maps the vertex element list combination to // this shader, create a new one if(!wasFound) { inputLayout = createInputLayout(shader, vertexBuffers, vertexBufferCount); this->inputLayouts.AddAndTrim( ShaderVertexCombinationIdMapping(shader, combinationId), inputLayout, InputLayoutCacheSize ); } return inputLayout; } // ------------------------------------------------------------------------------------------- // ID3D11InputLayoutPtr Direct3D11InputLayoutManager::createInputLayout( const Shader *shader, const std::shared_ptr *vertexBuffers, std::size_t vertexBufferCount ) { const Introspection::ShaderMetadata &metadata = shader->Metadata(); // Put together a vertex format description to create the input layout from std::vector vertexDescription(metadata.Inputs.size()); for(std::size_t index = 0; index < metadata.Inputs.size(); ++index) { const Introspection::InputParameter ¶meter = metadata.Inputs[index]; bool wasLocated = locateVertexElement( parameter, vertexBuffers, vertexBufferCount, vertexDescription[index] ); if(!wasLocated) { throw std::runtime_error("Could not match vertex element to shader input"); } } // Create a new input layout object through Direct3D 11 ID3D11InputLayoutPtr inputLayout; HRESULT resultHandle = this->device->CreateInputLayout( &vertexDescription[0], static_cast(vertexDescription.size()), shader->AccessMemory(), shader->GetProgramLengthInBytes(), &inputLayout ); if(FAILED(resultHandle)) { throw std::runtime_error("Could not create Direct3D 11 input layout"); } return inputLayout; } // ------------------------------------------------------------------------------------------- // std::size_t Direct3D11InputLayoutManager::getOrCreateCombinationID( const std::shared_ptr *vertexBuffers, std::size_t vertexBufferCount ) { // Build the vertex element list set so we can look up the combination id in the cache this->searchedCombination.Clear(); for(std::size_t index = 0; index < vertexBufferCount; ++index) { GenericVertexBuffer *vertexBuffer = vertexBuffers[index].get(); if(vertexBuffer == nullptr) { this->searchedCombination.AddVertexElementList(nullptr); } else { this->searchedCombination.AddVertexElementList(vertexBuffer->GetVertexElements()); } } // Try to look up the combination id in the cache std::size_t combinationId; bool wasFound = this->vertexCombinations.TryGetAndTouch( this->searchedCombination, combinationId ); // If this combination was not known yet, assign it a new unique id if(!wasFound) { this->vertexCombinations.AddAndTrim( this->searchedCombination, this->nextVertexCombinationID, VertexCombinationCacheSize ); ++this->nextVertexCombinationID; } return combinationId; } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Graphics::Rasterization