#include #include "../VolumeIntegrationSceneFixture.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace VolumeIntegrationTestUtils; namespace { constexpr int kWarmupFrames = 20; constexpr int kMeasuredFrames = 60; struct QualityProfile { const char* name = ""; float stepSize = 1.0f; float maxSteps = 2000.0f; const char* screenshotFilename = ""; }; struct OptimizationProfile { const char* name = ""; float enableEntryHdda = 1.0f; float enableEmptySpaceSkipping = 1.0f; float enableEarlyTermination = 1.0f; const char* screenshotFilename = ""; }; struct QualityResult { std::string name; float stepSize = 0.0f; float maxSteps = 0.0f; StableFrameStats stats = {}; std::string screenshotFilename; }; struct OptimizationResult { std::string name; float enableEntryHdda = 1.0f; float enableEmptySpaceSkipping = 1.0f; float enableEarlyTermination = 1.0f; StableFrameStats stats = {}; std::string screenshotFilename; }; std::string FormatNumber(double value, int precision = 3) { std::ostringstream stream; stream << std::fixed << std::setprecision(precision) << value; return stream.str(); } void WriteQualityResults(const std::vector& results) { const std::filesystem::path tsvPath = RenderingIntegrationTestUtils::ResolveRuntimePath("volume_quality_results.tsv"); std::ofstream tsv(tsvPath, std::ios::trunc); ASSERT_TRUE(tsv.is_open()) << tsvPath.string(); tsv << "profile\tstep_size\tmax_steps\twarmup_frames\tmeasured_frames\tavg_frame_ms\tmin_frame_ms\tmax_frame_ms\tavg_fps\tscreenshot\n"; for (const QualityResult& result : results) { tsv << result.name << '\t' << result.stepSize << '\t' << result.maxSteps << '\t' << result.stats.warmupFrames << '\t' << result.stats.measuredFrames << '\t' << FormatNumber(result.stats.averageFrameTimeMs) << '\t' << FormatNumber(result.stats.minFrameTimeMs) << '\t' << FormatNumber(result.stats.maxFrameTimeMs) << '\t' << FormatNumber(result.stats.averageFps) << '\t' << result.screenshotFilename << '\n'; } const std::filesystem::path markdownPath = RenderingIntegrationTestUtils::ResolveRuntimePath("volume_quality_results.md"); std::ofstream markdown(markdownPath, std::ios::trunc); ASSERT_TRUE(markdown.is_open()) << markdownPath.string(); markdown << "|profile|StepSize|MaxSteps|warmup_frames|measured_frames|avg_frame_ms|min_frame_ms|max_frame_ms|avg_fps|screenshot|\n"; markdown << "|---|---:|---:|---:|---:|---:|---:|---:|---:|---|\n"; for (const QualityResult& result : results) { markdown << '|' << result.name << '|' << result.stepSize << '|' << result.maxSteps << '|' << result.stats.warmupFrames << '|' << result.stats.measuredFrames << '|' << FormatNumber(result.stats.averageFrameTimeMs) << '|' << FormatNumber(result.stats.minFrameTimeMs) << '|' << FormatNumber(result.stats.maxFrameTimeMs) << '|' << FormatNumber(result.stats.averageFps) << '|' << result.screenshotFilename << "|\n"; } } void WriteOptimizationResults(const std::vector& results) { const std::filesystem::path tsvPath = RenderingIntegrationTestUtils::ResolveRuntimePath("volume_optimization_results.tsv"); std::ofstream tsv(tsvPath, std::ios::trunc); ASSERT_TRUE(tsv.is_open()) << tsvPath.string(); tsv << "profile\tenable_entry_hdda\tenable_empty_space_skipping\tenable_early_termination\twarmup_frames\tmeasured_frames\tavg_frame_ms\tmin_frame_ms\tmax_frame_ms\tavg_fps\tscreenshot\n"; for (const OptimizationResult& result : results) { tsv << result.name << '\t' << result.enableEntryHdda << '\t' << result.enableEmptySpaceSkipping << '\t' << result.enableEarlyTermination << '\t' << result.stats.warmupFrames << '\t' << result.stats.measuredFrames << '\t' << FormatNumber(result.stats.averageFrameTimeMs) << '\t' << FormatNumber(result.stats.minFrameTimeMs) << '\t' << FormatNumber(result.stats.maxFrameTimeMs) << '\t' << FormatNumber(result.stats.averageFps) << '\t' << result.screenshotFilename << '\n'; } const std::filesystem::path markdownPath = RenderingIntegrationTestUtils::ResolveRuntimePath("volume_optimization_results.md"); std::ofstream markdown(markdownPath, std::ios::trunc); ASSERT_TRUE(markdown.is_open()) << markdownPath.string(); markdown << "|profile|enable_entry_hdda|enable_empty_space_skipping|enable_early_termination|warmup_frames|measured_frames|avg_frame_ms|min_frame_ms|max_frame_ms|avg_fps|screenshot|\n"; markdown << "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|\n"; for (const OptimizationResult& result : results) { markdown << '|' << result.name << '|' << result.enableEntryHdda << '|' << result.enableEmptySpaceSkipping << '|' << result.enableEarlyTermination << '|' << result.stats.warmupFrames << '|' << result.stats.measuredFrames << '|' << FormatNumber(result.stats.averageFrameTimeMs) << '|' << FormatNumber(result.stats.minFrameTimeMs) << '|' << FormatNumber(result.stats.maxFrameTimeMs) << '|' << FormatNumber(result.stats.averageFps) << '|' << result.screenshotFilename << "|\n"; } } class VolumePerformanceSuiteTest : public VolumeIntegrationSceneFixture { protected: const char* GetSceneName() const override { return "VolumePerformanceSuite"; } void BuildScene() override; void ReleaseSceneResources() override; void ApplyQualityProfile(const QualityProfile& profile); void ApplyOptimizationProfile(const OptimizationProfile& profile); private: Material* mVolumeMaterial = nullptr; VolumeField* mVolumeField = nullptr; }; void VolumePerformanceSuiteTest::BuildScene() { mVolumeMaterial = CreateVolumetricMaterial( "VolumePerformanceMaterial", "Tests/Rendering/VolumePerformanceSuite/Volume.material", Vector4(1.0f, 1.0f, 1.0f, 1.0f), 0.2f, 1.0f, 2000.0f, 0.005f, Vector3(-0.72f, 0.16f, 0.67f), 8.0f, 1.0f, 1.0f, 1.0f); ASSERT_NE(mVolumeMaterial, nullptr); mVolumeField = LoadCloudVolumeField(); ASSERT_NE(mVolumeField, nullptr); GameObject* cameraObject = mScene->CreateGameObject("MainCamera"); auto* camera = cameraObject->AddComponent(); camera->SetPrimary(true); camera->SetFieldOfView(45.0f); camera->SetNearClipPlane(0.1f); camera->SetFarClipPlane(5000.0f); camera->SetClearColor(XCEngine::Math::Color(0.03f, 0.04f, 0.06f, 1.0f)); cameraObject->GetTransform()->SetLocalPosition(Vector3(-10.0f, 300.0f, -1200.0f)); cameraObject->GetTransform()->LookAt(Vector3(-10.0f, 73.0f, 0.0f)); GameObject* lightObject = mScene->CreateGameObject("MainDirectionalLight"); auto* light = lightObject->AddComponent(); light->SetLightType(LightType::Directional); light->SetColor(XCEngine::Math::Color(1.0f, 1.0f, 1.0f, 1.0f)); light->SetIntensity(1.0f); lightObject->GetTransform()->LookAt(Vector3(-0.5f, -0.8f, -0.3f)); GameObject* volumeObject = mScene->CreateGameObject("CloudVolume"); auto* volumeRenderer = volumeObject->AddComponent(); volumeRenderer->SetVolumeField(mVolumeField); volumeRenderer->SetMaterial(mVolumeMaterial); volumeRenderer->SetCastShadows(false); volumeRenderer->SetReceiveShadows(false); RenderSceneExtractor extractor; const RenderSceneData sceneData = extractor.Extract(*mScene, nullptr, kFrameWidth, kFrameHeight); ASSERT_EQ(sceneData.visibleItems.size(), 0u); ASSERT_EQ(sceneData.visibleVolumes.size(), 1u); ASSERT_TRUE(sceneData.lighting.HasMainDirectionalLight()); } void VolumePerformanceSuiteTest::ReleaseSceneResources() { delete mVolumeField; mVolumeField = nullptr; delete mVolumeMaterial; mVolumeMaterial = nullptr; } void VolumePerformanceSuiteTest::ApplyQualityProfile(const QualityProfile& profile) { ASSERT_NE(mVolumeMaterial, nullptr); mVolumeMaterial->SetFloat("_StepSize", profile.stepSize); mVolumeMaterial->SetFloat("_MaxSteps", profile.maxSteps); mVolumeMaterial->SetFloat("_LightSamples", 8.0f); mVolumeMaterial->SetFloat("_EnableEntryHdda", 1.0f); mVolumeMaterial->SetFloat("_EnableEmptySpaceSkipping", 1.0f); mVolumeMaterial->SetFloat("_EnableEarlyTermination", 1.0f); } void VolumePerformanceSuiteTest::ApplyOptimizationProfile(const OptimizationProfile& profile) { ASSERT_NE(mVolumeMaterial, nullptr); mVolumeMaterial->SetFloat("_StepSize", 1.0f); mVolumeMaterial->SetFloat("_MaxSteps", 2000.0f); mVolumeMaterial->SetFloat("_LightSamples", 8.0f); mVolumeMaterial->SetFloat("_EnableEntryHdda", profile.enableEntryHdda); mVolumeMaterial->SetFloat("_EnableEmptySpaceSkipping", profile.enableEmptySpaceSkipping); mVolumeMaterial->SetFloat("_EnableEarlyTermination", profile.enableEarlyTermination); } TEST_P(VolumePerformanceSuiteTest, MeasureQualityProfiles) { const std::array profiles = { QualityProfile{ "high_quality", 0.25f, 8000.0f, "volume_quality_high_d3d12.ppm" }, QualityProfile{ "balanced", 1.0f, 2000.0f, "volume_quality_balanced_d3d12.ppm" }, QualityProfile{ "high_performance", 4.0f, 512.0f, "volume_quality_performance_d3d12.ppm" } }; std::vector results; results.reserve(profiles.size()); for (const QualityProfile& profile : profiles) { ApplyQualityProfile(profile); const StableFrameStats stats = RenderAndMeasureStableFrames( kWarmupFrames, kMeasuredFrames, profile.screenshotFilename); EXPECT_GT(stats.averageFrameTimeMs, 0.0); EXPECT_GT(stats.averageFps, 0.0); const RenderingIntegrationTestUtils::PpmImage image = RenderingIntegrationTestUtils::LoadPpmImage(profile.screenshotFilename); EXPECT_EQ(image.width, kFrameWidth); EXPECT_EQ(image.height, kFrameHeight); results.push_back(QualityResult{ profile.name, profile.stepSize, profile.maxSteps, stats, profile.screenshotFilename }); } WriteQualityResults(results); } TEST_P(VolumePerformanceSuiteTest, MeasureOptimizationVariants) { const std::array profiles = { OptimizationProfile{ "full_optimization", 1.0f, 1.0f, 1.0f, "volume_opt_full_d3d12.ppm" }, OptimizationProfile{ "disable_entry_hdda", 0.0f, 1.0f, 1.0f, "volume_opt_no_entry_hdda_d3d12.ppm" }, OptimizationProfile{ "disable_empty_space_skipping", 1.0f, 0.0f, 1.0f, "volume_opt_no_empty_space_skip_d3d12.ppm" }, OptimizationProfile{ "disable_early_termination", 1.0f, 1.0f, 0.0f, "volume_opt_no_early_termination_d3d12.ppm" } }; std::vector results; results.reserve(profiles.size()); for (const OptimizationProfile& profile : profiles) { ApplyOptimizationProfile(profile); const StableFrameStats stats = RenderAndMeasureStableFrames( kWarmupFrames, kMeasuredFrames, profile.screenshotFilename); EXPECT_GT(stats.averageFrameTimeMs, 0.0); EXPECT_GT(stats.averageFps, 0.0); const RenderingIntegrationTestUtils::PpmImage image = RenderingIntegrationTestUtils::LoadPpmImage(profile.screenshotFilename); EXPECT_EQ(image.width, kFrameWidth); EXPECT_EQ(image.height, kFrameHeight); results.push_back(OptimizationResult{ profile.name, profile.enableEntryHdda, profile.enableEmptySpaceSkipping, profile.enableEarlyTermination, stats, profile.screenshotFilename }); } WriteOptimizationResults(results); } } // namespace INSTANTIATE_TEST_SUITE_P(D3D12, VolumePerformanceSuiteTest, ::testing::Values(XCEngine::RHI::RHIType::D3D12)); GTEST_API_ int main(int argc, char** argv) { return RunRenderingIntegrationTestMain(argc, argv); }