diff --git a/.github/workflows/testWindows.yml b/.github/workflows/testWindows.yml index 2d1f0d3ed..ec97fd218 100644 --- a/.github/workflows/testWindows.yml +++ b/.github/workflows/testWindows.yml @@ -64,7 +64,7 @@ jobs: timeout-minutes: 60 run: | cd "${{ inputs.unreal-binaries-path }}" - ./UnrealEditor-Cmd.exe "$env:TESTS_PROJECT_ROOT/TestsProject.uproject" -execcmds="Automation RunTests Cesium.;quit" -nullrhi -unattended -nosplash -ReportExportPath="$env:TESTS_PROJECT_LOGS" + ./UnrealEditor-Cmd.exe "$env:TESTS_PROJECT_ROOT/TestsProject.uproject" -execcmds="Automation RunTests Cesium.Unit.;quit" -nullrhi -unattended -nosplash -ReportExportPath="$env:TESTS_PROJECT_LOGS" - name: Display tests log if: always() run: | diff --git a/Documentation/developer-setup-windows.md b/Documentation/developer-setup-windows.md index b99ec69c1..3b4567904 100644 --- a/Documentation/developer-setup-windows.md +++ b/Documentation/developer-setup-windows.md @@ -74,6 +74,7 @@ To build a "Release" build of cesium-native, - Add a new configuration by clicking the `+` and choose `x64-Release`. - Select the new "x64-Release" from the Solution Configuration dropdown. - Right-click on `CMakeLists.txt` again and choose "Install". +> In Visual Studio, this defaults to the "RelWithDebInfo" configuration type. You can change this at any time ## Visual Studio Code diff --git a/Documentation/performance-profiling-setup-test.md b/Documentation/performance-profiling-setup-test.md new file mode 100644 index 000000000..b22469215 --- /dev/null +++ b/Documentation/performance-profiling-setup-test.md @@ -0,0 +1,29 @@ +# Set up a repeatable test + +We need an area of code to execute repeatedly, with as many variables locked down as possible. + + +### Set up Unreal +1) Open Unreal Editor (UnrealEditor.exe) +2) Create a blank map (project doesn't matter. Choose an existing one or create a new one) +3) Go to Edit->Plugins +4) Search for "Functional Testing plugin". Check it to enable it +![smaller](https://github.com/CesiumGS/cesium-unreal/assets/130494071/5a3bc9de-cdaf-4d9d-842d-104719426663) +5) Save all +6) Set this map as the 'Editor Startup Map' so it loads when starting from Visual Studio +![smaller 2](https://github.com/CesiumGS/cesium-unreal/assets/130494071/8ba5c6c2-8c97-4048-afe2-db74770d85cc) + + +### Build Release Code + +We need to make sure all our C++ code is building in release mode, preferably with debug symbols. + +> This assumes that you have already built your code successfully and are familiar with the concepts from our [developer setup guide](https://github.com/CesiumGS/cesium-unreal/blob/ue5-main/Documentation/developer-setup-windows.md). Although you could profile a debug build, it is typically more useful to build in release, since this is how a game is usually packaged. + +1) If building the cesium-native library, make sure you are using a release configuration derived from "RelWithDebInfo" +2) Open your Unreal project's Visual Studio solution (.sln). This example uses the solution generated from [cesium-unreal-samples](https://github.com/CesiumGS/cesium-unreal-samples) +3) Choose "Development Editor" + +![smaller 3](https://github.com/CesiumGS/cesium-unreal/assets/130494071/0e70065f-c717-466b-a92b-cab1dcfdd29b) + +4) From the menu, choose Build -> Build Solution diff --git a/Documentation/performance-profiling-with-cpu-usage.md b/Documentation/performance-profiling-with-cpu-usage.md new file mode 100644 index 000000000..b68844728 --- /dev/null +++ b/Documentation/performance-profiling-with-cpu-usage.md @@ -0,0 +1,83 @@ + +This guide will help you find performance problems in your C++ code using the [CPU Usage tool](https://learn.microsoft.com/en-us/visualstudio/profiling/beginners-guide-to-performance-profiling?view=vs-2022) included in Visual Studio's Diagnostic tools window. + +The CPU Usage tool is easy to set up with minimal impact on how your app is built or how it runs. If you use Visual Studio often, you may have this running already. This is a sampling-based profiler, with pros and cons detailed [here](https://learn.microsoft.com/en-us/visualstudio/profiling/understanding-performance-collection-methods-perf-profiler?view=vs-2022). + +# Set up a repeatable test + +In this example, we will use our Cesium performance tests. Follow the steps outlined [here](https://github.com/CesiumGS/cesium-unreal/blob/profiling-documentation/Documentation/performance-profiling-setup-test.md). + +# Prepare for capture + +### Visual Studio + +1) Open your project's Visual Studio solution (.sln). This example uses the solution generated from [cesium-unreal-samples](https://github.com/CesiumGS/cesium-unreal-samples) +2) From the menu, choose Debug->Windows->Show Diagnostic Tools +3) Configure it. Uncheck 'Memory Usage'. Under Settings, Uncheck "Enable CPU Profiling", we'll turn this back on later. + +DiagSetup + +4) Optionally, find two places in your code to set breakpoints. In our example, performance test start / end marks are perfect. + +![Breakpoint Set small](https://github.com/CesiumGS/cesium-unreal/assets/130494071/5a793b9c-fd68-42ed-96ae-6ec884c38951) + +>We could profile the entire debugging session if we needed to. But it's generally good practice to reduce your timing capture as much as possible. This can improve responsiveness when using resource intensive profiling tools, like memory tracking. + +# Run the timing capture session + +1) From Visual Studio, start your debugging session (Debug->Start Debugging, F5) +2) Find the performance tests in Unreal. Tools->Test Automation +![Automation Window small](https://github.com/CesiumGS/cesium-unreal/assets/130494071/d27e7d67-3658-4cb2-ab10-777498cba0da) + +3) Check "LoadTestDenver" +4) Click "Start Tests" +5) Your first break point should hit in Visual Studio +6) Go back to the Diagnostic Tools window, click on "Record CPU Profile". It should turn red. + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/ce0c7e86-c1ef-4a01-97fd-c97275b6f62b) + +7) Continue the debugging session (Debug->Continue, F5) +8) Your second break point should hit +9) Go back to the Diagnostic Tools window, you should now see a report + +# Interpret the report + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/a9fb3e0b-86f5-4239-b4ab-c7f9b1dba4a5) + +This can be a bit daunting at first, but most profiling tools have a similar workflow. + +### Start at the timeline + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/da733adc-6cae-4c89-8a6c-01a367667a0d) + +Note the highlighted area to the right where the CPU usage spikes. This corresponds to the breakpoints that we set. + +All data from the report will reflect this selection only. + +### Trace calls with high usage + +From the main window, click on "Open Details" +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/f34b5ee3-15b7-485a-a90a-8f71310b1b44) + +The CPU Usage window will appear. Set "Current View" to "Functions", then find the 'Self CPU' column and sort descending (down arrow). + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/20836178-8337-4d53-be58-f388db905f9f) + +This window now shows the functions that have the highest time spent within themselves only. Useful for finding individual functions that are called often or need to be optimized. + +In this example, `stbir_resample_horizontal_downsample` is of particular interest because it's in the code base we built. Entries with `[External]` or originate from an unfamiliar module are generally ignored, although it is useful to know we are calling into them. + +Right click on the `stbir_resample_horizontal_downsample` row, select "View in Call Tree". + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/e5a88c06-5b76-4a07-83be-db5147a961b2) + +# Draw conclusions + +The window above is starting to show some actionable information: +- 20% of the sampled CPU time was spent in `CesiumTextureUtility::loadTextureAnyThreadPart`. Basically, we're loading textures +- There are a variety of `stbir_XXX functions` that are taking the bulk of the time, and might be candidates for optimization +- The highest cost single function is `stbir_resample_horizontal_downsample`, but keep in mind the limits of a sampling profiler. We don't know how many times it was called, just that it was being executed ~6% of the time. + +Are these functions worth investigating and potentially optimizing? Maybe. Again, know this is a sampling profiler. Even if you optimize the highest cost function to 0.001%, you are only improving CPU efficiency. + +If your goal is to reach absolute numbers, like specific loading times, or frames per second, you may need another type of profiling tool. diff --git a/Documentation/performance-profiling-with-unreal-insights.md b/Documentation/performance-profiling-with-unreal-insights.md new file mode 100644 index 000000000..4a4e947f9 --- /dev/null +++ b/Documentation/performance-profiling-with-unreal-insights.md @@ -0,0 +1,108 @@ +This guide will help you find performance problems in your C++ code using [Unreal Insights](https://docs.unrealengine.com/5.0/en-US/unreal-insights-in-unreal-engine/), included with Unreal Engine. + +Unreal Insights can display the scope of timing events as well as activity across threads. There is minimal impact to app execution, and you can set up your own custom events. It provides more functionality than an exclusive [CPU sampling-based profiler](https://learn.microsoft.com/en-us/visualstudio/profiling/understanding-performance-collection-methods-perf-profiler?view=vs-2022), although both tools can complement each other. + +# Set up a repeatable test + +In this example, we will use our Cesium performance tests. Follow the steps outlined [here](https://github.com/CesiumGS/cesium-unreal/blob/profiling-documentation/Documentation/performance-profiling-setup-test.md). + +# Prepare for capture + +### Unreal Editor +1) In Visual Studio, click Debug -> Start Debugging (F5) +2) In Unreal, click Tools->Test Automation +3) Check the Cesium.Performance.LoadTestDenver row (don't start the test yet) +![Automation Window small](https://github.com/CesiumGS/cesium-unreal/assets/130494071/d27e7d67-3658-4cb2-ab10-777498cba0da) +4) Click Tools->Run Unreal Insights +5) In Unreal Insights, click on the "Connection" tab (don't connect yet) + +> You can also find UnrealInsights.exe in UE_5.X\Engine\Binaries\Win64 + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/eadd4013-ca10-4b61-bb7d-0ab233440a39) + +# Run the timing capture session +1) In Unreal Insights, click "Connect" +2) In Unreal Editor, click "Start Tests" (you should already have the Test Automation window open) +3) When the test ends, close Unreal Editor. We don't need it anymore. +4) In Unreal Insights, click the Trace Store tab, notice the trace that was just created +5) Click on it, then click on the 'Open Trace' button + +> On the right side, there's a "Explore Trace Store Directory" button. You can click on this to delete or organize your traces + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/f1e34fbc-35cd-4bc3-b935-5e322f5d9ba6) + +# Interpret the report + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/9cab7cf1-ab6d-4b58-a362-fc21ccff0334) + +By default, the Timings Insights Tab is shown. More detail can be found [here](https://docs.unrealengine.com/5.0/en-US/timing-insights-in-unreal-engine-5/). + +For this session, there are several sections of interest for us: +- The Frames panel (top, a timeline view) +- The Timings panel (middle, mostly empty because nothing is selected) +- The Log Panel (bottom) +- The Timers tab (right) + +### Isolate your area of interest + +1) In the Log Panel, search for "mark". This will show the logging of our timing marks for our test. Select the start mark, then hold shift and down arrow to select the end mark too +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/7cccc075-edf7-4b8e-b704-b9efc9de1a3c) + +2) Notice that the Timings panel is now displaying timing data, with a specific time region highlighted +3) In the Timings panel, select View Mode -> Compact Mode to see more of a bird's eye view +4) Select All Tracks and uncheck the following threads that don't have much activity for our test: ```RenderThread 3-7, BackgroundThreadPool #1, ForegroundWorker #0-#1, DDC IO ThreadPool #0-#2, Reserve Worker #0-#13, AudioMixerXXX``` +5) Use the mouse wheel to zoom in to selected region. Right click and drag to pan left and right. + +The view should be a lot cleaner now +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/aca0680e-3dc3-4d23-9838-8f598f384089) + + +### Examine high traffic timers + +Let's look at the Timers tab. + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/823fc4d4-25d3-40dc-9b41-1cffee560454) + +Every row is a timing event. Some events come from the engine, some are custom timers in the Cesium for Unreal plugin code. You'll notice that Incl is sorting descending, showing the events with the highest inclusive time. + +> You may feel the need to jump right in to `Cesium::CreateRHITexture2D`. It seems to have one of the highest exclusive times (Excl) of any of the events in the list, 1 second. After all, our selection is only 1.2 seconds long, so this must be the performance bottleneck right? Hold on. The total sampled time at the top (CPU) is 19.8s, indicating the times are the total sampled times across threads, not absolute session duration. + +Given that the sampled time of the highest cost calls are actually somewhat small compared to the total sampled CPU time, our bottleneck is most likely outside of our timed events. + +This brings us to... + + +### Examine low use areas + +1) Go back to the Timings panel. +2) In All Tracks, check Game Frames +2) Turn off compact mode by unchecking "View Mode->Compact Mode". +3) In View Mode, set "Depth Limit" to "4 lanes" +4) Zoom and pan to an area of the selection where the background workers haven't started loading yet + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/caa47e66-b088-46d8-9aa0-1916a65777de) + +The selected area is the first phase of the loading test. This is a region between when the start mark was logged until when background workers start loading models. + +It lasts about 8 game frames, or 388 ms, and does not seem to be making use of background threads at all. Could be something to investigate. + + +### Examine fragmented use areas + +1) Find the Timings panel +2) In View Mode, set "Depth Limit" to "Unlimited" +3) Zoom a bit into an area where our background workers are very busy + +![image](https://github.com/CesiumGS/cesium-unreal/assets/130494071/29d7c3a2-3710-4a2b-a4f1-09050bdb9287) + +This selected area is zoomed in enough to see that the background workers are all calling the same functions. They finish their work, then wait for more work to be available. Some of this work seems to take longer than others, especially at the beginning. + +Note the gaps between the work. In general, there seems to be more inactivity than activity during this timeframe. Ideally, we would like to see all work squished together, with no waits in between. Improvements like this should bring the total execution duration lower. In this case, total load time. + +# Draw conclusions + +We've identified some actionable information so far, even if it only leads to investigation: +* There is a 388 ms low use area at the beginning of the test (~30%). What is happening here? Can it be faster? +* During high use areas, background threads seems to inactive more than they are active. Why? Can this be optimized so they are always active? + +It's very common for profiling to be an iterative process. The result of a profiling session could easily be just adding more event timers, or digging deeper into how something works. Before we can expect that code change that results in a heroic 10x speedup, we need to be able to see clearly what is going on. diff --git a/Documentation/release-process.md b/Documentation/release-process.md index c1eea9dcc..cfd54f464 100644 --- a/Documentation/release-process.md +++ b/Documentation/release-process.md @@ -2,11 +2,15 @@ This is the process we follow when releasing a new version of Cesium for Unreal on GitHub and on the Unreal Engine Marketplace. -## Test the release candidate - +## Verify the code +* Update any hard coded API keys in the code + * CesiumSceneGeneration.cpp, testIonToken - Make sure matches samples project + * Google3dTilesLoadTest.cpp, testGoogleUrl - Make sure matches samples project * Verify that the cesium-native submodule in the `extern` directory references the expected commit of cesium-native. Update it if necessary. Verify that CI has completed successfully for that commit of cesium-native. * Merge `ue4-main` into `ue5-main`. * Wait for CI to complete for the `ue4-main` and `ue5-main` branches. Verify that it does so successfully. + +## Test the release candidate * Remove all existing copies of the Cesium for Unreal plugin from the engine plugin directories on your system. On Windows this is usually `C:\Program Files\Epic Games\UE_4.26\Engine\Plugins\Marketplace`, `C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\Marketplace`, and `C:\Program Files\Epic Games\UE_5.0\Engine\Plugins\Marketplace`. * Download the `UE4.27-AllPlatforms` or `UE4.26-AllPlatforms` for the `ue4-main` branch of cesium-unreal. Extract it to the appropriate Unreal Engine installation's engine plugins directory. * In the `ue5-main` branch, go to https://github.com/CesiumGS/cesium-unreal/actions and click the most recent build of the branch (it should be near the top). Scroll down to "Artifcats" and download the artifact that doesn't have an operating system in its name. It will also be the largest artifact. Extract it to `C:\Program Files\Epic Games\UE_5.0\Engine\Plugins\Marketplace` or equivalent. diff --git a/Source/CesiumEditor/Private/CesiumEditorSubLevelMutex.cpp b/Source/CesiumEditor/Private/CesiumEditorSubLevelMutex.cpp index 3e89862b2..4d1a100be 100644 --- a/Source/CesiumEditor/Private/CesiumEditorSubLevelMutex.cpp +++ b/Source/CesiumEditor/Private/CesiumEditorSubLevelMutex.cpp @@ -6,6 +6,7 @@ #include "CesiumSubLevelComponent.h" #include "CesiumSubLevelSwitcherComponent.h" #include "Components/ActorComponent.h" +#include "Engine/World.h" #include "LevelInstance/LevelInstanceActor.h" CesiumEditorSubLevelMutex::CesiumEditorSubLevelMutex() { diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 16d60d85a..cd64188f4 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -877,27 +877,41 @@ class UnrealResourcePreparer void ACesium3DTileset::UpdateLoadStatus() { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateLoadStatus) - this->LoadProgress = this->_pTileset->computeLoadProgress(); + float nativeLoadProgress = this->_pTileset->computeLoadProgress(); - if (this->LoadProgress < 100 || - this->_lastTilesWaitingForOcclusionResults > 0) { - this->_activeLoading = true; - } else if (this->_activeLoading && this->LoadProgress == 100) { + // If native tileset still loading, just copy its progress + if (nativeLoadProgress < 100) { + this->LoadProgress = nativeLoadProgress; + return; + } + + // Native tileset is 100% loaded, but there might be a few frames where + // nothing needs to be loaded as we are waiting for occlusion results to come + // back, which means we are not done with loading all the tiles in the tileset + // yet. Interpret this as 99% (almost) done + if (this->_lastTilesWaitingForOcclusionResults > 0) { + this->LoadProgress = 99; + return; + } - // There might be a few frames where nothing needs to be loaded as we - // are waiting for occlusion results to come back, which means we are not - // done with loading all the tiles in the tileset yet. - if (this->_lastTilesWaitingForOcclusionResults == 0) { - TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::BroadcastOnTilesetLoaded) + // If we have tiles to hide next frame, we haven't completely finished loading + // yet. We need to tick once more. We're really close to done. + if (!this->_tilesToHideNextFrame.empty()) { + this->LoadProgress = glm::min(this->LoadProgress, 99.9999f); + return; + } - // Tileset just finished loading, we broadcast the update - UE_LOG(LogCesium, Verbose, TEXT("Broadcasting OnTileLoaded")); - OnTilesetLoaded.Broadcast(); + // We can now report 100 percent loaded + float lastLoadProgress = this->LoadProgress; + this->LoadProgress = 100; - // Tileset remains 100% loaded if we don't have to reload it - // so we don't want to keep on sending finished loading updates - this->_activeLoading = false; - } + // Only broadcast the update when we first hit 100%, not everytime + if (lastLoadProgress != LoadProgress) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::BroadcastOnTilesetLoaded) + + // Tileset just finished loading, we broadcast the update + UE_LOG(LogCesium, Verbose, TEXT("Broadcasting OnTileLoaded")); + OnTilesetLoaded.Broadcast(); } } @@ -1032,6 +1046,8 @@ void ACesium3DTileset::LoadTileset() { this->_startTime = std::chrono::high_resolution_clock::now(); + this->LoadProgress = 0; + Cesium3DTilesSelection::TilesetOptions options; options.enableOcclusionCulling = @@ -2061,22 +2077,25 @@ void ACesium3DTileset::Tick(float DeltaTime) { CreateViewStateFromViewParameters(camera, unrealWorldToCesiumTileset)); } - const Cesium3DTilesSelection::ViewUpdateResult& result = - this->_captureMovieMode - ? this->_pTileset->updateViewOffline(frustums) - : this->_pTileset->updateView(frustums, DeltaTime); - updateLastViewUpdateResultState(result); - this->UpdateLoadStatus(); + const Cesium3DTilesSelection::ViewUpdateResult* pResult; + if (this->_captureMovieMode) { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateViewOffline) + pResult = &this->_pTileset->updateViewOffline(frustums); + } else { + TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::updateView) + pResult = &this->_pTileset->updateView(frustums, DeltaTime); + } + updateLastViewUpdateResultState(*pResult); - removeCollisionForTiles(result.tilesFadingOut); + removeCollisionForTiles(pResult->tilesFadingOut); removeVisibleTilesFromList( _tilesToHideNextFrame, - result.tilesToRenderThisFrame); + pResult->tilesToRenderThisFrame); hideTiles(_tilesToHideNextFrame); _tilesToHideNextFrame.clear(); - for (Cesium3DTilesSelection::Tile* pTile : result.tilesFadingOut) { + for (Cesium3DTilesSelection::Tile* pTile : pResult->tilesFadingOut) { Cesium3DTilesSelection::TileRenderContent* pRenderContent = pTile->getContent().getRenderContent(); if (!this->UseLodTransitions || @@ -2086,19 +2105,22 @@ void ACesium3DTileset::Tick(float DeltaTime) { } } - showTilesToRender(result.tilesToRenderThisFrame); + showTilesToRender(pResult->tilesToRenderThisFrame); if (this->UseLodTransitions) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::UpdateTileFades) - for (Cesium3DTilesSelection::Tile* pTile : result.tilesToRenderThisFrame) { + for (Cesium3DTilesSelection::Tile* pTile : + pResult->tilesToRenderThisFrame) { updateTileFade(pTile, true); } - for (Cesium3DTilesSelection::Tile* pTile : result.tilesFadingOut) { + for (Cesium3DTilesSelection::Tile* pTile : pResult->tilesFadingOut) { updateTileFade(pTile, false); } } + + this->UpdateLoadStatus(); } void ACesium3DTileset::EndPlay(const EEndPlayReason::Type EndPlayReason) { diff --git a/Source/CesiumRuntime/Private/CesiumRuntime.cpp b/Source/CesiumRuntime/Private/CesiumRuntime.cpp index d0289b183..a46aeada0 100644 --- a/Source/CesiumRuntime/Private/CesiumRuntime.cpp +++ b/Source/CesiumRuntime/Private/CesiumRuntime.cpp @@ -100,20 +100,28 @@ std::string getCacheDatabaseName() { } // namespace +std::shared_ptr& getCacheDatabase() { + static int MaxCacheItems = + GetDefault()->MaxCacheItems; + + static std::shared_ptr pCacheDatabase = + std::make_shared( + spdlog::default_logger(), + getCacheDatabaseName(), + MaxCacheItems); + + return pCacheDatabase; +} + const std::shared_ptr& getAssetAccessor() { static int RequestsPerCachePrune = GetDefault()->RequestsPerCachePrune; - static int MaxCacheItems = - GetDefault()->MaxCacheItems; static std::shared_ptr pAssetAccessor = std::make_shared( std::make_shared( spdlog::default_logger(), std::make_shared(), - std::make_shared( - spdlog::default_logger(), - getCacheDatabaseName(), - MaxCacheItems), + getCacheDatabase(), RequestsPerCachePrune)); return pAssetAccessor; } diff --git a/Source/CesiumRuntime/Private/Tests/CesiumCameraManager.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumCameraManager.spec.cpp index a8b5ba961..a00b81f1a 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumCameraManager.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumCameraManager.spec.cpp @@ -7,7 +7,7 @@ BEGIN_DEFINE_SPEC( FCesiumCameraManagerSpec, - "Cesium.CameraManager", + "Cesium.Unit.CameraManager", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FCesiumCameraManagerSpec) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp index b14391ac4..ace818666 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdAttribute.spec.cpp @@ -7,7 +7,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumFeatureIdAttributeSpec, - "Cesium.FeatureIdAttribute", + "Cesium.Unit.FeatureIdAttribute", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp index a70bea7b0..e28971251 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdSet.spec.cpp @@ -8,7 +8,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumFeatureIdSetSpec, - "Cesium.FeatureIdSet", + "Cesium.Unit.FeatureIdSet", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp index 9bb1f047f..b3ebd1971 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumFeatureIdTexture.spec.cpp @@ -7,7 +7,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumFeatureIdTextureSpec, - "Cesium.FeatureIdTexture", + "Cesium.Unit.FeatureIdTexture", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGeoreference.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGeoreference.spec.cpp index 571a69fb0..ac35d51c0 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGeoreference.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGeoreference.spec.cpp @@ -12,7 +12,7 @@ using namespace CesiumUtility; BEGIN_DEFINE_SPEC( FCesiumGeoreferenceSpec, - "Cesium.Georeference", + "Cesium.Unit.Georeference", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp index a21ee5be5..5d7b3b202 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumGlobeAnchor.spec.cpp @@ -8,7 +8,7 @@ BEGIN_DEFINE_SPEC( FCesiumGlobeAnchorSpec, - "Cesium.GlobeAnchor", + "Cesium.Unit.GlobeAnchor", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.cpp b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.cpp new file mode 100644 index 000000000..7bc304c1c --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.cpp @@ -0,0 +1,232 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#if WITH_EDITOR + +#include "CesiumLoadTestCore.h" + +#include "CesiumAsync/ICacheDatabase.h" +#include "CesiumRuntime.h" + +#include "Editor.h" +#include "Settings/LevelEditorPlaySettings.h" +#include "Tests/AutomationCommon.h" +#include "Tests/AutomationEditorCommon.h" +#include "UnrealClient.h" + +namespace Cesium { + +struct LoadTestContext { + SceneGenerationContext creationContext; + SceneGenerationContext playContext; + + float cameraFieldOfView = 90.0f; + + bool testInProgress; + double startMark; + double endMark; + + void reset() { + creationContext = playContext = SceneGenerationContext(); + testInProgress = false; + startMark = endMark = 0; + } +}; + +LoadTestContext gLoadTestContext; + +DEFINE_LATENT_AUTOMATION_COMMAND_FOUR_PARAMETER( + TimeLoadingCommand, + FString, + loggingName, + LoadTestContext&, + context, + std::function, + setupStep, + std::function, + verifyStep); +bool TimeLoadingCommand::Update() { + + if (!context.testInProgress) { + + // Bind all play in editor pointers + context.playContext.initForPlay(context.creationContext); + context.playContext.syncWorldCamera(); + + if (setupStep) + setupStep(context.playContext); + + // Start test mark, turn updates back on + context.startMark = FPlatformTime::Seconds(); + UE_LOG(LogCesium, Display, TEXT("-- Load start mark -- %s"), *loggingName); + + context.playContext.setSuspendUpdate(false); + + context.testInProgress = true; + + // Return, let world tick + return false; + } + + double timeMark = FPlatformTime::Seconds(); + double testElapsedTime = timeMark - context.startMark; + + // The command is over if tilesets are loaded, or timed out + // Wait for a maximum of 30 seconds + const size_t testTimeout = 30; + bool tilesetsloaded = context.playContext.areTilesetsDoneLoading(); + bool timedOut = testElapsedTime >= testTimeout; + + if (tilesetsloaded || timedOut) { + context.endMark = timeMark; + UE_LOG(LogCesium, Display, TEXT("-- Load end mark -- %s"), *loggingName); + + if (timedOut) { + UE_LOG( + LogCesium, + Error, + TEXT("TIMED OUT: Loading stopped after %.2f seconds"), + testElapsedTime); + } else { + UE_LOG( + LogCesium, + Display, + TEXT("Tileset load completed in %.2f seconds"), + testElapsedTime); + } + + if (verifyStep) + verifyStep(context.playContext); + + context.testInProgress = false; + + // Command is done + return true; + } + + // Let world tick, we'll come back to this command + return false; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER( + LoadTestScreenshotCommand, + FString, + screenshotName); +bool LoadTestScreenshotCommand::Update() { + UE_LOG( + LogCesium, + Display, + TEXT("Requesting screenshot to /Saved/Screenshots/WindowsEditor...")); + + // Add a dash to separate name from unique index of screen shot + // Also add a dot to keep the base path logic from stripping away too much + FString requestFilename = screenshotName + "-" + "."; + FScreenshotRequest::RequestScreenshot(requestFilename, false, true); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER( + TestCleanupCommand, + LoadTestContext&, + context); +bool TestCleanupCommand::Update() { + // Turn on the editor tileset updates so we can see what we loaded + gLoadTestContext.creationContext.setSuspendUpdate(false); + return true; +} + +DEFINE_LATENT_AUTOMATION_COMMAND(WaitForPIECommand); +bool WaitForPIECommand::Update() { + if (!GEditor || !GEditor->IsPlayingSessionInEditor()) + return false; + UE_LOG(LogCesium, Display, TEXT("Play in Editor ready...")); + return true; +} + +void clearCacheDb() { + std::shared_ptr pCacheDatabase = + getCacheDatabase(); + pCacheDatabase->clearAll(); +} + +bool RunLoadTest( + const FString& testName, + std::function locationSetup, + const std::vector& testPasses, + int viewportWidth, + int viewportHeight) { + + gLoadTestContext.reset(); + + // + // Programmatically set up the world + // + UE_LOG(LogCesium, Display, TEXT("Creating common world objects...")); + createCommonWorldObjects(gLoadTestContext.creationContext); + + // Configure location specific objects + UE_LOG(LogCesium, Display, TEXT("Setting up location...")); + locationSetup(gLoadTestContext.creationContext); + gLoadTestContext.creationContext.trackForPlay(); + + // Halt tileset updates and reset them + gLoadTestContext.creationContext.setSuspendUpdate(true); + gLoadTestContext.creationContext.refreshTilesets(); + clearCacheDb(); + + // Let the editor viewports see the same thing the test will + gLoadTestContext.creationContext.syncWorldCamera(); + + // + // Start async commands + // + + // Wait for shaders. Shader compiles could affect performance + ADD_LATENT_AUTOMATION_COMMAND(FWaitForShadersToFinishCompiling); + + // Queue play in editor and set desired viewport size + FRequestPlaySessionParams Params; + Params.WorldType = EPlaySessionWorldType::PlayInEditor; + Params.EditorPlaySettings = NewObject(); + Params.EditorPlaySettings->NewWindowWidth = viewportWidth; + Params.EditorPlaySettings->NewWindowHeight = viewportHeight; + Params.EditorPlaySettings->EnableGameSound = false; + GEditor->RequestPlaySession(Params); + + // Wait until PIE is ready + ADD_LATENT_AUTOMATION_COMMAND(WaitForPIECommand()); + + // Wait to show distinct gap in profiler + ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f)); + + std::vector::const_iterator it; + for (it = testPasses.begin(); it != testPasses.end(); ++it) { + const TestPass& pass = *it; + + // Do our timing capture + FString loggingName = testName + "-" + pass.name; + + ADD_LATENT_AUTOMATION_COMMAND(TimeLoadingCommand( + loggingName, + gLoadTestContext, + pass.setupStep, + pass.verifyStep)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f)); + + FString screenshotName = testName + "-" + pass.name; + ADD_LATENT_AUTOMATION_COMMAND(LoadTestScreenshotCommand(screenshotName)) + + ADD_LATENT_AUTOMATION_COMMAND(FWaitLatentCommand(1.0f)); + } + + // End play in editor + ADD_LATENT_AUTOMATION_COMMAND(FEndPlayMapCommand()); + + ADD_LATENT_AUTOMATION_COMMAND(TestCleanupCommand(gLoadTestContext)); + + return true; +} + +}; // namespace Cesium + +#endif diff --git a/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.h b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.h new file mode 100644 index 000000000..75e5fcb74 --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestCore.h @@ -0,0 +1,28 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#pragma once + +#if WITH_EDITOR + +#include + +#include "CesiumSceneGeneration.h" + +namespace Cesium { + +struct TestPass { + FString name; + std::function setupStep; + std::function verifyStep; +}; + +bool RunLoadTest( + const FString& testName, + std::function locationSetup, + const std::vector& testPasses, + int viewportWidth, + int viewportHeight); + +}; // namespace Cesium + +#endif diff --git a/Source/CesiumRuntime/Private/Tests/CesiumLoadTestSamples.cpp b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestSamples.cpp new file mode 100644 index 000000000..a4ad7ebf2 --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/CesiumLoadTestSamples.cpp @@ -0,0 +1,147 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#if WITH_EDITOR + +#include "CesiumLoadTestCore.h" + +#include "Misc/AutomationTest.h" + +#include "CesiumGltfComponent.h" +#include "CesiumIonRasterOverlay.h" +#include "GlobeAwareDefaultPawn.h" + +using namespace Cesium; + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FCesiumSampleLoadDenver, + "Cesium.Performance.SampleLoadDenver", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FCesiumSampleLoadMontrealPointCloud, + "Cesium.Performance.SampleLoadMontrealPointCloud", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +void refreshSampleTilesets(SceneGenerationContext& context) { + context.refreshTilesets(); +} + +void setupForDenver(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-104.988892, 39.743462, 1798.679443), + FVector(0, 0, 0), + FRotator(-5.2, -149.4, 0), + 90.0f); + + // Add Cesium World Terrain + ACesium3DTileset* worldTerrainTileset = + context.world->SpawnActor(); + worldTerrainTileset->SetTilesetSource(ETilesetSource::FromCesiumIon); + worldTerrainTileset->SetIonAssetID(1); + worldTerrainTileset->SetIonAccessToken(SceneGenerationContext::testIonToken); + worldTerrainTileset->SetActorLabel(TEXT("Cesium World Terrain")); + + // Bing Maps Aerial overlay + UCesiumIonRasterOverlay* pOverlay = NewObject( + worldTerrainTileset, + FName("Bing Maps Aerial"), + RF_Transactional); + pOverlay->MaterialLayerKey = TEXT("Overlay0"); + pOverlay->IonAssetID = 2; + pOverlay->SetActive(true); + pOverlay->OnComponentCreated(); + worldTerrainTileset->AddInstanceComponent(pOverlay); + + // Aerometrex Denver + ACesium3DTileset* aerometrexTileset = + context.world->SpawnActor(); + aerometrexTileset->SetTilesetSource(ETilesetSource::FromCesiumIon); + aerometrexTileset->SetIonAssetID(354307); + aerometrexTileset->SetIonAccessToken(SceneGenerationContext::testIonToken); + aerometrexTileset->SetMaximumScreenSpaceError(2.0); + aerometrexTileset->SetActorLabel(TEXT("Aerometrex Denver")); + + context.tilesets.push_back(worldTerrainTileset); + context.tilesets.push_back(aerometrexTileset); +} + +void setupForMontrealPointCloud(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-73.616526, 45.57335, 95.048859), + FVector(0, 0, 0), + FRotator(-90.0, 0.0, 0.0), + 90.0f); + + ACesium3DTileset* montrealTileset = + context.world->SpawnActor(); + montrealTileset->SetTilesetSource(ETilesetSource::FromCesiumIon); + montrealTileset->SetIonAssetID(28945); + montrealTileset->SetIonAccessToken(SceneGenerationContext::testIonToken); + montrealTileset->SetMaximumScreenSpaceError(16.0); + montrealTileset->SetActorLabel(TEXT("Montreal Point Cloud")); + + context.tilesets.push_back(montrealTileset); +} + +bool FCesiumSampleLoadDenver::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", refreshSampleTilesets, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForDenver, + testPasses, + 1024, + 768); +} + +bool FCesiumSampleLoadMontrealPointCloud::RunTest(const FString& Parameters) { + auto adjustCamera = [this](SceneGenerationContext& context) { + // Zoom way out + context.startPosition = FVector(0, 0, 7240000.0); + context.startRotation = FRotator(-90.0, 0.0, 0.0); + context.syncWorldCamera(); + + context.pawn->SetActorLocation(context.startPosition); + }; + + auto verifyVisibleTiles = [this](SceneGenerationContext& context) { + Cesium3DTilesSelection::Tileset* pTileset = + context.tilesets[0]->GetTileset(); + if (TestNotNull("Tileset", pTileset)) { + int visibleTiles = 0; + pTileset->forEachLoadedTile([&](Cesium3DTilesSelection::Tile& tile) { + if (tile.getState() != Cesium3DTilesSelection::TileLoadState::Done) + return; + const Cesium3DTilesSelection::TileContent& content = tile.getContent(); + const Cesium3DTilesSelection::TileRenderContent* pRenderContent = + content.getRenderContent(); + if (!pRenderContent) { + return; + } + + UCesiumGltfComponent* Gltf = static_cast( + pRenderContent->getRenderResources()); + if (Gltf && Gltf->IsVisible()) { + ++visibleTiles; + } + }); + + TestEqual("visibleTiles", visibleTiles, 1); + } + }; + + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Adjust", adjustCamera, verifyVisibleTiles}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForMontrealPointCloud, + testPasses, + 512, + 512); +} + +#endif diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataConversions.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataConversions.spec.cpp index 90c0f4b39..89b21ff41 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataConversions.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataConversions.spec.cpp @@ -8,7 +8,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumMetadataConversionsSpec, - "Cesium.MetadataConversions", + "Cesium.Unit.MetadataConversions", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FCesiumMetadataConversionsSpec) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp index cf239f920..a9c43bfd5 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataPickingBlueprintLibrary.spec.cpp @@ -11,7 +11,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumMetadataPickingSpec, - "Cesium.MetadataPicking", + "Cesium.Unit.MetadataPicking", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp index b10c65557..94700accb 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumMetadataValue.spec.cpp @@ -8,7 +8,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumMetadataValueSpec, - "Cesium.MetadataValue", + "Cesium.Unit.MetadataValue", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FCesiumMetadataValueSpec) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp index 51e41e371..f7e0a678f 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumOriginShiftComponent.spec.cpp @@ -15,7 +15,7 @@ BEGIN_DEFINE_SPEC( FCesiumOriginShiftComponentSpec, - "Cesium.OriginShiftComponent", + "Cesium.Unit.OriginShiftComponent", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp index cc28df267..ee885ecc4 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPrimitiveFeatures.spec.cpp @@ -7,7 +7,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumPrimitiveFeaturesSpec, - "Cesium.PrimitiveFeatures", + "Cesium.Unit.PrimitiveFeatures", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp index ce538820f..d76a0812b 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyArray.spec.cpp @@ -6,7 +6,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumPropertyArraySpec, - "Cesium.PropertyArray", + "Cesium.Unit.PropertyArray", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FCesiumPropertyArraySpec) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp index 487a9da38..a589f438b 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTable.spec.cpp @@ -9,7 +9,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumPropertyTableSpec, - "Cesium.PropertyTable", + "Cesium.Unit.PropertyTable", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) Model model; diff --git a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp index a2bb04aa8..a9f136196 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/CesiumPropertyTableProperty.spec.cpp @@ -8,7 +8,7 @@ using namespace CesiumGltf; BEGIN_DEFINE_SPEC( FCesiumPropertyTablePropertySpec, - "Cesium.PropertyTableProperty", + "Cesium.Unit.PropertyTableProperty", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FCesiumPropertyTablePropertySpec) diff --git a/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.cpp b/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.cpp new file mode 100644 index 000000000..6c8c98b57 --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.cpp @@ -0,0 +1,164 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#if WITH_EDITOR + +#include "CesiumSceneGeneration.h" + +#include "Tests/AutomationEditorCommon.h" + +#include "GameFramework/PlayerStart.h" +#include "LevelEditorViewport.h" + +#include "Cesium3DTileset.h" +#include "CesiumGeoreference.h" +#include "CesiumSunSky.h" +#include "GlobeAwareDefaultPawn.h" + +#include "CesiumTestHelpers.h" + +namespace Cesium { + +FString SceneGenerationContext::testIonToken( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyOGUxNjFmMy1mY2ZhLTQwMmEtYTNkYy1kZmExMGJjNjdlNTkiLCJpZCI6MjU5LCJpYXQiOjE2OTYxOTg1MTl9.QN5_xydinXOHF0xqy2zwQ5Hh4I5pVcLeMaqiJ9ZEsD4"); + +void SceneGenerationContext::setCommonProperties( + const FVector& origin, + const FVector& position, + const FRotator& rotation, + float fieldOfView) { + startPosition = position; + startRotation = rotation; + startFieldOfView = fieldOfView; + + georeference->SetOriginLongitudeLatitudeHeight(origin); + + pawn->SetActorLocation(startPosition); + pawn->SetActorRotation(startRotation); + + TInlineComponentArray cameras; + pawn->GetComponents(cameras); + for (UCameraComponent* cameraComponent : cameras) + cameraComponent->SetFieldOfView(startFieldOfView); +} + +void SceneGenerationContext::refreshTilesets() { + std::vector::iterator it; + for (it = tilesets.begin(); it != tilesets.end(); ++it) + (*it)->RefreshTileset(); +} + +void SceneGenerationContext::setSuspendUpdate(bool suspend) { + std::vector::iterator it; + for (it = tilesets.begin(); it != tilesets.end(); ++it) + (*it)->SuspendUpdate = suspend; +} + +bool SceneGenerationContext::areTilesetsDoneLoading() { + if (tilesets.empty()) + return false; + + std::vector::const_iterator it; + for (it = tilesets.begin(); it != tilesets.end(); ++it) { + ACesium3DTileset* tileset = *it; + + int progress = (int)tileset->GetLoadProgress(); + if (progress != 100) { + // We aren't done + return false; + } + } + return true; +} + +void SceneGenerationContext::trackForPlay() { + CesiumTestHelpers::trackForPlay(sunSky); + CesiumTestHelpers::trackForPlay(georeference); + CesiumTestHelpers::trackForPlay(pawn); + + std::vector::iterator it; + for (it = tilesets.begin(); it != tilesets.end(); ++it) { + ACesium3DTileset* tileset = *it; + CesiumTestHelpers::trackForPlay(tileset); + } +} + +void SceneGenerationContext::initForPlay( + SceneGenerationContext& creationContext) { + world = GEditor->PlayWorld; + sunSky = CesiumTestHelpers::findInPlay(creationContext.sunSky); + georeference = CesiumTestHelpers::findInPlay(creationContext.georeference); + pawn = CesiumTestHelpers::findInPlay(creationContext.pawn); + + startPosition = creationContext.startPosition; + startRotation = creationContext.startRotation; + startFieldOfView = creationContext.startFieldOfView; + + tilesets.clear(); + + std::vector& creationTilesets = creationContext.tilesets; + std::vector::iterator it; + for (it = creationTilesets.begin(); it != creationTilesets.end(); ++it) { + ACesium3DTileset* creationTileset = *it; + ACesium3DTileset* tileset = CesiumTestHelpers::findInPlay(creationTileset); + tilesets.push_back(tileset); + } +} + +void SceneGenerationContext::syncWorldCamera() { + assert(GEditor); + + if (GEditor->IsPlayingSessionInEditor()) { + // If in PIE, set the player + assert(world->GetNumPlayerControllers() == 1); + + APlayerController* controller = world->GetFirstPlayerController(); + assert(controller); + + controller->ClientSetLocation(startPosition, startRotation); + + APlayerCameraManager* cameraManager = controller->PlayerCameraManager; + assert(cameraManager); + + cameraManager->SetFOV(startFieldOfView); + } else { + // If editing, set any viewports + for (FLevelEditorViewportClient* ViewportClient : + GEditor->GetLevelViewportClients()) { + if (ViewportClient == NULL) + continue; + ViewportClient->SetViewLocation(startPosition); + ViewportClient->SetViewRotation(startRotation); + if (ViewportClient->ViewportType == LVT_Perspective) + ViewportClient->ViewFOV = startFieldOfView; + ViewportClient->Invalidate(); + } + } +} + +void createCommonWorldObjects(SceneGenerationContext& context) { + + context.world = FAutomationEditorCommonUtils::CreateNewMap(); + + context.sunSky = context.world->SpawnActor(); + + APlayerStart* playerStart = context.world->SpawnActor(); + + FSoftObjectPath objectPath( + TEXT("Class'/CesiumForUnreal/DynamicPawn.DynamicPawn_C'")); + TSoftObjectPtr DynamicPawn = TSoftObjectPtr(objectPath); + + context.georeference = + ACesiumGeoreference::GetDefaultGeoreference(context.world); + context.pawn = context.world->SpawnActor( + Cast(DynamicPawn.LoadSynchronous())); + + context.pawn->AutoPossessPlayer = EAutoReceiveInput::Player0; + + AWorldSettings* pWorldSettings = context.world->GetWorldSettings(); + if (pWorldSettings) + pWorldSettings->bEnableWorldBoundsChecks = false; +} + +} // namespace Cesium + +#endif // #if WITH_EDITOR diff --git a/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.h b/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.h new file mode 100644 index 000000000..a6f0ca8b4 --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/CesiumSceneGeneration.h @@ -0,0 +1,50 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#pragma once + +#if WITH_EDITOR + +#include + +class UWorld; +class ACesiumSunSky; +class ACesiumGeoreference; +class AGlobeAwareDefaultPawn; +class ACesium3DTileset; +class ACesiumCameraManager; + +namespace Cesium { + +struct SceneGenerationContext { + UWorld* world; + ACesiumSunSky* sunSky; + ACesiumGeoreference* georeference; + AGlobeAwareDefaultPawn* pawn; + std::vector tilesets; + + FVector startPosition; + FRotator startRotation; + float startFieldOfView; + + void setCommonProperties( + const FVector& origin, + const FVector& position, + const FRotator& rotation, + float fieldOfView); + + void refreshTilesets(); + void setSuspendUpdate(bool suspend); + bool areTilesetsDoneLoading(); + + void trackForPlay(); + void initForPlay(SceneGenerationContext& creationContext); + void syncWorldCamera(); + + static FString testIonToken; +}; + +void createCommonWorldObjects(SceneGenerationContext& context); + +}; // namespace Cesium + +#endif // #if WITH_EDITOR diff --git a/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h b/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h index fade23532..797c43852 100644 --- a/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h +++ b/Source/CesiumRuntime/Private/Tests/CesiumTestHelpers.h @@ -177,6 +177,8 @@ template T* findInPlay(T* pEditorObject) { return nullptr; return getComponentWithTag(pPlayOwner, getUniqueTag(pEditorObject)); } + + return nullptr; } /// diff --git a/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp b/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp index dde3e5d33..d83bcd84b 100644 --- a/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/GeoTransforms.spec.cpp @@ -10,7 +10,7 @@ using namespace CesiumUtility; BEGIN_DEFINE_SPEC( FGeoTransformsSpec, - "Cesium.GeoTransforms", + "Cesium.Unit.GeoTransforms", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) END_DEFINE_SPEC(FGeoTransformsSpec) diff --git a/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp b/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp index bd4fe9886..29382980b 100644 --- a/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/GlobeAwareDefaultPawn.spec.cpp @@ -14,7 +14,7 @@ BEGIN_DEFINE_SPEC( FGlobeAwareDefaultPawnSpec, - "Cesium.GlobeAwareDefaultPawn", + "Cesium.Unit.GlobeAwareDefaultPawn", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) diff --git a/Source/CesiumRuntime/Private/Tests/Google3dTilesLoadTest.cpp b/Source/CesiumRuntime/Private/Tests/Google3dTilesLoadTest.cpp new file mode 100644 index 000000000..c8be8b6e8 --- /dev/null +++ b/Source/CesiumRuntime/Private/Tests/Google3dTilesLoadTest.cpp @@ -0,0 +1,235 @@ +// Copyright 2020-2023 CesiumGS, Inc. and Contributors + +#if WITH_EDITOR + +#include "CesiumLoadTestCore.h" + +#include "Engine/World.h" +#include "Misc/AutomationTest.h" + +#include "Cesium3DTileset.h" +#include "CesiumSunSky.h" + +using namespace Cesium; + +namespace Cesium { +FString testGoogleUrl( + "https://tile.googleapis.com/v1/3dtiles/root.json?key=AIzaSyCaIL-JEK2Tw9MMBVKSTIu8dPkwfzfqAbU"); +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesPompidou, + "Cesium.Performance.GoogleTiles.Pompidou", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesChrysler, + "Cesium.Performance.GoogleTiles.Chrysler", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesGuggenheim, + "Cesium.Performance.GoogleTiles.Guggenheim", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesDeathValley, + "Cesium.Performance.GoogleTiles.DeathValley", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesTokyo, + "Cesium.Performance.GoogleTiles.Tokyo", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FGoogleTilesGoogleplex, + "Cesium.Performance.GoogleTiles.Googleplex", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::PerfFilter) + +#define TEST_SCREEN_WIDTH 1280 +#define TEST_SCREEN_HEIGHT 720 + +void googleWarmCacheSetup(SceneGenerationContext& context) { + context.refreshTilesets(); +} + +void setupForPompidou(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(2.352200, 48.860600, 200), + FVector(0, 0, 0), + FRotator(-20.0, -90.0, 0.0), + 60.0f); + + context.sunSky->TimeZone = 2.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel(TEXT("Center Pompidou, Paris, France")); + context.tilesets.push_back(tileset); +} + +void setupForChrysler(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-73.9752624659, 40.74697185903, 307.38), + FVector(0, 0, 0), + FRotator(-15.0, -90.0, 0.0), + 60.0f); + + context.sunSky->TimeZone = -4.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel(TEXT("Chrysler Building, NYC")); + context.tilesets.push_back(tileset); +} + +void setupForGuggenheim(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-2.937, 43.2685, 150), + FVector(0, 0, 0), + FRotator(-15.0, 0.0, 0.0), + 60.0f); + + context.sunSky->TimeZone = 2.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel(TEXT("Guggenheim Museum, Bilbao, Spain")); + context.tilesets.push_back(tileset); +} + +void setupForDeathValley(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-116.812278, 36.42, 300), + FVector(0, 0, 0), + FRotator(0, 0.0, 0.0), + 60.0f); + + context.sunSky->TimeZone = -7.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel( + TEXT("Zabriskie Point, Death Valley National Park, California")); + context.tilesets.push_back(tileset); +} + +void setupForTokyo(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(139.7563178458, 35.652798383944, 525.62), + FVector(0, 0, 0), + FRotator(-15, -150, 0.0), + 60.0f); + + context.sunSky->TimeZone = 9.0f; + context.sunSky->UpdateSun(); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel(TEXT("Tokyo Tower, Tokyo, Japan")); + context.tilesets.push_back(tileset); +} + +void setupForGoogleplex(SceneGenerationContext& context) { + context.setCommonProperties( + FVector(-122.083969, 37.424492, 142.859116), + FVector(0, 0, 0), + FRotator(-25, 95, 0), + 90.0f); + + ACesium3DTileset* tileset = context.world->SpawnActor(); + tileset->SetUrl(Cesium::testGoogleUrl); + tileset->SetTilesetSource(ETilesetSource::FromUrl); + tileset->SetActorLabel(TEXT("Google Photorealistic 3D Tiles")); + + context.tilesets.push_back(tileset); +} + +bool FGoogleTilesPompidou::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForPompidou, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +bool FGoogleTilesChrysler::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", googleWarmCacheSetup, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForChrysler, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +bool FGoogleTilesGuggenheim::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", googleWarmCacheSetup, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForGuggenheim, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +bool FGoogleTilesDeathValley::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", googleWarmCacheSetup, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForDeathValley, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +bool FGoogleTilesTokyo::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", googleWarmCacheSetup, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForTokyo, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +bool FGoogleTilesGoogleplex::RunTest(const FString& Parameters) { + std::vector testPasses; + testPasses.push_back(TestPass{"Cold Cache", nullptr, nullptr}); + testPasses.push_back(TestPass{"Warm Cache", googleWarmCacheSetup, nullptr}); + + return RunLoadTest( + GetBeautifiedTestName(), + setupForGoogleplex, + testPasses, + TEST_SCREEN_WIDTH, + TEST_SCREEN_HEIGHT); +} + +#endif diff --git a/Source/CesiumRuntime/Private/Tests/SubLevels.spec.cpp b/Source/CesiumRuntime/Private/Tests/SubLevels.spec.cpp index 5189c68d8..e644cb6ae 100644 --- a/Source/CesiumRuntime/Private/Tests/SubLevels.spec.cpp +++ b/Source/CesiumRuntime/Private/Tests/SubLevels.spec.cpp @@ -15,7 +15,7 @@ BEGIN_DEFINE_SPEC( FSubLevelsSpec, - "Cesium.SubLevels", + "Cesium.Unit.SubLevels", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index ee872edca..fccbb1afd 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -1211,7 +1211,6 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { uint32_t _lastTilesRendered; uint32_t _lastWorkerThreadTileLoadQueueLength; uint32_t _lastMainThreadTileLoadQueueLength; - bool _activeLoading; uint32_t _lastTilesVisited; uint32_t _lastCulledTilesVisited; diff --git a/Source/CesiumRuntime/Public/CesiumRuntime.h b/Source/CesiumRuntime/Public/CesiumRuntime.h index f383d15c7..5c4252b48 100644 --- a/Source/CesiumRuntime/Public/CesiumRuntime.h +++ b/Source/CesiumRuntime/Public/CesiumRuntime.h @@ -12,6 +12,7 @@ class UCesiumRasterOverlay; namespace CesiumAsync { class AsyncSystem; class IAssetAccessor; +class ICacheDatabase; } // namespace CesiumAsync DECLARE_LOG_CATEGORY_EXTERN(LogCesium, Log, All); @@ -48,3 +49,6 @@ CESIUMRUNTIME_API extern FCesiumRasterOverlayIonTroubleshooting CESIUMRUNTIME_API CesiumAsync::AsyncSystem& getAsyncSystem() noexcept; CESIUMRUNTIME_API const std::shared_ptr& getAssetAccessor(); + +CESIUMRUNTIME_API std::shared_ptr& +getCacheDatabase();