MetaCore/tests/MetaCoreRenderTests.cpp
2026-07-22 16:08:48 +08:00

202 lines
15 KiB
C++

#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreRendering/MetaCoreRendering.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include <cmath>
#include <algorithm>
#include <filesystem>
#include <iostream>
#include <glm/geometric.hpp>
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<std::string> 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<std::string> 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<MetaCore::MetaCoreShadowCandidate> 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<MetaCore::MetaCoreInstanceCandidate> 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<float> 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<glm::vec3,3> 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<std::uint16_t,4> 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<MetaCore::MetaCoreReflectionProbeComponent>();reflectionProbe.Priority=7;reflectionProbe.SourceEnvironmentPath="Assets/Environments/factory.hdr";reflectionProbe.BakeResolution=512U;
object.AddComponent<MetaCore::MetaCoreLodGroupComponent>().Levels.push_back({0.5F, {}, {}});
object.AddComponent<MetaCore::MetaCoreParticleEmitterComponent>().MaxParticles = 321U;
object.AddComponent<MetaCore::MetaCoreLineRendererComponent>().Points = {{0,0,0},{1,0,0}};
object.AddComponent<MetaCore::MetaCoreTrailRendererComponent>().MaxPoints = 42U;
object.AddComponent<MetaCore::MetaCoreDecalProjectorComponent>().Opacity = 0.4F;
object.AddComponent<MetaCore::MetaCoreTerrainComponent>().ChunkResolution = 32U;
const auto snapshot = scene.CaptureSnapshot();
MetaCore::MetaCoreScene restored; restored.RestoreSnapshot(snapshot);
const auto restoredObject = restored.FindGameObject(object.GetId());
Expect(restoredObject && restoredObject.HasComponent<MetaCore::MetaCoreReflectionProbeComponent>(), "reflection probe snapshot");
Expect(restoredObject.GetComponent<MetaCore::MetaCoreReflectionProbeComponent>().BakeResolution==512U&&restoredObject.GetComponent<MetaCore::MetaCoreReflectionProbeComponent>().SourceEnvironmentPath=="Assets/Environments/factory.hdr","reflection probe bake settings snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreLodGroupComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreParticleEmitterComponent>(), "lod and particles snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreLineRendererComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreTrailRendererComponent>(), "line and trail snapshot");
Expect(restoredObject.HasComponent<MetaCore::MetaCoreDecalProjectorComponent>() && restoredObject.HasComponent<MetaCore::MetaCoreTerrainComponent>(), "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;
}