// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // This file is part of the "Nazara Engine - Graphics module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include namespace Nz { RenderElementPool& SubmeshRenderer::GetPool() { return m_submeshPool; } std::unique_ptr SubmeshRenderer::InstanciateData() { return std::make_unique(); } void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderResources& /*renderResources*/, std::size_t elementCount, const Pointer* elements, SparsePtr renderStates) { Graphics* graphics = Graphics::Instance(); auto& data = static_cast(rendererData); Recti invalidScissorBox(-1, -1, -1, -1); const RenderBuffer* currentIndexBuffer = nullptr; const RenderBuffer* currentVertexBuffer = nullptr; const MaterialInstance* currentMaterialInstance = nullptr; const RenderPipeline* currentPipeline = nullptr; const ShaderBinding* currentShaderBinding = nullptr; const SkeletonInstance* currentSkeletonInstance = nullptr; const WorldInstance* currentWorldInstance = nullptr; Recti currentScissorBox = invalidScissorBox; RenderBufferView currentLightData; auto FlushDrawCall = [&]() { // Does nothing for now (but will serve once instancing is implemented) }; auto FlushDrawData = [&]() { FlushDrawCall(); currentShaderBinding = nullptr; }; const auto& depthTexture2D = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::E2D]; const auto& depthTexture2DArray = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::E2D_Array]; const auto& depthTextureCube = Graphics::Instance()->GetDefaultTextures().depthTextures[ImageType::Cubemap]; const auto& whiteTexture2D = Graphics::Instance()->GetDefaultTextures().whiteTextures[ImageType::E2D]; const auto& defaultSampler = graphics->GetSamplerCache().Get({}); TextureSamplerInfo samplerInfo; samplerInfo.depthCompare = true; const auto& shadowSampler = graphics->GetSamplerCache().Get(samplerInfo); std::size_t oldDrawCallCount = data.drawCalls.size(); for (std::size_t i = 0; i < elementCount; ++i) { assert(elements[i]->GetElementType() == UnderlyingCast(BasicRenderElement::Submesh)); const RenderSubmesh& submesh = static_cast(*elements[i]); const RenderStates& renderState = renderStates[i]; if (const RenderPipeline* pipeline = submesh.GetRenderPipeline(); currentPipeline != pipeline) { FlushDrawCall(); currentPipeline = pipeline; } if (const MaterialInstance* materialInstance = &submesh.GetMaterialInstance(); currentMaterialInstance != materialInstance) { FlushDrawData(); currentMaterialInstance = materialInstance; } if (const RenderBuffer* indexBuffer = submesh.GetIndexBuffer(); currentIndexBuffer != indexBuffer) { FlushDrawCall(); currentIndexBuffer = indexBuffer; } if (const RenderBuffer* vertexBuffer = submesh.GetVertexBuffer(); currentVertexBuffer != vertexBuffer) { FlushDrawCall(); currentVertexBuffer = vertexBuffer; } if (const SkeletonInstance* skeletonInstance = submesh.GetSkeletonInstance(); currentSkeletonInstance != skeletonInstance) { FlushDrawData(); currentSkeletonInstance = skeletonInstance; } if (const WorldInstance* worldInstance = &submesh.GetWorldInstance(); currentWorldInstance != worldInstance) { // TODO: Flushing draw calls on instance binding means we can have e.g. 1000 sprites rendered using a draw call for each one // which is far from being efficient, using some bindless could help (or at least instancing?) FlushDrawData(); currentWorldInstance = worldInstance; } if (currentLightData != renderState.lightData) { FlushDrawData(); currentLightData = renderState.lightData; } const Recti& scissorBox = submesh.GetScissorBox(); const Recti& targetScissorBox = (scissorBox.width >= 0) ? scissorBox : invalidScissorBox; if (currentScissorBox != targetScissorBox) { FlushDrawCall(); currentScissorBox = targetScissorBox; } if (!currentShaderBinding) { assert(currentMaterialInstance); m_bindingCache.clear(); m_textureBindingCache.clear(); m_textureBindingCache.reserve(renderState.shadowMapsSpot.size() + renderState.shadowMapsDirectional.size() + renderState.shadowMapsPoint.size()); currentMaterialInstance->FillShaderBinding(m_bindingCache); const Material& material = *currentMaterialInstance->GetParentMaterial(); // Predefined shader bindings if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::InstanceDataUbo); bindingIndex != Material::InvalidBindingIndex) { assert(currentWorldInstance); const auto& instanceBuffer = currentWorldInstance->GetInstanceBuffer(); auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ instanceBuffer.get(), 0, instanceBuffer->GetSize() }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::LightDataUbo); bindingIndex != Material::InvalidBindingIndex && currentLightData) { auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ currentLightData.GetBuffer(), currentLightData.GetOffset(), currentLightData.GetSize() }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapDirectional); bindingIndex != Material::InvalidBindingIndex) { std::size_t textureBindingBaseIndex = m_textureBindingCache.size(); for (std::size_t j = 0; j < renderState.shadowMapsDirectional.size(); ++j) { const Texture* texture = renderState.shadowMapsDirectional[j]; if (!texture) texture = depthTexture2DArray.get(); auto& textureEntry = m_textureBindingCache.emplace_back(); textureEntry.texture = texture; textureEntry.sampler = shadowSampler.get(); } auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::SampledTextureBindings { SafeCast(renderState.shadowMapsDirectional.size()), &m_textureBindingCache[textureBindingBaseIndex] }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapPoint); bindingIndex != Material::InvalidBindingIndex) { std::size_t textureBindingBaseIndex = m_textureBindingCache.size(); for (std::size_t j = 0; j < renderState.shadowMapsPoint.size(); ++j) { const Texture* texture = renderState.shadowMapsPoint[j]; if (!texture) texture = depthTextureCube.get(); auto& textureEntry = m_textureBindingCache.emplace_back(); textureEntry.texture = texture; textureEntry.sampler = defaultSampler.get(); //< cube shadowmap don't use depth compare } auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::SampledTextureBindings { SafeCast(renderState.shadowMapsPoint.size()), &m_textureBindingCache[textureBindingBaseIndex] }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ShadowmapSpot); bindingIndex != Material::InvalidBindingIndex) { std::size_t textureBindingBaseIndex = m_textureBindingCache.size(); for (std::size_t j = 0; j < renderState.shadowMapsSpot.size(); ++j) { const Texture* texture = renderState.shadowMapsSpot[j]; if (!texture) texture = depthTexture2D.get(); auto& textureEntry = m_textureBindingCache.emplace_back(); textureEntry.texture = texture; textureEntry.sampler = shadowSampler.get(); } auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::SampledTextureBindings { SafeCast(renderState.shadowMapsSpot.size()), &m_textureBindingCache[textureBindingBaseIndex] }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::SkeletalDataUbo); bindingIndex != Material::InvalidBindingIndex && currentSkeletonInstance) { const auto& skeletalBuffer = currentSkeletonInstance->GetSkeletalBuffer(); auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ skeletalBuffer.get(), 0, skeletalBuffer->GetSize() }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::ViewerDataUbo); bindingIndex != Material::InvalidBindingIndex) { const auto& viewerBuffer = viewerInstance.GetViewerBuffer(); auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ viewerBuffer.get(), 0, viewerBuffer->GetSize() }; } if (UInt32 bindingIndex = material.GetEngineBindingIndex(EngineShaderBinding::OverlayTexture); bindingIndex != Material::InvalidBindingIndex) { auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::SampledTextureBinding{ whiteTexture2D.get(), defaultSampler.get() }; } assert(currentPipeline); ShaderBindingPtr drawDataBinding = currentPipeline->GetPipelineInfo().pipelineLayout->AllocateShaderBinding(0); drawDataBinding->Update(m_bindingCache.data(), m_bindingCache.size()); currentShaderBinding = drawDataBinding.get(); data.shaderBindings.emplace_back(std::move(drawDataBinding)); } auto& drawCall = data.drawCalls.emplace_back(); drawCall.firstIndex = 0; drawCall.indexBuffer = currentIndexBuffer; drawCall.indexCount = submesh.GetIndexCount(); drawCall.indexType = submesh.GetIndexType(); drawCall.renderPipeline = currentPipeline; drawCall.scissorBox = currentScissorBox; drawCall.shaderBinding = currentShaderBinding; drawCall.vertexBuffer = currentVertexBuffer; } const RenderSubmesh* firstSubmesh = static_cast(elements[0]); std::size_t drawCallCount = data.drawCalls.size() - oldDrawCallCount; data.drawCallPerElement[firstSubmesh] = SubmeshRendererData::DrawCallIndices{ oldDrawCallCount, drawCallCount }; } void SubmeshRenderer::Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t /*elementCount*/, const Pointer* elements) { auto& data = static_cast(rendererData); Vector2f targetSize = viewerInstance.GetTargetSize(); Recti fullscreenScissorBox(0, 0, SafeCast(std::floor(targetSize.x)), SafeCast(std::floor(targetSize.y))); const RenderBuffer* currentIndexBuffer = nullptr; const RenderBuffer* currentVertexBuffer = nullptr; const RenderPipeline* currentPipeline = nullptr; const ShaderBinding* currentShaderBinding = nullptr; Recti currentScissorBox(-1, -1, -1, -1); const RenderSubmesh* firstSubmesh = static_cast(elements[0]); auto it = data.drawCallPerElement.find(firstSubmesh); assert(it != data.drawCallPerElement.end()); const auto& indices = it->second; for (std::size_t i = 0; i < indices.count; ++i) { const auto& drawData = data.drawCalls[indices.start + i]; if (currentPipeline != drawData.renderPipeline) { commandBuffer.BindRenderPipeline(*drawData.renderPipeline); currentPipeline = drawData.renderPipeline; } if (currentShaderBinding != drawData.shaderBinding) { commandBuffer.BindRenderShaderBinding(0, *drawData.shaderBinding); currentShaderBinding = drawData.shaderBinding; } if (currentIndexBuffer != drawData.indexBuffer) { if (drawData.indexBuffer) commandBuffer.BindIndexBuffer(*drawData.indexBuffer, drawData.indexType); currentIndexBuffer = drawData.indexBuffer; } if (currentVertexBuffer != drawData.vertexBuffer) { commandBuffer.BindVertexBuffer(0, *drawData.vertexBuffer); currentVertexBuffer = drawData.vertexBuffer; } const Recti& targetScissorBox = (drawData.scissorBox.width >= 0) ? drawData.scissorBox : fullscreenScissorBox; if (currentScissorBox != targetScissorBox) { commandBuffer.SetScissor(targetScissorBox); currentScissorBox = targetScissorBox; } if (currentIndexBuffer) commandBuffer.DrawIndexed(SafeCast(drawData.indexCount), 1U, SafeCast(drawData.firstIndex)); else commandBuffer.Draw(SafeCast(drawData.indexCount), 1U, SafeCast(drawData.firstIndex)); } } void SubmeshRenderer::Reset(ElementRendererData& rendererData, RenderResources& renderResources) { auto& data = static_cast(rendererData); for (auto& shaderBinding : data.shaderBindings) renderResources.PushForRelease(std::move(shaderBinding)); data.shaderBindings.clear(); data.drawCalls.clear(); } }