#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreRendering/MetaCoreRendering.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreSceneSerializer.h" #include #include #include #include #include namespace { int Failures = 0; void Expect(bool condition, const char* message) { if (!condition) { ++Failures; std::cerr << "FAIL: " << message << '\n'; } } MetaCore::MetaCoreTypeRegistry BuildRegistry() { MetaCore::MetaCoreTypeRegistry registry; MetaCore::MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCore::MetaCoreRegisterRenderingGeneratedTypes(registry); MetaCore::MetaCoreRegisterSceneGeneratedTypes(registry); return registry; } void TestSettingsAndProfiles() { const MetaCore::MetaCorePostProcessProfileDocument safeDefaults; Expect(std::abs(safeDefaults.ManualEv100 - 15.0F) < 0.0001F, "default exposure preserves Filament PBR brightness"); Expect(!safeDefaults.BloomEnabled, "default bloom does not make ordinary PBR materials appear emissive"); auto settings = MetaCore::MetaCoreBuildDefaultRenderSettings(); settings.DirectionalCascadeCount = 3U; settings.MsaaSamples = 3U; settings.GlobalParticleBudget = 0U; std::vector issues; Expect(!MetaCore::MetaCoreValidateRenderSettings(settings, &issues), "invalid settings rejected"); Expect(settings.DirectionalCascadeCount == 4U && settings.MsaaSamples == 1U && settings.GlobalParticleBudget == 100000U, "settings repaired deterministically"); const auto registry = BuildRegistry(); const auto path = std::filesystem::temp_directory_path() / "MetaCoreRenderTests" / "Rendering.mcruntime"; Expect(MetaCore::MetaCoreWriteRenderSettings(path, settings, registry), "render settings write"); const auto loaded = MetaCore::MetaCoreReadRenderSettings(path, registry); Expect(loaded.has_value() && loaded->DirectionalCascadeCount == 4U, "render settings round trip"); MetaCore::MetaCoreEnvironmentProfileDocument environment; environment.AssetGuid = MetaCore::MetaCoreAssetGuid::Generate(); environment.Name = "Factory IBL"; const auto environmentPath = path.parent_path() / "factory.mcenvironment"; Expect(MetaCore::MetaCoreWriteEnvironmentProfile(environmentPath, environment, registry), "environment profile write"); const auto loadedEnvironment = MetaCore::MetaCoreReadEnvironmentProfile(environmentPath, registry); Expect(loadedEnvironment.has_value() && loadedEnvironment->AssetGuid == environment.AssetGuid, "environment profile round trip"); MetaCore::MetaCorePostProcessProfileDocument postProcess; postProcess.AssetGuid = MetaCore::MetaCoreAssetGuid::Generate(); postProcess.Name = "Factory Camera"; const auto postProcessPath = path.parent_path() / "factory.mcpostprocess"; Expect(MetaCore::MetaCoreWritePostProcessProfile(postProcessPath, postProcess, registry), "post process profile write"); const auto loadedPostProcess = MetaCore::MetaCoreReadPostProcessProfile(postProcessPath, registry); Expect(loadedPostProcess.has_value() && loadedPostProcess->AssetGuid == postProcess.AssetGuid, "post process profile round trip"); MetaCore::MetaCorePostProcessProfileDocument engineDefault, scene, camera; scene.Name = "Scene"; camera.Name = "Camera"; Expect(&MetaCore::MetaCoreResolvePostProcessProfile(engineDefault, &scene, &camera) == &camera, "camera profile precedence"); Expect(&MetaCore::MetaCoreResolvePostProcessProfile(engineDefault, &scene, nullptr) == &scene, "scene profile precedence"); } void TestMaterialTemplates() { MetaCore::MetaCoreMaterialTemplateRegistry registry; Expect(registry.GetTemplates().size() == 7U, "all production material templates registered"); MetaCore::MetaCoreMaterialAssetDocument material; material.TemplateId = "metacore.pbr"; material.Parameters.push_back({"metallic", MetaCore::MetaCoreMaterialParameterType::Float, 2.0F, false, {}, {}}); std::vector issues; Expect(registry.ValidateAndMigrate(material, &issues), "compatible material parameters migrate"); const auto metallic = std::find_if(material.Parameters.begin(), material.Parameters.end(), [](const auto& value){return value.Name=="metallic";}); Expect(metallic != material.Parameters.end() && std::abs(metallic->FloatValue-1.0F)<0.0001F, "material range clamps"); material.TemplateId = "project.raw.mat"; Expect(!registry.ValidateAndMigrate(material, nullptr), "project raw material source rejected"); material.TemplateId = "metacore.pbr"; material.TemplateVersion = 999U; Expect(!registry.ValidateAndMigrate(material, nullptr), "future material template version rejected instead of guessed"); } void TestBudgetsAndLod() { MetaCore::MetaCoreLodGroupComponent lod; lod.Levels = {{0.5F, {}, {}}, {0.2F, {}, {}}, {0.05F, {}, {}}}; Expect(MetaCore::MetaCoreSelectLod(lod, 0.7F) == 0, "lod0 selected"); Expect(MetaCore::MetaCoreSelectLod(lod, 0.3F) == 1, "lod1 selected"); Expect(MetaCore::MetaCoreSelectLod(lod, 0.19F, 1) == 1, "lod hysteresis retains previous level"); Expect(MetaCore::MetaCoreSelectLod(lod, 0.01F) == -1, "lod culls below final threshold"); auto settings = MetaCore::MetaCoreBuildDefaultRenderSettings(); settings.MaxShadowCastingPointSpotLights = 2U; std::vector candidates{ {4, MetaCore::MetaCoreLightType::Point, 1, 1.0F, true, true}, {2, MetaCore::MetaCoreLightType::Spot, 5, 50.0F, true, true}, {1, MetaCore::MetaCoreLightType::Directional, 0, 100.0F, true, true}, {3, MetaCore::MetaCoreLightType::Point, 1, 2.0F, true, true}}; const auto selected = MetaCore::MetaCoreAllocateShadowBudget(candidates, settings); Expect(selected.size()==3U && selected[0]==1U && selected[1]==2U && selected[2]==4U, "shadow allocation is priority and id stable"); Expect(MetaCore::MetaCoreAllocateParticleBudget(500U, 300U, 200U)==200U, "particle budget clamps globally"); } void TestVisibilityProbeInstancingAndParticles() { MetaCore::MetaCoreReflectionProbeCandidate low; low.ObjectId=2; low.Probe.BakedCubemapGuid=MetaCore::MetaCoreAssetGuid::Generate(); low.Probe.Shape=MetaCore::MetaCoreReflectionProbeShape::Sphere; low.Probe.Radius=10.0F; MetaCore::MetaCoreReflectionProbeCandidate high=low; high.ObjectId=1; high.Probe.BakedCubemapGuid=MetaCore::MetaCoreAssetGuid::Generate(); high.Probe.Priority=2; high.Position={1,0,0}; const auto probes=MetaCore::MetaCoreSelectReflectionProbes({low,high},{0,0,0}); Expect(probes.size()==2U&&probes[0].ObjectId==1U&&std::abs(probes[0].Weight+probes[1].Weight-1.0F)<0.0001F,"probe selection uses priority and normalized two-probe blend"); MetaCore::MetaCoreOcclusionHistory history;history.BeginFrame(); Expect(history.IsVisible(1U,false,false,false,true,2U),"new object gets occlusion grace frame 1");history.BeginFrame(); Expect(history.IsVisible(1U,false,false,false,false,2U),"new object gets occlusion grace frame 2");history.BeginFrame(); Expect(!history.IsVisible(1U,false,false,false,false,2U),"occluded object culled after grace"); Expect(history.IsVisible(1U,false,true,false,false,2U),"camera cut restores grace"); const auto mesh=MetaCore::MetaCoreAssetGuid::Generate();const auto material=MetaCore::MetaCoreAssetGuid::Generate(); const std::vector candidates{{2,mesh,{material},10,20,true},{1,mesh,{material},10,20,true},{3,mesh,{material},11,20,true}}; const auto batches=MetaCore::MetaCoreBuildInstanceBatches(candidates); Expect(batches.size()==2U&&batches[0].ObjectIds.size()==2U,"matching mesh/material/parameters become one instance batch"); MetaCore::MetaCoreVisibilityStatistics statistics;MetaCore::MetaCoreAccumulateVisibilityResult(statistics,MetaCore::MetaCoreVisibilityResult::Visible,120U);MetaCore::MetaCoreAccumulateVisibilityResult(statistics,MetaCore::MetaCoreVisibilityResult::FrustumCulled); Expect(statistics.Submitted==2U&&statistics.Visible==1U&&statistics.FrustumCulled==1U&&statistics.VisibleTriangleCount==120U,"visibility diagnostics count reasons and triangles"); MetaCore::MetaCoreParticleEmitterComponent emitter;emitter.EmissionRate=10.0F;emitter.BurstCount=4U;emitter.MaxParticles=5U;emitter.Lifetime=2.0F;emitter.RandomSeed=42U; MetaCore::MetaCoreCpuParticleEmitter simulation;simulation.Reset(emitter.RandomSeed);simulation.Simulate(emitter,0.1F,3U); Expect(simulation.GetParticles().size()==3U&&simulation.GetDroppedParticleCount()==2U,"particle emitter drops deterministically at global budget"); const auto firstPosition=simulation.GetParticles()[0].Position;MetaCore::MetaCoreCpuParticleEmitter repeated;repeated.Reset(emitter.RandomSeed);repeated.Simulate(emitter,0.1F,3U); Expect(glm::length(firstPosition-repeated.GetParticles()[0].Position)<0.0001F,"particle random seed is deterministic"); } void TestProductionVisibilityAndGeometry() { const auto frustum = MetaCore::MetaCoreExtractFrustum(glm::mat4(1.0F)); MetaCore::MetaCoreStaticVisibilityBvh bvh; bvh.Build({ {1U, {{-0.2F,-0.2F,-0.2F},{0.2F,0.2F,0.2F}}, 1U, 0.0F, false}, {2U, {{4.0F,4.0F,4.0F},{5.0F,5.0F,5.0F}}, 1U, 0.0F, false}, {3U, {{-0.1F,-0.1F,-0.1F},{0.1F,0.1F,0.1F}}, 2U, 0.0F, false} }); MetaCore::MetaCoreVisibilityStatistics statistics; const auto visible = bvh.Query(frustum, {}, 1U, &statistics); Expect(visible.size()==1U&&visible[0]==1U&&bvh.GetNodeCount()>0U,"BVH performs layer and frustum visibility queries"); MetaCore::MetaCoreConservativeHzb hzb; const std::vector depth(16U,10.0F); Expect(hzb.Build(4U,4U,depth)&&hzb.GetMipCount()==3U,"HZB builds deterministic max-depth pyramid"); Expect(hzb.IsVisible({0.25F,0.25F,0.75F,0.75F},5.0F),"HZB keeps nearer object"); Expect(!hzb.IsVisible({0.25F,0.25F,0.75F,0.75F},20.0F),"HZB rejects conservatively occluded object"); MetaCore::MetaCoreParticleState particle;particle.Size=2.0F;particle.Lifetime=1.0F;particle.Color={1,0,0}; const auto billboards=MetaCore::MetaCoreBuildParticleBillboards({&particle,1U},{1,0,0},{0,1,0},glm::mat4(1.0F)); Expect(billboards.Vertices.size()==4U&&billboards.Indices.size()==6U,"particle billboard geometry generated"); const std::array line{{{0,0,0},{1,0,0},{1,1,0}}}; const auto ribbon=MetaCore::MetaCoreBuildLineRibbon(line,0.1F,{1,1,1,1},{1,0,0,0},{0,0,-1},32U); Expect(ribbon.Vertices.size()==6U&&ribbon.Indices.size()==12U,"line and trail ribbon geometry generated"); const std::array heights{{0U,65535U,32768U,0U}}; const auto terrain=MetaCore::MetaCoreBuildTerrainGeometry(heights,2U,2U,{10,10,5},1U); Expect(terrain.Vertices.size()==4U&&terrain.Indices.size()==6U&&terrain.Vertices[1].Position.z==5.0F,"16-bit terrain geometry generated"); MetaCore::MetaCoreTrailHistory trail;trail.Update({0,0,0},0.0F,1.0F,0.1F,3U);trail.Update({1,0,0},0.1F,1.0F,0.1F,3U);trail.Update({2,0,0},0.1F,1.0F,0.1F,3U);trail.Update({3,0,0},0.1F,1.0F,0.1F,3U); Expect(trail.GetPoints().size()==3U&&trail.GetPoints().front().Position.x==1.0F,"trail history reuses bounded point budget"); MetaCore::MetaCoreDecalProjectorComponent decal;decal.Size={2,2,2};decal.LayerMask=1U; Expect(MetaCore::MetaCoreIsDecalVisible(decal,glm::mat4(1.0F),{{-0.5F,-0.5F,-0.5F},{0.5F,0.5F,0.5F}},1U,{0,0,5}),"decal box and layer filtering accepts receiver"); MetaCore::MetaCoreMaterialCookRequest cook{"metacore.pbr",1U,"source","Linux-x86_64","OpenGL","quality=high"}; const auto key=MetaCore::MetaCoreBuildMaterialCookKey(cook);cook.CompilerOptions="quality=low"; Expect(key.size()==64U&&key!=MetaCore::MetaCoreBuildMaterialCookKey(cook),"material cook cache key covers compiler options"); Expect(MetaCore::MetaCoreParseRenderDebugView("world-normal")==MetaCore::MetaCoreRenderDebugView::WorldNormal&& !MetaCore::MetaCoreParseRenderDebugView("invalid"),"development Player debug view parsing is strict"); MetaCore::MetaCoreReflectionProbeDerivedHeader probe;probe.ProbeGuid=MetaCore::MetaCoreAssetGuid::Generate();probe.SourceHash="abc";probe.IblRelativePath="probe_ibl.ktx";probe.SkyboxRelativePath="probe_skybox.ktx"; const auto path=std::filesystem::temp_directory_path()/"MetaCoreRenderTests"/"probe.mcrprobe"; Expect(MetaCore::MetaCoreWriteReflectionProbeDerivedHeader(path,probe),"reflection probe derived header write"); const auto loaded=MetaCore::MetaCoreReadReflectionProbeDerivedHeader(path); Expect(loaded&&loaded->ProbeGuid==probe.ProbeGuid&&loaded->IblRelativePath==probe.IblRelativePath,"reflection probe derived header round trip"); } void TestSceneProductionComponents() { MetaCore::MetaCoreScene scene; auto object = scene.CreateGameObject("Rendering Components"); auto& reflectionProbe=object.AddComponent();reflectionProbe.Priority=7;reflectionProbe.SourceEnvironmentPath="Assets/Environments/factory.hdr";reflectionProbe.BakeResolution=512U; object.AddComponent().Levels.push_back({0.5F, {}, {}}); object.AddComponent().MaxParticles = 321U; object.AddComponent().Points = {{0,0,0},{1,0,0}}; object.AddComponent().MaxPoints = 42U; object.AddComponent().Opacity = 0.4F; object.AddComponent().ChunkResolution = 32U; const auto snapshot = scene.CaptureSnapshot(); MetaCore::MetaCoreScene restored; restored.RestoreSnapshot(snapshot); const auto restoredObject = restored.FindGameObject(object.GetId()); Expect(restoredObject && restoredObject.HasComponent(), "reflection probe snapshot"); Expect(restoredObject.GetComponent().BakeResolution==512U&&restoredObject.GetComponent().SourceEnvironmentPath=="Assets/Environments/factory.hdr","reflection probe bake settings snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "lod and particles snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "line and trail snapshot"); Expect(restoredObject.HasComponent() && restoredObject.HasComponent(), "decal and terrain snapshot"); MetaCore::MetaCoreSceneDocument document; document.Name="Rendering"; document.GameObjects=snapshot.GameObjects; const auto registry=BuildRegistry(); const auto path=std::filesystem::temp_directory_path()/"MetaCoreRenderTests"/"Rendering.mcscene.json"; Expect(MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(path,document,registry),"render component scene json save"); const auto loaded=MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(path,registry); Expect(loaded&&loaded->GameObjects.size()==1U&&loaded->GameObjects[0].Terrain.has_value(),"render component scene json load"); } } int main() { TestSettingsAndProfiles(); TestMaterialTemplates(); TestBudgetsAndLod(); TestVisibilityProbeInstancingAndParticles(); TestProductionVisibilityAndGeometry(); TestSceneProductionComponents(); if(Failures==0)std::cout<<"MetaCoreRenderTests passed\n";return Failures==0?0:1; }