// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/CSG/CSGMesh.h" #include "Builder.Config.h" #if COMPILE_WITH_GI_BAKING #include "Engine/Graphics/RenderTask.h" #include "Engine/Core/Singleton.h" // Forward declarations #if COMPILE_WITH_ASSETS_IMPORTER namespace DirectX { class ScratchImage; }; #endif class Actor; class Terrain; class Foliage; class StaticModel; class GPUPipelineState; namespace ShadowsOfMordor { /// /// Shadows Of Mordor lightmaps builder utility. /// class Builder : public Singleton { public: enum class GeometryType { StaticModel, Terrain, Foliage, }; struct LightmapUVsChart { int32 Width; int32 Height; LightmapEntry Result; int32 EntryIndex; }; struct GeometryEntry { GeometryType Type; float Scale; BoundingBox Box; Rectangle UVsBox; union { struct { StaticModel* Actor; } AsStaticModel; struct { Terrain* Actor; int32 PatchIndex; int32 ChunkIndex; } AsTerrain; struct { Foliage* Actor; int32 InstanceIndex; int32 TypeIndex; int32 MeshIndex; } AsFoliage; }; int32 ChartIndex; }; typedef Array GeometryEntriesCollection; typedef Array LightmapUVsChartsCollection; enum BuildingStage { CleanLightmaps = 0, RenderCache, PostprocessCache, ClearLightmapData, RenderHemispheres, PostprocessLightmaps, }; /// /// Single hemisphere data /// struct HemisphereData { Float3 Position; Float3 Normal; int16 TexelX; int16 TexelY; }; /// /// Per lightmap cache data /// class LightmapBuildCache { public: Array Entries; Array Hemispheres; GPUBuffer* LightmapData = nullptr; #if HEMISPHERES_BAKE_STATE_SAVE // Restored data for the lightmap from the loaded state (copied to the LightmapData on first hemispheres render job) Array LightmapDataInit; #endif ~LightmapBuildCache(); bool Init(const LightmapSettings* settings); }; /// /// Per scene cache data /// class SceneBuildCache { public: // Meta Builder* Builder; int32 SceneIndex; // Data Scene* Scene; CriticalSection EntriesLocker; GeometryEntriesCollection Entries; LightmapUVsChartsCollection Charts; Array Lightmaps; GPUBuffer* TempLightmapData; // Stats int32 LightmapsCount; int32 HemispheresCount; int32 MergedHemispheresCount; public: /// /// Initializes a new instance of the class. /// SceneBuildCache(); public: /// /// Gets the lightmaps baking settings. /// /// Settings const LightmapSettings& GetSettings() const; public: /// /// Waits for lightmap textures being fully loaded before baking process. /// /// True if failed, otherwise false. bool WaitForLightmaps(); /// /// Updates the lightmaps textures data. /// void UpdateLightmaps(); /// /// Initializes this instance. /// /// The builder. /// The scene index. /// The scene. /// True if failed, otherwise false. bool Init(ShadowsOfMordor::Builder* builder, int32 index, ::Scene* scene); /// /// Releases this scene data cache. /// void Release(); public: // Importing lightmaps data BytesContainer ImportLightmapTextureData; int32 ImportLightmapIndex; int32 ImportLightmapTextureIndex; #if COMPILE_WITH_ASSETS_IMPORTER bool onImportLightmap(TextureData& image); #endif }; class BuilderRenderTask : public SceneRenderTask { void OnRender(GPUContext* context) override { Builder::Instance()->onJobRender(context); } }; private: friend class ShadowsOfMordorBuilderService; CriticalSection _locker; bool _wasBuildCalled; bool _isActive; volatile int64 _wasBuildCancelled; Array _scenes; volatile int64 _wasJobDone; BuildingStage _stage; bool _wasStageDone; int32 _workerActiveSceneIndex; int32 _workerStagePosition0; int32 _workerStagePosition1; int32 _giBounceRunningIndex; uint64 _lastJobFrame; float _hemisphereTexelsTotalWeight; int32 _bounceCount; int32 _hemispheresPerJob; DateTime _hemispheresPerJobUpdateTime; #if HEMISPHERES_BAKE_STATE_SAVE DateTime _lastStateSaveTime; bool _firstStateSave; #endif BuildProgressStep _lastStep; DateTime _lastStepStart; BuilderRenderTask* _task = nullptr; GPUTexture* _output = nullptr; AssetReference _shader; GPUPipelineState* _psRenderCacheModel = nullptr; GPUPipelineState* _psRenderCacheTerrain = nullptr; GPUPipelineState* _psBlurCache = nullptr; GPUBuffer* _irradianceReduction = nullptr; GPUTexture* _cachePositions = nullptr; GPUTexture* _cacheNormals = nullptr; public: Builder(); public: /// /// Called on building start /// Action OnBuildStarted; /// /// Called on building progress made /// Arguments: current step, current step progress, total progress /// Delegate OnBuildProgress; /// /// Called on building finish (argument: true if build failed, otherwise false) /// Delegate OnBuildFinished; public: void CheckIfRestoreState(); bool RestoreState(); /// /// Starts building lightmap. /// void Build(); /// /// Returns true if build is running. /// bool IsActive() const { return _isActive; } /// /// Sends cancel current build signal. /// void CancelBuild(); void Dispose(); private: void saveState(); bool loadState(); void deleteState(); void reportProgress(BuildProgressStep step, float stepProgress); void reportProgress(BuildProgressStep step, float stepProgress, int32 subSteps); void reportProgress(BuildProgressStep step, float stepProgress, float totalProgress); void onJobRender(GPUContext* context); bool checkBuildCancelled(); bool runStage(BuildingStage stage, bool resetPosition = true); bool initResources(); void releaseResources(); bool waitForJobDataSync(); static bool sortCharts(const LightmapUVsChart& a, const LightmapUVsChart& b); bool doWorkInner(DateTime buildStart); int32 doWork(); void cacheEntries(); void generateCharts(); void packCharts(); void updateLightmaps(); void updateEntries(); void generateHemispheres(); #if DEBUG_EXPORT_LIGHTMAPS_PREVIEW static void exportLightmapPreview(SceneBuildCache* scene, int32 lightmapIndex); #endif #if DEBUG_EXPORT_CACHE_PREVIEW void exportCachePreview(SceneBuildCache* scene, GenerateHemispheresData& cacheData, LightmapBuildCache& lightmapEntry) const; #endif #if DEBUG_EXPORT_HEMISPHERES_PREVIEW void addDebugHemisphere(GPUContext* context, GPUTextureView* radianceMap); void downloadDebugHemisphereAtlases(SceneBuildCache* scene); void releaseDebugHemisphereAtlases(); #endif }; }; #endif