// 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 #include #include #include #include #include #include #include namespace Nz { ForwardPipelinePass::ForwardPipelinePass(PassData& passData, std::string passName, const ParameterList& /*parameters*/) : FramePipelinePass(FramePipelineNotification::ElementInvalidation | FramePipelineNotification::MaterialInstanceRegistration), m_lastVisibilityHash(0), m_passName(std::move(passName)), m_viewer(passData.viewer), m_elementRegistry(passData.elementRegistry), m_pipeline(passData.pipeline), m_pendingLightUploadAllocation(nullptr), m_rebuildCommandBuffer(false), m_rebuildElements(false) { Graphics* graphics = Graphics::Instance(); m_forwardPassIndex = graphics->GetMaterialPassRegistry().GetPassIndex("ForwardPass"); std::size_t lightUboAlignedSize = AlignPow2(PredefinedLightOffsets.totalSize, SafeCast(graphics->GetRenderDevice()->GetDeviceInfo().limits.minUniformBufferOffsetAlignment)); m_lightDataBuffer = graphics->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform, lightUboAlignedSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write); m_lightDataBuffer->UpdateDebugName("Lights buffer"); m_renderState.lightData = RenderBufferView(m_lightDataBuffer.get()); } void ForwardPipelinePass::Prepare(FrameData& frameData) { NazaraAssert(frameData.visibleLights, "visible lights must be valid"); if (m_lastVisibilityHash != frameData.visibilityHash || m_rebuildElements) //< FIXME { frameData.renderResources.PushForRelease(std::move(m_renderElements)); m_renderElements.clear(); for (const auto& renderableData : frameData.visibleRenderables) { InstancedRenderable::ElementData elementData{ &renderableData.scissorBox, renderableData.skeletonInstance, renderableData.worldInstance }; renderableData.instancedRenderable->BuildElement(m_elementRegistry, elementData, m_forwardPassIndex, m_renderElements); } m_renderQueueRegistry.Clear(); m_renderQueue.Clear(); for (const auto& renderElement : m_renderElements) { renderElement->Register(m_renderQueueRegistry); m_renderQueue.Insert(renderElement.GetElement()); } m_renderQueueRegistry.Finalize(); m_lastVisibilityHash = frameData.visibilityHash; InvalidateElements(); } // TODO: Don't sort every frame if no material pass requires distance sorting m_renderQueue.Sort([&](const RenderElement* element) { return element->ComputeSortingScore(frameData.frustum, m_renderQueueRegistry); }); PrepareLights(frameData.renderResources, frameData.frustum, *frameData.visibleLights); if (m_rebuildElements) { m_elementRegistry.ForEachElementRenderer([&](std::size_t elementType, ElementRenderer& elementRenderer) { if (elementType >= m_elementRendererData.size()) m_elementRendererData.resize(elementType + 1); if (!m_elementRendererData[elementType]) m_elementRendererData[elementType] = elementRenderer.InstanciateData(); elementRenderer.Reset(*m_elementRendererData[elementType], frameData.renderResources); }); const ViewerInstance& viewerInstance = m_viewer->GetViewerInstance(); m_elementRegistry.ProcessRenderQueue(m_renderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = m_elementRegistry.GetElementRenderer(elementType); elementRenderer.Prepare(viewerInstance, *m_elementRendererData[elementType], frameData.renderResources, elementCount, elements, SparsePtr(&m_renderState, 0)); }); m_elementRegistry.ForEachElementRenderer([&](std::size_t elementType, ElementRenderer& elementRenderer) { elementRenderer.PrepareEnd(frameData.renderResources, *m_elementRendererData[elementType]); }); m_rebuildCommandBuffer = true; m_rebuildElements = false; } } void ForwardPipelinePass::RegisterMaterialInstance(const MaterialInstance& materialInstance) { if (!materialInstance.HasPass(m_forwardPassIndex)) return; auto it = m_materialInstances.find(&materialInstance); if (it == m_materialInstances.end()) { auto& matPassEntry = m_materialInstances[&materialInstance]; matPassEntry.onMaterialInstancePipelineInvalidated.Connect(materialInstance.OnMaterialInstancePipelineInvalidated, [=](const MaterialInstance*, std::size_t passIndex) { if (passIndex != m_forwardPassIndex) return; m_rebuildElements = true; }); matPassEntry.onMaterialInstanceShaderBindingInvalidated.Connect(materialInstance.OnMaterialInstanceShaderBindingInvalidated, [=](const MaterialInstance*) { m_rebuildCommandBuffer = true; }); } else it->second.usedCount++; } FramePass& ForwardPipelinePass::RegisterToFrameGraph(FrameGraph& frameGraph, const PassInputOuputs& inputOuputs) { if (inputOuputs.inputAttachments.size() > 0) throw std::runtime_error("no input expected"); if (inputOuputs.outputAttachments.size() != 1) throw std::runtime_error("one output expected"); if (inputOuputs.depthStencilOutput == InvalidAttachmentIndex) throw std::runtime_error("expected depth-stencil output"); FramePass& forwardPass = frameGraph.AddPass(m_passName); forwardPass.AddOutput(inputOuputs.outputAttachments[0]); if (inputOuputs.depthStencilInput != FramePipelinePass::InvalidAttachmentIndex) forwardPass.SetDepthStencilInput(inputOuputs.depthStencilInput); else forwardPass.SetDepthStencilClear(1.f, 0); forwardPass.SetDepthStencilOutput(inputOuputs.depthStencilOutput); forwardPass.SetClearColor(0, m_viewer->GetClearColor()); forwardPass.SetExecutionCallback([&]() { return (m_rebuildCommandBuffer) ? FramePassExecution::UpdateAndExecute : FramePassExecution::Execute; }); forwardPass.SetCommandCallback([this](CommandBufferBuilder& builder, const FramePassEnvironment& /*env*/) { Recti viewport = m_viewer->GetViewport(); builder.SetScissor(viewport); builder.SetViewport(viewport); const auto& viewerInstance = m_viewer->GetViewerInstance(); m_elementRegistry.ProcessRenderQueue(m_renderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = m_elementRegistry.GetElementRenderer(elementType); elementRenderer.Render(viewerInstance, *m_elementRendererData[elementType], builder, elementCount, elements); }); m_rebuildCommandBuffer = false; }); return forwardPass; } void ForwardPipelinePass::UnregisterMaterialInstance(const MaterialInstance& materialInstance) { auto it = m_materialInstances.find(&materialInstance); if (it != m_materialInstances.end()) { if (--it->second.usedCount == 0) m_materialInstances.erase(it); } } void ForwardPipelinePass::OnTransfer(RenderResources& /*renderFrame*/, CommandBufferBuilder& builder) { assert(m_pendingLightUploadAllocation); builder.CopyBuffer(*m_pendingLightUploadAllocation, RenderBufferView(m_lightDataBuffer.get())); m_pendingLightUploadAllocation = nullptr; } void ForwardPipelinePass::PrepareDirectionalLights(void* lightMemory) { std::size_t lightCount = std::min(m_directionalLights.size(), PredefinedLightData::MaxLightCount); AccessByOffset(lightMemory, PredefinedLightOffsets.directionalLightCountOffset) = SafeCast(lightCount); for (std::size_t i = 0; i < lightCount; ++i) { UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.directionalLightsOffset + PredefinedDirectionalLightOffsets.totalSize * i; const DirectionalLight* light = m_directionalLights[i].light; const Color& lightColor = light->GetColor(); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.directionOffset) = light->GetDirection(); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f); // Shadowmap handling const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_directionalLights[i].lightIndex, m_viewer); if (shadowmap) { const DirectionalLightShadowData* shadowData = SafeCast(m_pipeline.RetrieveLightShadowData(m_directionalLights[i].lightIndex)); float* cascadeFarPlanes = AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeFarPlanesOffset); Matrix4f* cascadeViewProj = AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeViewProjMatricesOffset); shadowData->GetCascadeData(m_viewer, SparsePtr(cascadeFarPlanes, 4*sizeof(float)), SparsePtr(cascadeViewProj)); AccessByOffset(basePtr, PredefinedDirectionalLightOffsets.cascadeCountOffset) = SafeCast(shadowData->GetCascadeCount()); } if (m_renderState.shadowMapsDirectional[i] != shadowmap) { m_renderState.shadowMapsDirectional[i] = shadowmap; InvalidateElements(); } } } void ForwardPipelinePass::PreparePointLights(void* lightMemory) { std::size_t lightCount = std::min(m_pointLights.size(), PredefinedLightData::MaxLightCount); AccessByOffset(lightMemory, PredefinedLightOffsets.pointLightCountOffset) = SafeCast(lightCount); for (std::size_t i = 0; i < lightCount; ++i) { UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.pointLightsOffset + PredefinedPointLightOffsets.totalSize * i; const PointLight* light = m_pointLights[i].light; const Color& lightColor = light->GetColor(); AccessByOffset(basePtr, PredefinedPointLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); AccessByOffset(basePtr, PredefinedPointLightOffsets.positionOffset) = light->GetPosition(); AccessByOffset(basePtr, PredefinedPointLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f); AccessByOffset(basePtr, PredefinedPointLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); AccessByOffset(basePtr, PredefinedPointLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); AccessByOffset(basePtr, PredefinedPointLightOffsets.radiusOffset) = light->GetRadius(); AccessByOffset(basePtr, PredefinedPointLightOffsets.invRadiusOffset) = light->GetInvRadius(); // Shadowmap handling const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_pointLights[i].lightIndex, m_viewer); if (m_renderState.shadowMapsPoint[i] != shadowmap) { m_renderState.shadowMapsPoint[i] = shadowmap; InvalidateElements(); } } } void ForwardPipelinePass::PrepareSpotLights(void* lightMemory) { std::size_t lightCount = std::min(m_spotLights.size(), PredefinedLightData::MaxLightCount); AccessByOffset(lightMemory, PredefinedLightOffsets.spotLightCountOffset) = SafeCast(lightCount); for (std::size_t i = 0; i < lightCount; ++i) { UInt8* basePtr = static_cast(lightMemory) + PredefinedLightOffsets.spotLightsOffset + PredefinedSpotLightOffsets.totalSize * i; const SpotLight* light = m_spotLights[i].light; const Color& lightColor = light->GetColor(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.colorOffset) = Vector3f(lightColor.r, lightColor.g, lightColor.b); AccessByOffset(basePtr, PredefinedSpotLightOffsets.directionOffset) = light->GetDirection(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.positionOffset) = light->GetPosition(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.invShadowMapSizeOffset) = (light->IsShadowCaster()) ? Vector2f(1.f / light->GetShadowMapSize()) : Vector2f(-1.f, -1.f); AccessByOffset(basePtr, PredefinedSpotLightOffsets.ambientFactorOffset) = light->GetAmbientFactor(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.diffuseFactorOffset) = light->GetDiffuseFactor(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.innerAngleOffset) = light->GetInnerAngleCos(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.outerAngleOffset) = light->GetOuterAngleCos(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.invRadiusOffset) = light->GetInvRadius(); AccessByOffset(basePtr, PredefinedSpotLightOffsets.viewProjMatrixOffset) = light->GetViewProjMatrix(); // Shadowmap handling const Texture* shadowmap = m_pipeline.RetrieveLightShadowmap(m_spotLights[i].lightIndex, m_viewer); if (m_renderState.shadowMapsSpot[i] != shadowmap) { m_renderState.shadowMapsSpot[i] = shadowmap; InvalidateElements(); } } } void ForwardPipelinePass::PrepareLights(RenderResources& renderResources, const Frustumf& frustum, const Bitset& visibleLights) { // Select lights m_directionalLights.clear(); m_pointLights.clear(); m_spotLights.clear(); for (std::size_t lightIndex : visibleLights.IterBits()) { const Light* light = m_pipeline.RetrieveLight(lightIndex); switch (light->GetLightType()) { case UnderlyingCast(BasicLightType::Directional): m_directionalLights.push_back({ SafeCast(light), lightIndex, 0.f }); break; case UnderlyingCast(BasicLightType::Point): m_pointLights.push_back({ SafeCast(light), lightIndex, light->ComputeContributionScore(frustum) }); break; case UnderlyingCast(BasicLightType::Spot): m_spotLights.push_back({ SafeCast(light), lightIndex, light->ComputeContributionScore(frustum) }); break; } } // Sort lights std::sort(m_pointLights.begin(), m_pointLights.end(), [&](const RenderableLight& lhs, const RenderableLight& rhs) { return lhs.contributionScore < rhs.contributionScore; }); std::sort(m_spotLights.begin(), m_spotLights.end(), [&](const RenderableLight& lhs, const RenderableLight& rhs) { return lhs.contributionScore < rhs.contributionScore; }); UploadPool& uploadPool = renderResources.GetUploadPool(); auto& lightAllocation = uploadPool.Allocate(m_lightDataBuffer->GetSize()); PrepareDirectionalLights(lightAllocation.mappedPtr); PreparePointLights(lightAllocation.mappedPtr); PrepareSpotLights(lightAllocation.mappedPtr); m_pendingLightUploadAllocation = &lightAllocation; m_pipeline.QueueTransfer(this); } }