diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 2217e5c9..ef120353 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -45,13 +45,12 @@ private: bool CreatePipelineResources(const RenderContext& context); void DestroyPipelineResources(); - const Resources::Material* ResolveMaterial(const VisibleRenderObject& visibleObject, uint32_t materialIndex) const; const Resources::Texture* ResolveTexture(const Resources::Material* material) const; - RHI::RHIResourceView* ResolveTextureView(const VisibleRenderObject& visibleObject, uint32_t materialIndex); - bool DrawVisibleObject( + RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem); + bool DrawVisibleItem( const RenderContext& context, const RenderSceneData& sceneData, - const VisibleRenderObject& visibleObject); + const VisibleRenderItem& visibleItem); RHI::RHIDevice* m_device = nullptr; RHI::RHIType m_backendType = RHI::RHIType::D3D12; diff --git a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h new file mode 100644 index 00000000..0437ce61 --- /dev/null +++ b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Rendering { + +enum class BuiltinMaterialPass : Core::uint32 { + Forward = 0 +}; + +inline bool IsForwardPassName(const Containers::String& value) { + const Containers::String normalized = value.Trim().ToLower(); + return normalized.Empty() || + normalized == Containers::String("forward") || + normalized == Containers::String("forwardbase") || + normalized == Containers::String("forwardlit") || + normalized == Containers::String("forwardonly"); +} + +inline const Resources::Material* ResolveMaterial( + const Components::MeshRendererComponent* meshRenderer, + const Resources::Mesh* mesh, + Core::uint32 materialIndex) { + if (meshRenderer != nullptr && materialIndex < meshRenderer->GetMaterialCount()) { + if (const Resources::Material* material = meshRenderer->GetMaterial(materialIndex)) { + return material; + } + } + + if (mesh != nullptr && materialIndex < mesh->GetMaterials().Size()) { + if (const Resources::Material* material = mesh->GetMaterials()[materialIndex]) { + return material; + } + } + + if (meshRenderer != nullptr && meshRenderer->GetMaterialCount() > 0) { + if (const Resources::Material* material = meshRenderer->GetMaterial(0)) { + return material; + } + } + + if (mesh != nullptr && mesh->GetMaterials().Size() > 0) { + return mesh->GetMaterials()[0]; + } + + return nullptr; +} + +inline const Resources::Material* ResolveMaterial(const VisibleRenderItem& visibleItem) { + if (visibleItem.material != nullptr) { + return visibleItem.material; + } + + return ResolveMaterial(visibleItem.meshRenderer, visibleItem.mesh, visibleItem.materialIndex); +} + +inline Core::int32 ResolveMaterialRenderQueue(const Resources::Material* material) { + return material != nullptr + ? material->GetRenderQueue() + : static_cast(Resources::MaterialRenderQueue::Geometry); +} + +inline bool IsTransparentRenderQueue(Core::int32 renderQueue) { + return renderQueue >= static_cast(Resources::MaterialRenderQueue::Transparent); +} + +inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) { + if (material == nullptr) { + return true; + } + + switch (pass) { + case BuiltinMaterialPass::Forward: { + const Containers::String shaderPass = material->GetShaderPass(); + if (!shaderPass.Empty() && !IsForwardPassName(shaderPass)) { + return false; + } + + const Containers::String lightMode = material->GetTag("LightMode"); + if (!lightMode.Empty() && !IsForwardPassName(lightMode)) { + return false; + } + + return true; + } + default: + return true; + } +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h index ae127a54..fafd138a 100644 --- a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h +++ b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -18,7 +19,7 @@ namespace Rendering { struct RenderSceneData { Components::CameraComponent* camera = nullptr; RenderCameraData cameraData; - std::vector visibleObjects; + std::vector visibleItems; bool HasCamera() const { return camera != nullptr; } }; @@ -39,9 +40,10 @@ private: const Components::CameraComponent& camera, uint32_t viewportWidth, uint32_t viewportHeight) const; - void ExtractVisibleObjects( + void ExtractVisibleItems( Components::GameObject* gameObject, - std::vector& visibleObjects) const; + const Math::Vector3& cameraPosition, + std::vector& visibleItems) const; }; } // namespace Rendering diff --git a/engine/include/XCEngine/Rendering/VisibleRenderObject.h b/engine/include/XCEngine/Rendering/VisibleRenderObject.h index c2a5a0f7..2e9528a8 100644 --- a/engine/include/XCEngine/Rendering/VisibleRenderObject.h +++ b/engine/include/XCEngine/Rendering/VisibleRenderObject.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace XCEngine { @@ -12,13 +13,21 @@ class MeshRendererComponent; namespace Rendering { -struct VisibleRenderObject { +struct VisibleRenderItem { Components::GameObject* gameObject = nullptr; Components::MeshFilterComponent* meshFilter = nullptr; Components::MeshRendererComponent* meshRenderer = nullptr; Resources::Mesh* mesh = nullptr; + const Resources::Material* material = nullptr; + Core::uint32 materialIndex = 0; + Core::uint32 sectionIndex = 0; + bool hasSection = false; + Core::int32 renderQueue = 0; + float cameraDistanceSq = 0.0f; Math::Matrix4x4 localToWorld = Math::Matrix4x4::Identity(); }; +using VisibleRenderObject = VisibleRenderItem; + } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 2ef8a0a9..d30a18fc 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -3,6 +3,7 @@ #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" #include "RHI/RHICommandList.h" +#include "Rendering/RenderMaterialUtility.h" #include "Rendering/RenderSurface.h" #include "Resources/Material/Material.h" #include "Resources/Texture/Texture.h" @@ -250,8 +251,12 @@ bool BuiltinForwardPipeline::Render( commandList->SetPipelineState(m_pipelineState); commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); - for (const VisibleRenderObject& visibleObject : sceneData.visibleObjects) { - DrawVisibleObject(context, sceneData, visibleObject); + for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) { + if (!MatchesBuiltinPass(ResolveMaterial(visibleItem), BuiltinMaterialPass::Forward)) { + continue; + } + + DrawVisibleItem(context, sceneData, visibleItem); } if (surface.IsAutoTransitionEnabled()) { @@ -506,42 +511,13 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_initialized = false; } -const Resources::Material* BuiltinForwardPipeline::ResolveMaterial( - const VisibleRenderObject& visibleObject, - uint32_t materialIndex) const { - if (visibleObject.meshRenderer != nullptr && materialIndex < visibleObject.meshRenderer->GetMaterialCount()) { - if (const Resources::Material* material = visibleObject.meshRenderer->GetMaterial(materialIndex)) { - return material; - } - } - - if (visibleObject.mesh != nullptr && materialIndex < visibleObject.mesh->GetMaterials().Size()) { - if (const Resources::Material* material = visibleObject.mesh->GetMaterials()[materialIndex]) { - return material; - } - } - - if (visibleObject.meshRenderer != nullptr && visibleObject.meshRenderer->GetMaterialCount() > 0) { - if (const Resources::Material* material = visibleObject.meshRenderer->GetMaterial(0)) { - return material; - } - } - - if (visibleObject.mesh != nullptr && visibleObject.mesh->GetMaterials().Size() > 0) { - return visibleObject.mesh->GetMaterials()[0]; - } - - return nullptr; -} - const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { return material != nullptr ? FindMaterialTexture(*material) : nullptr; } RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( - const VisibleRenderObject& visibleObject, - uint32_t materialIndex) { - const Resources::Material* material = ResolveMaterial(visibleObject, materialIndex); + const VisibleRenderItem& visibleItem) { + const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Texture* texture = ResolveTexture(material); if (texture != nullptr) { const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture); @@ -553,11 +529,11 @@ RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( return m_fallbackTextureView; } -bool BuiltinForwardPipeline::DrawVisibleObject( +bool BuiltinForwardPipeline::DrawVisibleItem( const RenderContext& context, const RenderSceneData& sceneData, - const VisibleRenderObject& visibleObject) { - const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleObject.mesh); + const VisibleRenderItem& visibleItem) { + const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { return false; } @@ -575,37 +551,10 @@ bool BuiltinForwardPipeline::DrawVisibleObject( const PerObjectConstants constants = { sceneData.cameraData.projection, sceneData.cameraData.view, - visibleObject.localToWorld.Transpose() + visibleItem.localToWorld.Transpose() }; - const Containers::Array& sections = visibleObject.mesh->GetSections(); - const bool hasSections = !sections.Empty(); - - if (hasSections) { - for (size_t sectionIndex = 0; sectionIndex < sections.Size(); ++sectionIndex) { - const Resources::MeshSection& section = sections[sectionIndex]; - RHI::RHIResourceView* textureView = ResolveTextureView(visibleObject, section.materialID); - if (textureView == nullptr) { - continue; - } - - m_constantSet->WriteConstant(0, &constants, sizeof(constants)); - m_textureSet->Update(0, textureView); - RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet, m_samplerSet }; - commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_pipelineLayout); - - if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { - // MeshLoader flattens section indices into a single global index buffer. - commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); - } else if (section.vertexCount > 0) { - commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); - } - } - - return true; - } - - RHI::RHIResourceView* textureView = ResolveTextureView(visibleObject, 0); + RHI::RHIResourceView* textureView = ResolveTextureView(visibleItem); if (textureView == nullptr) { return false; } @@ -615,6 +564,23 @@ bool BuiltinForwardPipeline::DrawVisibleObject( RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet, m_samplerSet }; commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_pipelineLayout); + if (visibleItem.hasSection) { + const Containers::Array& sections = visibleItem.mesh->GetSections(); + if (visibleItem.sectionIndex >= sections.Size()) { + return false; + } + + const Resources::MeshSection& section = sections[visibleItem.sectionIndex]; + if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { + // MeshLoader flattens section indices into a single global index buffer. + commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); + } else if (section.vertexCount > 0) { + commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); + } + + return true; + } + if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) { commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0); } else if (cachedMesh->vertexCount > 0) { diff --git a/engine/src/Rendering/RenderSceneExtractor.cpp b/engine/src/Rendering/RenderSceneExtractor.cpp index 3d3e0b5a..5db015f2 100644 --- a/engine/src/Rendering/RenderSceneExtractor.cpp +++ b/engine/src/Rendering/RenderSceneExtractor.cpp @@ -5,8 +5,11 @@ #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" #include "Components/TransformComponent.h" +#include "Rendering/RenderMaterialUtility.h" #include "Scene/Scene.h" +#include + namespace XCEngine { namespace Rendering { @@ -19,6 +22,25 @@ bool IsUsableCamera(const Components::CameraComponent* camera) { camera->GetGameObject()->IsActiveInHierarchy(); } +bool CompareVisibleItems(const VisibleRenderItem& lhs, const VisibleRenderItem& rhs) { + if (lhs.renderQueue != rhs.renderQueue) { + return lhs.renderQueue < rhs.renderQueue; + } + + const bool isTransparentQueue = IsTransparentRenderQueue(lhs.renderQueue); + if (lhs.cameraDistanceSq != rhs.cameraDistanceSq) { + return isTransparentQueue + ? lhs.cameraDistanceSq > rhs.cameraDistanceSq + : lhs.cameraDistanceSq < rhs.cameraDistanceSq; + } + + if (lhs.gameObject != rhs.gameObject) { + return lhs.gameObject < rhs.gameObject; + } + + return lhs.sectionIndex < rhs.sectionIndex; +} + } // namespace RenderSceneData RenderSceneExtractor::Extract( @@ -33,12 +55,18 @@ RenderSceneData RenderSceneExtractor::Extract( } sceneData.cameraData = BuildCameraData(*sceneData.camera, viewportWidth, viewportHeight); + const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition; const std::vector rootGameObjects = scene.GetRootGameObjects(); for (Components::GameObject* rootGameObject : rootGameObjects) { - ExtractVisibleObjects(rootGameObject, sceneData.visibleObjects); + ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems); } + std::stable_sort( + sceneData.visibleItems.begin(), + sceneData.visibleItems.end(), + CompareVisibleItems); + return sceneData; } @@ -116,9 +144,10 @@ RenderCameraData RenderSceneExtractor::BuildCameraData( return cameraData; } -void RenderSceneExtractor::ExtractVisibleObjects( +void RenderSceneExtractor::ExtractVisibleItems( Components::GameObject* gameObject, - std::vector& visibleObjects) const { + const Math::Vector3& cameraPosition, + std::vector& visibleItems) const { if (gameObject == nullptr || !gameObject->IsActiveInHierarchy()) { return; } @@ -131,18 +160,49 @@ void RenderSceneExtractor::ExtractVisibleObjects( meshRenderer->IsEnabled()) { Resources::Mesh* mesh = meshFilter->GetMesh(); if (mesh != nullptr && mesh->IsValid()) { - VisibleRenderObject visibleObject; - visibleObject.gameObject = gameObject; - visibleObject.meshFilter = meshFilter; - visibleObject.meshRenderer = meshRenderer; - visibleObject.mesh = mesh; - visibleObject.localToWorld = gameObject->GetTransform()->GetLocalToWorldMatrix(); - visibleObjects.push_back(visibleObject); + const Math::Matrix4x4 localToWorld = gameObject->GetTransform()->GetLocalToWorldMatrix(); + const Math::Vector3 worldPosition = localToWorld.GetTranslation(); + const float cameraDistanceSq = (worldPosition - cameraPosition).SqrMagnitude(); + const Containers::Array& sections = mesh->GetSections(); + + if (!sections.Empty()) { + for (size_t sectionIndex = 0; sectionIndex < sections.Size(); ++sectionIndex) { + const Resources::MeshSection& section = sections[sectionIndex]; + + VisibleRenderItem visibleItem; + visibleItem.gameObject = gameObject; + visibleItem.meshFilter = meshFilter; + visibleItem.meshRenderer = meshRenderer; + visibleItem.mesh = mesh; + visibleItem.materialIndex = section.materialID; + visibleItem.sectionIndex = static_cast(sectionIndex); + visibleItem.hasSection = true; + visibleItem.material = ResolveMaterial(meshRenderer, mesh, section.materialID); + visibleItem.renderQueue = ResolveMaterialRenderQueue(visibleItem.material); + visibleItem.cameraDistanceSq = cameraDistanceSq; + visibleItem.localToWorld = localToWorld; + visibleItems.push_back(visibleItem); + } + } else { + VisibleRenderItem visibleItem; + visibleItem.gameObject = gameObject; + visibleItem.meshFilter = meshFilter; + visibleItem.meshRenderer = meshRenderer; + visibleItem.mesh = mesh; + visibleItem.materialIndex = 0; + visibleItem.sectionIndex = 0; + visibleItem.hasSection = false; + visibleItem.material = ResolveMaterial(meshRenderer, mesh, 0); + visibleItem.renderQueue = ResolveMaterialRenderQueue(visibleItem.material); + visibleItem.cameraDistanceSq = cameraDistanceSq; + visibleItem.localToWorld = localToWorld; + visibleItems.push_back(visibleItem); + } } } for (Components::GameObject* child : gameObject->GetChildren()) { - ExtractVisibleObjects(child, visibleObjects); + ExtractVisibleItems(child, cameraPosition, visibleItems); } } diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index aeec195e..b1f4623e 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -6,11 +6,14 @@ #include #include #include +#include #include -#include +#include #include +#include using namespace XCEngine::Components; +using namespace XCEngine::Core; using namespace XCEngine::Math; using namespace XCEngine::Rendering; using namespace XCEngine::Resources; @@ -27,6 +30,40 @@ Mesh* CreateTestMesh(const char* path) { return mesh; } +Mesh* CreateSectionedTestMesh(const char* path, std::initializer_list materialIds) { + Mesh* mesh = CreateTestMesh(path); + uint32_t startIndex = 0; + for (uint32_t materialId : materialIds) { + MeshSection section = {}; + section.baseVertex = 0; + section.vertexCount = 3; + section.startIndex = startIndex; + section.indexCount = 3; + section.materialID = materialId; + mesh->AddSection(section); + startIndex += 3; + } + + return mesh; +} + +Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char* shaderPass = nullptr, const char* lightMode = nullptr) { + auto* material = new Material(); + IResource::ConstructParams params = {}; + params.name = "TestMaterial"; + params.path = path; + params.guid = ResourceGUID::Generate(path); + material->Initialize(params); + material->SetRenderQueue(renderQueue); + if (shaderPass != nullptr) { + material->SetShaderPass(shaderPass); + } + if (lightMode != nullptr) { + material->SetTag("LightMode", lightMode); + } + return material; +} + TEST(RenderSceneExtractor_Test, SelectsHighestDepthPrimaryCameraAndVisibleObjects) { Scene scene("RenderScene"); @@ -64,10 +101,12 @@ TEST(RenderSceneExtractor_Test, SelectsHighestDepthPrimaryCameraAndVisibleObject EXPECT_EQ(sceneData.cameraData.viewportHeight, 720u); EXPECT_EQ(sceneData.cameraData.worldPosition, Vector3(2.0f, 3.0f, 4.0f)); - ASSERT_EQ(sceneData.visibleObjects.size(), 1u); - EXPECT_EQ(sceneData.visibleObjects[0].gameObject, visibleObject); - EXPECT_EQ(sceneData.visibleObjects[0].mesh, visibleMesh); - EXPECT_EQ(sceneData.visibleObjects[0].localToWorld.GetTranslation(), Vector3(1.0f, 2.0f, 3.0f)); + ASSERT_EQ(sceneData.visibleItems.size(), 1u); + EXPECT_EQ(sceneData.visibleItems[0].gameObject, visibleObject); + EXPECT_EQ(sceneData.visibleItems[0].mesh, visibleMesh); + EXPECT_EQ(sceneData.visibleItems[0].localToWorld.GetTranslation(), Vector3(1.0f, 2.0f, 3.0f)); + EXPECT_FALSE(sceneData.visibleItems[0].hasSection); + EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast(MaterialRenderQueue::Geometry)); meshFilter->ClearMesh(); hiddenMeshFilter->ClearMesh(); @@ -98,4 +137,132 @@ TEST(RenderSceneExtractor_Test, OverrideCameraTakesPriority) { EXPECT_EQ(sceneData.cameraData.viewportHeight, 480u); } +TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRenderQueue) { + Scene scene("SectionScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + GameObject* renderObject = scene.CreateGameObject("MultiSectionMesh"); + renderObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 5.0f)); + auto* meshFilter = renderObject->AddComponent(); + auto* meshRenderer = renderObject->AddComponent(); + + Mesh* mesh = CreateSectionedTestMesh("Meshes/sectioned.mesh", { 1u, 0u }); + Material* opaqueMaterial = CreateTestMaterial( + "Materials/opaque.mat", + static_cast(MaterialRenderQueue::Geometry), + "ForwardLit", + "ForwardBase"); + Material* transparentMaterial = CreateTestMaterial( + "Materials/transparent.mat", + static_cast(MaterialRenderQueue::Transparent), + "ForwardLit", + "ForwardBase"); + + meshFilter->SetMesh(mesh); + meshRenderer->SetMaterial(0, opaqueMaterial); + meshRenderer->SetMaterial(1, transparentMaterial); + + RenderSceneExtractor extractor; + const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600); + + ASSERT_TRUE(sceneData.HasCamera()); + ASSERT_EQ(sceneData.visibleItems.size(), 2u); + + EXPECT_TRUE(sceneData.visibleItems[0].hasSection); + EXPECT_EQ(sceneData.visibleItems[0].sectionIndex, 1u); + EXPECT_EQ(sceneData.visibleItems[0].materialIndex, 0u); + EXPECT_EQ(sceneData.visibleItems[0].material, opaqueMaterial); + EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast(MaterialRenderQueue::Geometry)); + + EXPECT_TRUE(sceneData.visibleItems[1].hasSection); + EXPECT_EQ(sceneData.visibleItems[1].sectionIndex, 0u); + EXPECT_EQ(sceneData.visibleItems[1].materialIndex, 1u); + EXPECT_EQ(sceneData.visibleItems[1].material, transparentMaterial); + EXPECT_EQ(sceneData.visibleItems[1].renderQueue, static_cast(MaterialRenderQueue::Transparent)); + + meshRenderer->ClearMaterials(); + meshFilter->ClearMesh(); + delete opaqueMaterial; + delete transparentMaterial; + delete mesh; +} + +TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront) { + Scene scene("QueueSortScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + auto createRenderable = [&](const char* name, float z, int32_t renderQueue) -> Mesh* { + GameObject* object = scene.CreateGameObject(name); + object->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, z)); + auto* meshFilter = object->AddComponent(); + auto* meshRenderer = object->AddComponent(); + Mesh* mesh = CreateTestMesh(name); + Material* material = CreateTestMaterial(name, renderQueue, "ForwardLit", "ForwardBase"); + meshFilter->SetMesh(mesh); + meshRenderer->SetMaterial(0, material); + return mesh; + }; + + Mesh* opaqueFarMesh = createRenderable("OpaqueFar", 10.0f, static_cast(MaterialRenderQueue::Geometry)); + Mesh* transparentNearMesh = createRenderable("TransparentNear", 2.0f, static_cast(MaterialRenderQueue::Transparent)); + Mesh* opaqueNearMesh = createRenderable("OpaqueNear", 2.0f, static_cast(MaterialRenderQueue::Geometry)); + Mesh* transparentFarMesh = createRenderable("TransparentFar", 10.0f, static_cast(MaterialRenderQueue::Transparent)); + + RenderSceneExtractor extractor; + const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600); + + ASSERT_EQ(sceneData.visibleItems.size(), 4u); + EXPECT_STREQ(sceneData.visibleItems[0].gameObject->GetName().c_str(), "OpaqueNear"); + EXPECT_STREQ(sceneData.visibleItems[1].gameObject->GetName().c_str(), "OpaqueFar"); + EXPECT_STREQ(sceneData.visibleItems[2].gameObject->GetName().c_str(), "TransparentFar"); + EXPECT_STREQ(sceneData.visibleItems[3].gameObject->GetName().c_str(), "TransparentNear"); + + auto cleanupObject = [](GameObject* object) { + auto* meshFilter = object->GetComponent(); + auto* meshRenderer = object->GetComponent(); + Material* material = meshRenderer != nullptr ? meshRenderer->GetMaterial(0) : nullptr; + if (meshRenderer != nullptr) { + meshRenderer->ClearMaterials(); + } + if (meshFilter != nullptr) { + meshFilter->ClearMesh(); + } + delete material; + }; + + cleanupObject(scene.Find("OpaqueFar")); + cleanupObject(scene.Find("TransparentNear")); + cleanupObject(scene.Find("OpaqueNear")); + cleanupObject(scene.Find("TransparentFar")); + + delete opaqueFarMesh; + delete transparentNearMesh; + delete opaqueNearMesh; + delete transparentFarMesh; +} + +TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardPassMetadata) { + Material forwardMaterial; + forwardMaterial.SetShaderPass("ForwardLit"); + forwardMaterial.SetTag("LightMode", "ForwardBase"); + EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::Forward)); + + Material noMetadataMaterial; + EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::Forward)); + + Material shadowMaterial; + shadowMaterial.SetShaderPass("ShadowCaster"); + EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::Forward)); + + Material depthOnlyMaterial; + depthOnlyMaterial.SetTag("LightMode", "DepthOnly"); + EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::Forward)); +} + } // namespace