You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ============================ MAJOR FEATURES & CHANGES ============================ Change 3441680 by Uriel.Doyon Added units to point light intensity, to allow the user to specify the value in candelas or lumens. New point light actors now configure the intensity in candelas by default. Replaced viewport exposure settings by an EV100 slider. Hidding the tone mapper in the show flag now still applies the exposure. Added a new AutoExposure method called EV100 which allows to specify : - MinEV100, MaxEV100 - Calibration Constnat - Exposure Compensation #jira UE-42783 Change 3454934 by Chris.Bunner Backing out changelists 3441680, 3454636 and 3454844 for the sake of integration stability. Change 3629223 by Rolando.Caloca DR - Rollback //UE4/Dev-Rendering/Engine/Source/Runtime/VulkanRHI to changelist 3627847 Change 3629708 by Rolando.Caloca DR - vk - Redo some changes from DevMobile 3601439 3604186 3606672 3617383 3617474 3617483 Change 3636145 by Chris.Bunner Linux compile fix. Change 3636198 by Chris.Bunner Include fix. Change 3636225 by Daniel.Wright Removed spammy draw event Change 3636397 by Daniel.Wright Visualize volumetric lightmaps uses 18% grey in lit mode Change 3636398 by Daniel.Wright Translucency lighting modes work with Volumetric Lightmaps * The old Indirect Lighting Cache always interpolated one sample per object, while Volumetric lightmaps operate at the frequency of the Translucency Lighting Mode (per-vertex or per-pixel) so the lighting accuracy is much higher. * The old ILC always applied the single lighting sample with a per-pixel normal, even in per-vertex lighting modes * Volumetric PerVertex NonDirectional (cheapest lit translucency) went from 74 instructions down to 42 * Volumetric Directional went from 104 up to 122 Change 3636604 by Chris.Bunner Added a material translation error on identically named/associated but differently valued parameters as this will cause one of them to be lost during parameter evaluation, the result being "random". Change 3637668 by Rolando.Caloca DR - hlslcc - Skip preprocessor when not required which reduces peak mem consumption by ~500 MB - Fix missing indexing on intrinsic return type Change 3638541 by Chris.Bunner Fixed editor materials - Skip hardcoded parameter names that we rely on being overridden when checking for non-matching duplicate parameters. Change 3638798 by Mark.Satterthwaite Rebuilt hlslcc for Mac for Rolando's 3637668 changes. Change 3638861 by Mark.Satterthwaite Missed making a log verbose in the Metal ring-buffer. Change 3639482 by Rolando.Caloca DR - vk - Minor fixes Change 3639909 by Michael.Lentine Add special case for struct needed to compile. Workaround issues in parser such that (x)[0] is replaced with x[0] and (x).a is replaced with x.a. Change 3639916 by Michael.Lentine Spelling fix. Change 3640053 by Mark.Satterthwaite Fix Desktop Forward rendering on Nvidia Metal rendering on macOS by modifying the shader and runtime to treat ForwardLocalLightBuffer data as uint rather than float and invert the use of as*() casts. Tthis is *necessary* to avoid a bug/limitation of Nvidia's current Metal shader compiler that flushes all denorm values to zero on load from any resource. AMD & iOS Metal only flush-to-zero when sampling from textures and that's what Apple regard as the expected behaviour. I have however asked them to standardise on the D3D behaviour of preserving denorms on all load, move (incl. min/max/as*()-casts) & store operations. This won't happen in the current or imminent OS/Xcode releases. Now only Intel Metal is broken and their problems run deeper. #jira UE-48881 Change 3640983 by Olaf.Piesche Cache the depth buffer collision shader for GPU particles even when simple forward is enabled, as that can be turned off at runtime. #jira UE-48799 Change 3641480 by Michael.Lentine Add min16float to FP16Math Change 3642442 by Mark.Satterthwaite Fix the native shader libraries again & undo the increase in cook time from changes to the way FShaderCodeLibrary was compiling & deduplicating the shaders. - Remvoe the single linear array for accumulated shader code: reallocating this is tremendously expensive and will double the time taken to iteratively cook large projects. - Uncompress the shader data for the native library system so that it actually works again. - Fix some errant change to the Metal compiler that was trying to wedge the fully compiled library into the single Metal library which is 100% bogus. #jira UE-49192 Change3642919by Chris.Bunner Reverted unintended changes to material static parameter set serialization. Bumped material version to force re-serialization. Fixed a few typos. Change 3642923 by Richard.Wallis Fix for "Pixel Inspector On" message not disappearing when closing pixel inspector window while on. Handle tab closed events to cancel the pixel inspector if window closed while running. #jira UE-46504 Change 3643296 by Michael.Lentine Convert all structures that aren't use globally to halfs. Change 3643381 by Ryan.Brucks New Plugin allowing Blueprint Texture and RT reads as well as MIC creation and modification. Change 3643929 by Ben.Salem Added better precision on thread values in Perf Monitor. Also added global thread values to go with per-world ones to help track down the most accurate numbers possible. #tests Ran locally, changes validated by benj Change 3644203 by Mark.Satterthwaite Refactor mtlpp a little bit to make the compiler do more of the work & update for all the latest publicly exposed APIs. Change 3644336 by Mark.Satterthwaite Ref-count the mtlpp Device object so that it can be the repository for IMP caching - now just have to go through the types created from a specific device and have them keep a reference.... Change 3644431 by Uriel.Doyon Added a intensity units property, for point lights and spot lights, that can be set to Candelas, Lumens or Unitless (legacy). The default units value for newly placed point lights and spot lights is configured in the project settings. Spot lights configured in lumens have their whole luminous energy redirected toward the cone. This means that changing the outer cone angle, changes the spot light brightness. New exposure menu that uses an EV100 slider. New post process settings "camera" tab regrouping : ShutterSpeed, ISO, Aperture and ExposureCompensation. Post process settings "auto exposure" tab renamed "exposure". The calibration constant for "Auto Exposure Basic" is now configurable in the advanced tab. The auto exposure method is renamed "Metering Mode" New pre-exposure feature that allows the engine to apply the previous frame exposure before writing to the scene color. This allows the engine to render the scene with similar range than the final color (after exposure), and avoid arithmetic overflow for low precision RT formats. The amount of exposure applied directly is called pre-exposure, and is compensated by a smaller post-exposure value. Change 3645098 by Marcus.Wassmer Don't add meshes twice when in simpleforward Change 3645551 by Daniel.Wright VolumetricScatteringIntensity is greyed out based on Mobility Change 3645707 by Chris.Bunner Skip empty parameters when identifying invalid duplicates during material translation. Change 3646225 by Uriel.Doyon Texture streaming support for particle sub-uv Change 3646323 by Rolando.Caloca DR - vk - Fix bad update texture 2/3d parameters Change 3646463 by Mark.Satterthwaite Metal shader files that can serve as extensions to the metal_stdlib rather than trying to wedge even more into the shader compiler. Might as well leverage Metal's C++'ness. Currently just a skeleton of a buffer type that would better emulate HLSL Buffer<>/RWBuffer<> objects. Change 3646727 by Marcus.Wassmer fix linux and nonunity compiles Change 3647777 by Rolando.Caloca DR - Mobile Vulkan optimization Change 3647822 by Lauren.Ridge Layers are now renameable (except Layer 0 is always "Background") in Material Layers Functions Change 3647918 by Chris.Bunner Static analysis fixes - Monolithic include, forward delcared enums, locally shadowed variable. Change 3648010 by Michael.Lentine Don't use min16float by default. Change 3648015 by Michael.Lentine Add special case fixes for min16float as well as half on console. Change 3648024 by Lauren.Ridge Moving MaterialLayersFunctions Layer naming to EditorOnly wrappers Change 3648127 by Lauren.Ridge Moving name getter functionality to FMaterialLayersFunctions Change 3648265 by Lauren.Ridge Fixing loctext key Change 3648293 by Rolando.Caloca DR - D3D12 fix Change 3648326 by Rolando.Caloca DR - vk - Added subrectangle support when updating Texture2D and Texture3D Change 3648522 by Rolando.Caloca DR - vk - Do not try to create a BufferView of a StructuredBuffer (no valid pixelformat) Change 3648612 by Rolando.Caloca DR - vk - Implement RHIMapStagingSurface Change 3648673 by Rolando.Caloca DR - vk - Reduce Vulkan pipeline cache disk size by filtering duplicated shader ucodes Change 3648913 by Arne.Schober DR - Performance optimization during HLod traversal. using vector load and avoiding array copy by passing it through reference instead through value. Change 3649443 by Daniel.Wright Exposed EmissiveBoost, since Lightmass supports Emissive areas on meshes Change 3650436 by Mark.Satterthwaite Implemented the necessary extensions for Metal shading language to move lots of complexity out of MetalBackend and into actual shader code to make life *vastly* simpler. - Full ue4::typed_buffer<T> wrapper type & associated ue4::buffer<>/ue4::buffer_atomic<> API - totally untested but should be functionally equivalent to HLSL Buffer<T>/RWBuffer<T>. - All the sensible casts I can think of are now defined in ue4_format - any additional ones can just be added. The enurm of formats needs to be exposed to MetalRHI so we upload the correct values, but this should be trivial. - Added a full series of wrappers around texturecube_array and depthcube_array to insulate code from whether it is backed by a real cube_array or a texture2d_array so we don't have to maintain complicated variants for new/old iOS devices. - Added implementations for a bunch of annoying HLSL & GLSL intrinsics that were being matched by name + reverse_bits which needs a custom implementation on old shader standards. Change 3650861 by Rolando.Caloca DR - vk - Fix warnings Change 3651116 by Rolando.Caloca DR - vk - Support for compressed saved PSO cache Change 3651321 by Rolando.Caloca DR - vk - Prep for load multiple PSO files Change 3651337 by Chris.Bunner Editor-only default material fallback (hardcoded material). #jira UE-48404 Change 3651839 by Rolando.Caloca DR - vk - Integrate minor pipeline changes Change 3652042 by Mark.Satterthwaite More work on mtlpp: - Selector class that caches IMP from SEL & Class. - Fixes to ns::Error. - Added test case application for testing denorm & float reinterpret-cast behaviour on Metal. Change 3652370 by Uriel.Doyon New "stat StreamingOverview" giving high level metrics of texture usage. New function ResetAverageRequiredTexturePoolSize() and GetAverageRequiredTexturePoolSize() giving the average ideal value for "r.streaming.poolsize". Change 3653658 by Chris.Bunner Material vertex interpolator for sprite and gpu sprite particles. Change 3653676 by Rolando.Caloca DR - vk - Integrate changes: Multiple PSO caches, shared ucode & compression, size reduction up to 80% Change 3653940 by Daniel.Wright Moved Volumetric Lightmap textures out of FScene and into FPrecomputedVolumetricLightmapData so their lifetime can match the MapBuildData. This allows tossing the source BulkData in game after RHI texture creation even though switching lighting scenarios does Release/InitializeRenderingResources multiple times. Change 3653956 by Daniel.Wright Fixed leak of BatchVisibilityId's Change 3653991 by Daniel.Wright Fixed missing include Change 3654013 by Daniel.Wright Refactored reflection capture composite SM4 handling, now forces fully rough even if !REFLECTION_COMPOSITE_USE_BLENDED_REFLECTION_CAPTURES (forward shading default) Change 3654018 by Daniel.Wright Remove unused SM4 reflection capture cubemap Change 3654118 by Rolando.Caloca DR - vk - Fix for queries; support for r.Vulkan.ProfileCmdBuffers to only time cmd buffers w/o gpu bubbles Change 3654339 by Chris.Bunner Temporarily disabed a material error whilst working with content teams to fix the introduced bugs. Change 3654534 by Daniel.Wright Editor is only supported on Feature Level 5 platforms. Added a message box and exit when SM4 is detected (d3d10, OpenGL 3). Change 3654751 by Rolando.Caloca DR - vk - Add readback for RGB10A2; minor optimization Change 3654940 by Rolando.Caloca DR - vk - Warning fix Change 3655104 by Mark.Satterthwaite Add a bunch of code to mtlpp to wrap the Xcode command-line Metal tools and use them to provide a convenient command-line tool that can compile two Metal shaders & diff the resulting AIR to make debugging easier. Obviously this only works on macOS. Change 3655173 by Jian.Ru Render dithered material as masked if a stencil prepass is not used #jira UE-50064, UE-49537 Change 3655479 by Daniel.Wright Fixed HandleLegacyMapBuildData not getting called on P maps loaded in the editor, which apparently was dropped in an integration error, causing all legacy maps (before the BuildData change) to lose their built lighting. Change 3656341 by Richard.Wallis Metal validation checks to test for drawing of the end of a vertex stream as seen in UE-48172 (Landscape Mesh Flickers Rapidly When Using Sculpting Landscape). This is not a fix for that but just a error log to catch the bad draw calls. #jira UE-48172 Change 3656844 by Rolando.Caloca DR - vk - Avoid microcode copy - Fix link error Change 3656894 by Rolando.Caloca DR - vk - Enable api dump without needing validation enabled Change 3656915 by Marcus.Wassmer Fix DX12 buffer lock for read Change 3657166 by Rolando.Caloca DR - vk - Proper fix for api dump layer Change 3657401 by Rolando.Caloca DR - vk - Linux compile fix Change 3657607 by Rolando.Caloca DR - vk - Prep for changes Change 3658722 by Chris.Bunner Refresh shared texture samplers when changing max ansiotropy level (e.g. scalability settings). #jira UE-30086 Change 3659499 by Daniel.Wright Moved bEnableAutoLODGeneration to LOD category so it's not the only one in HLOD category Change 3659644 by Mark.Satterthwaite D3D11 equivalent to 3656341 (only enabled when -d3ddebug is) to validate DrawIndexedPrimitives isn't trying to draw off the end of the instanced vertex streams which has inconsistent (and potentially undefined) behaviour across the various APIs we support. This fires when painting the landscape because the code attempts to render with a FirstInstance value that is as large as the number of elements in the instance-data streams which D3D11 drivers silently fails to render, but which Metal (& probably Vulkan too) will renders as garbage. This pattern is wrong & will be even more dangerous in a Draw*Indirect world. #jira UE-48172 Change 3659831 by Rolando.Caloca DR - vk - Copy 3657927 (fixes for Mali) Change 3661921 by Rolando.Caloca DR - vk - Minor log/info changes Change 3661985 by Mark.Satterthwaite Change the Metal sampler filter translation to better match D3D. Change 3662050 by Richard.Wallis Compile fix for Metal enums. Change 3662062 by Rolando.Caloca DR - Copy from 3662060 - OpenGL cloth fix Change 3662100 by Mark.Satterthwaite Use a temporary file and an atomic move to put the Metal PCH into the right place and reduce the number of false PCH compile failures on the build farm. Change 3662253 by Daniel.Wright Reflection Captures support Lighting Scenarios without recapturing * Reflection Captures are now part of the Map Build * Modifying a capture in editor will display a preview, but game can only display built captures (black for unbuilt with screen message) * Reflection Capture build data moved to the BuildData package * Building lighting / reflection captures no longer dirties ULevels * Sky lights which capture the scene now work correctly with Lighting Scenarios * Lighting Scenarios must now be loaded for each time they are made visible (no switching back and forth while keeping both loaded) Change 3663215 by Mark.Satterthwaite Initial, incomplete, tool added to mtlpp to help debug macOS Internal Compiler Errors - the mtlpp command-line tool can be fed the debug output from UE4 along with compiler settings to automatically compile shaders and construct the render pipeline that crashes the runtime compiler. So far only macOS render-pipeline-states are supported currently. Change 3663293 by Mark.Satterthwaite Added Metal device selection to the mtlpp command-line tool so I can quickly test compile on dual-GPU Macbook Pro's. Change 3663471 by Daniel.Wright Reflection Capture Builds no longer use UEditorLevelUtils::SetLevelVisibility to control level visibility, which streams out sublevels, modifying nav mesh Fixed status updates during Reflection Capture Build Captures overflowing GMaxNumReflectionCaptures now log a warning instead of pretending that they built successfully Change3664056by Rolando.Caloca DR - Linux compile fix Change 3664460 by Daniel.Wright Restored unused LoadTimesObjectVersion, packages saved with it will issue a warning on load Change 3664802 by Uriel.Doyon Fixed flash created by pre-exposure when the value changed dramatically between frames Change 3664890 by Daniel.Wright Created 'Stat MapBuildData' to track the memory size of lighting and reflection capture build outputs Change 3665163 by Rolando.Caloca DR - Copy from 3665156 - Gracefully fail when there are mem leaks exiting Vulkan Change 3665629 by Daniel.Wright Only Surface domain materials cast shadows in Lightmass Change 3665855 by Marcus.Wassmer PR #4032: Fix comparison of SceneColorFormat (Contributed by Hybrid0) Change 3666707 by Guillaume.Abadie Replaces some custom material node to get View.BufferSizeAndInvSize with ViewProperty material expression in some engine material functions. Change 3667239 by Rolando.Caloca DR - Use hlslcc define for common issues Change 3668108 by Brian.Karis Disabled to Catmull-Rom filter. Too many flickering issues. Disabled antiflicker as well. Change 3668157 by Mark.Satterthwaite In the prototype Metal stdlib extension library add inline versions of the D3D SM6 "wave" intrinsics that can be expressed in terms of Metal 2.0 simd/quad group operations (macOS=simd, iOS=quad). These are unlikely to be as efficient as direct intrinsics but they should be functionally equivalent. These functions are not available *yet* as I still need to hook the ue4_stdlib into MetalShaderFormat & MetalRHI. The following HLSL 6 functions are implemented: WaveAllBitAnd WaveAllMax WaveAllMin WaveAllBitOr WaveAllBitXor WaveAllEqual WaveAllProduct WaveAllSum WaveAllTrue WaveAnyTrue WaveBallot WaveGetLaneCount WaveGetLaneIndex WaveOnce WavePrefixProduct WavePrefixSum WaveReadFirstLane WaveReadLaneAt The following can't be implemented in Metal as of Metal 2.0 AFAIK: WaveGetOrderedIndex WaveIsHelperLane GlobalOrderedCountIncrement QuadReadLaneAt QuadSwapX QuadSwapY Change 3668260 by Olaf.Piesche Cache particle collision shaders regardless of simple forward state Missed this checkin #jira FORT-51307 Change 3669243 by Daniel.Wright Bumped shader version to propagate FReflectionCaptureData rename Change 3669369 by Mark.Satterthwaite Duplicating Metal changes from //UE4/Release-4.18 to Dev-Rendering (//UE4/Dev-Rendering) 3662503 Collapse system-variables to one declaration in MetalUtils to avoid later shader compiler errors when they are specified more than once, which is seemingly permissable. Fixes volumetric fog. #jira UE-50293 3665210 - Invalidate all Metal shaders again to force a recompile to workaround another driver bug. - On macOS compact the clip-distance value into a single output to avoid bugs in the runtime pipeline compilers. - In SCW's direct-compile mode MetalShaderForamt should always dump the resulting Metal shader and print any errors we encounter to the log. - Change FGenerateMetalVisitor to take a FMetalCodeBackend& not a FMetalCodeBackend* to avoid a lot of pointless pointer validation. #jira UE-50244 3665429 Fix a crash on shutdown due to MetalRHI caching vertex-declarations beyond the lifetime of the RHI by moving the cache into the FMetalDynamicRHI itself. #jira UE-50356 3665613 Fix DistanceField rendering on Metal & the associated validation layer error when it is enabled - MetalRHI can't use the same approach as Vulkan without a bit more work. #jira UE-50364 3667584 Fix black flickering on some materials that use World Position Offset - the Metal sincos intrinsic comes into two flavours for single precision floats and we want the precise version not the fast version. The "cross" implementation needed a few more fma's too and this change has to invalidate Metal shaders again to take effect. #jira UE-50399 3667805 Changing sincos in Metal fragment shaders had undesirable side-effects - the compiler is now re-associating another floating point operation - so limit the use of precise::sincos to vertex-shaders for now. This fixes the WPO materials without causing any other obvious problems. #jira UE-50399 Change 3669912 by Mark.Satterthwaite Fix Metal compilation for PCSS shadows - the HLSL that is compiled uses both SampleCmp and Sample which GLSL disallows (shadowSampler types in GLSL only allow SampleCmp) but is perfectly valid in Metal where only the texture type declaration changes. Duplicate MetalBackend.cpp changes from Joe.Graf's: 3667781 Fixed an extra ) being emitted during HLSL->Metal translation Added depthcube_array support per Mark's instructions Change 3670308 by Mark.Satterthwaite Missing autorelease pool blocks in MetalTexture functions. Change 3670989 by Mark.Satterthwaite Stop trying to be so clever with Metal clip-distances: AMD have a bug in 10.13.0 that means we can only emit one clip-distance value, so simply emit the value with the lowest index (we have handily ordered them by importance!) and let the others become user-interpolators until this bug is resolved in a macOS SU (according to Max@AMD the fix is in, just not in time for 4.18). This means planar reflections will work, VR no-multi-view-fallback will work & layered rendering will work either individually or together - the difference being that on macOS *only* the VR no-multi-view-fallback & layered rendering paths may be slower when combined with the global clip plane. Hit & run fix to MetalCaptureManager and availability of tile-shader functions while I'm here. #jira UE-50518 Change 3671014 by Mark.Satterthwaite Correct handling of RowLinearPVRTC blits for iOS Metal desired for Ocean. Change 3671575 by Rolando.Caloca DR - Copy 3668036 Stop syncing CPU with GPU on Vulkan Change 3671637 by Rolando.Caloca DR - Copy3670937Fixes Vulkan editor outline Change 3672309 by Mark.Satterthwaite Submitted on behalf of Richard Wallis: Only disable V-Sync on Metal in macOS 10.13 when running in "true" fullscreen mode (where we switch display modes & forbid switching spaces or tabbing out) so that you won't see the rainbow artefact caused by being out-of-sync with WindowServer, only the tearing you'd expect when V-Sync is disabled. We'll chat with Apple about whether there is a way to avoid the rainbow artefact when switching spaces in Windowed Fullscreen with displaySync disabled. #jira UE-50134 Change 3672314 by Daniel.Wright User friendly message dialog for when a required key is missing from BaseLightmass.ini Change 3672315 by Daniel.Wright Assert on load when a uniform buffer struct goes missing, instead of a crash on save Change 3672476 by Chris.Bunner Removed duplicate material instance editor command binding that appeared in a task stream merge. Change 3672626 by Mark.Satterthwaite Move the ue4_stdlib.metal extensions to MetalRHI's Public header directory as it'll need to be available to both modules and that's easier to accomplish from here. Change 3672643 by Mark.Satterthwaite iOS compilation fixes. Change 3672728 by Daniel.Wright Fixed encoded HDR reflection captures Change 3672753 by Jian.Ru Fix texture swimming #jira UE-49369 Change 3672815 by Daniel.Wright Tooltip for build button explaining why it might be disabled Change 3673350 by Rolando.Caloca DR - vk - Do not reallocate memory every draw call Change 3673501 by Rolando.Caloca DR - vk - Remove more reallocations Change 3673505 by Rolando.Caloca DR - Remove global variable with semantic Change 3673514 by Rolando.Caloca DR - vk - compile fix Change 3675899 by Chris.Bunner Fixed support for editor-time transient parameter overrides. This happens when a compiled material's scalar or vector parameter is changed in value only, the active material resources should also update and be reverted when the material graph is closed. The code was incorrectly pulling base parameters from expressions instead of the actual uniform. Change 3676843 by Arne.Schober DR - UE-49473 - Fix Stateleak caused by custom drawer in the long for loop, where the depth stencil state might not be reset in a subsequent itteration of the loop. Change 3678269 by Daniel.Wright Fixed Encoded HDR reflection capture data getting the wrong Brightness applied when cooking Change 3678543 by Daniel.Wright MapBuildData now tosses the unneeded reflection capture format on load. Affects target platforms that require multiple formats at cook time. Change 3679602 by Jian.Ru Fix up mesh decal shader complexity view mode #jira UE-50272 Change 3679959 by Chris.Bunner Fixed logic on overriden vector parameter retrieval for material instances checking a function owned parameter. #jira UE-50712 Change 3679998 by Daniel.Wright Fixed crash when precomputing static visibility only [CL3680175by Marcus Wassmer in Main branch]
2347 lines
85 KiB
C++
2347 lines
85 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
StaticLightingSystem.cpp: Bsp light mesh illumination builder code
|
|
=============================================================================*/
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/Guid.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/GarbageCollection.h"
|
|
#include "Layout/Visibility.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Components/LightComponentBase.h"
|
|
#include "Components/ReflectionCaptureComponent.h"
|
|
#include "AI/Navigation/NavigationSystem.h"
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "Components/LightComponent.h"
|
|
#include "Model.h"
|
|
#include "Engine/Brush.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Settings/EditorExperimentalSettings.h"
|
|
#include "Settings/LevelEditorMiscSettings.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Engine/GeneratedMeshAreaLight.h"
|
|
#include "Components/SkyLightComponent.h"
|
|
#include "Components/ModelComponent.h"
|
|
#include "Engine/LightMapTexture2D.h"
|
|
#include "Editor.h"
|
|
#include "Engine/Selection.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorModes.h"
|
|
#include "Dialogs/Dialogs.h"
|
|
|
|
FSwarmDebugOptions GSwarmDebugOptions;
|
|
|
|
#include "Lightmass/LightmassCharacterIndirectDetailVolume.h"
|
|
#include "StaticLighting.h"
|
|
#include "StaticLightingSystem/StaticLightingPrivate.h"
|
|
#include "ModelLight.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "LevelUtils.h"
|
|
#include "EngineModule.h"
|
|
#include "LightMap.h"
|
|
#include "ShadowMap.h"
|
|
#include "EditorBuildUtils.h"
|
|
#include "ComponentRecreateRenderStateContext.h"
|
|
#include "Engine/LODActor.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogStaticLightingSystem);
|
|
|
|
#include "EngineGlobals.h"
|
|
#include "Toolkits/AssetEditorManager.h"
|
|
|
|
#include "Lightmass/LightmassImportanceVolume.h"
|
|
#include "Components/LightmassPortalComponent.h"
|
|
#include "Lightmass/Lightmass.h"
|
|
#include "StatsViewerModule.h"
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Misc/UObjectToken.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "StaticLightingSystem"
|
|
|
|
/** The number of hardware threads to not use for building static lighting. */
|
|
#define NUM_STATIC_LIGHTING_UNUSED_THREADS 0
|
|
|
|
bool GbLogAddingMappings = false;
|
|
|
|
/** Counts the number of lightmap textures generated each lighting build. */
|
|
extern ENGINE_API int32 GLightmapCounter;
|
|
/** Whether to compress lightmaps. Reloaded from ini each lighting build. */
|
|
extern ENGINE_API bool GCompressLightmaps;
|
|
|
|
/** Whether to allow lighting builds to generate streaming lightmaps. */
|
|
extern ENGINE_API bool GAllowStreamingLightmaps;
|
|
|
|
// NOTE: We're only counting the top-level mip-map for the following variables.
|
|
/** Total number of texels allocated for all lightmap textures. */
|
|
extern ENGINE_API uint64 GNumLightmapTotalTexels;
|
|
/** Total number of texels used if the texture was non-power-of-two. */
|
|
extern ENGINE_API uint64 GNumLightmapTotalTexelsNonPow2;
|
|
/** Number of lightmap textures generated. */
|
|
extern ENGINE_API int32 GNumLightmapTextures;
|
|
/** Total number of mapped texels. */
|
|
extern ENGINE_API uint64 GNumLightmapMappedTexels;
|
|
/** Total number of unmapped texels. */
|
|
extern ENGINE_API uint64 GNumLightmapUnmappedTexels;
|
|
/** Whether to allow cropping of unmapped borders in lightmaps and shadowmaps. Controlled by BaseEngine.ini setting. */
|
|
extern ENGINE_API bool GAllowLightmapCropping;
|
|
/** Total lightmap texture memory size (in bytes), including GLightmapTotalStreamingSize. */
|
|
extern ENGINE_API uint64 GLightmapTotalSize;
|
|
/** Total memory size for streaming lightmaps (in bytes). */
|
|
extern ENGINE_API uint64 GLightmapTotalStreamingSize;
|
|
/** Largest boundingsphere radius to use when packing lightmaps into a texture atlas. */
|
|
extern ENGINE_API float GMaxLightmapRadius;
|
|
|
|
/** Total number of texels allocated for all shadowmap textures. */
|
|
extern ENGINE_API uint64 GNumShadowmapTotalTexels;
|
|
/** Number of shadowmap textures generated. */
|
|
extern ENGINE_API int32 GNumShadowmapTextures;
|
|
/** Total number of mapped texels. */
|
|
extern ENGINE_API uint64 GNumShadowmapMappedTexels;
|
|
/** Total number of unmapped texels. */
|
|
extern ENGINE_API uint64 GNumShadowmapUnmappedTexels;
|
|
/** Total shadowmap texture memory size (in bytes), including GShadowmapTotalStreamingSize. */
|
|
extern ENGINE_API uint64 GShadowmapTotalSize;
|
|
/** Total texture memory size for streaming shadowmaps. */
|
|
extern ENGINE_API uint64 GShadowmapTotalStreamingSize;
|
|
|
|
/** If non-zero, purge old lightmap data when rebuilding lighting. */
|
|
int32 GPurgeOldLightmaps=1;
|
|
static FAutoConsoleVariableRef CVarPurgeOldLightmaps(
|
|
TEXT("PurgeOldLightmaps"),
|
|
GPurgeOldLightmaps,
|
|
TEXT("If non-zero, purge old lightmap data when rebuilding lighting.")
|
|
);
|
|
|
|
|
|
int32 GMultithreadedLightmapEncode = 1;
|
|
static FAutoConsoleVariableRef CVarMultithreadedLightmapEncode(TEXT("r.MultithreadedLightmapEncode"), GMultithreadedLightmapEncode, TEXT("Lightmap encoding after rebuild lightmaps is done multithreaded."));
|
|
int32 GMultithreadedShadowmapEncode = 1;
|
|
static FAutoConsoleVariableRef CVarMultithreadedShadowmapEncode(TEXT("r.MultithreadedShadowmapEncode"), GMultithreadedShadowmapEncode, TEXT("Shadowmap encoding after rebuild lightmaps is done multithreaded."));
|
|
|
|
|
|
|
|
|
|
TSharedPtr<FStaticLightingManager> FStaticLightingManager::StaticLightingManager;
|
|
|
|
TSharedPtr<FStaticLightingManager> FStaticLightingManager::Get()
|
|
{
|
|
if (!StaticLightingManager.IsValid())
|
|
{
|
|
StaticLightingManager = MakeShareable(new FStaticLightingManager);
|
|
}
|
|
return StaticLightingManager;
|
|
}
|
|
|
|
void FStaticLightingManager::ProcessLightingData()
|
|
{
|
|
auto StaticLightingSystem = FStaticLightingManager::Get()->ActiveStaticLightingSystem;
|
|
|
|
check(StaticLightingSystem);
|
|
|
|
FNavigationLockContext NavUpdateLock(StaticLightingSystem->GetWorld(), ENavigationLockReason::LightingUpdate);
|
|
|
|
bool bSuccessful = StaticLightingSystem->FinishLightmassProcess();
|
|
|
|
FEditorDelegates::OnLightingBuildKept.Broadcast();
|
|
|
|
if (!bSuccessful)
|
|
{
|
|
FStaticLightingManager::Get()->FailLightingBuild();
|
|
}
|
|
|
|
FStaticLightingManager::Get()->ClearCurrentNotification();
|
|
}
|
|
|
|
void FStaticLightingManager::CancelLightingBuild()
|
|
{
|
|
if (FStaticLightingManager::Get()->ActiveStaticLightingSystem->IsAsyncBuilding())
|
|
{
|
|
GEditor->SetMapBuildCancelled( true );
|
|
FStaticLightingManager::Get()->ClearCurrentNotification();
|
|
FEditorDelegates::OnLightingBuildFailed.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingManager::Get()->FailLightingBuild();
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::SendProgressNotification()
|
|
{
|
|
// Start the lightmass 'progress' notification
|
|
FNotificationInfo Info( LOCTEXT("LightBuildMessage", "Building lighting") );
|
|
Info.bFireAndForget = false;
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("LightBuildCancel","Cancel"),
|
|
LOCTEXT("LightBuildCancelToolTip","Cancels the lighting build in progress."),
|
|
FSimpleDelegate::CreateStatic(&FStaticLightingManager::CancelLightingBuild)));
|
|
|
|
LightBuildNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (LightBuildNotification.IsValid())
|
|
{
|
|
LightBuildNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::ClearCurrentNotification()
|
|
{
|
|
if ( LightBuildNotification.IsValid() )
|
|
{
|
|
LightBuildNotification.Pin()->SetCompletionState(SNotificationItem::CS_None);
|
|
LightBuildNotification.Pin()->ExpireAndFadeout();
|
|
LightBuildNotification.Reset();
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::SetNotificationText( FText Text )
|
|
{
|
|
if ( LightBuildNotification.IsValid() )
|
|
{
|
|
LightBuildNotification.Pin()->SetText( Text );
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::ImportRequested()
|
|
{
|
|
if (FStaticLightingManager::Get()->ActiveStaticLightingSystem)
|
|
{
|
|
FStaticLightingManager::Get()->ActiveStaticLightingSystem->CurrentBuildStage = FStaticLightingSystem::ImportRequested;
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::DiscardRequested()
|
|
{
|
|
if (FStaticLightingManager::Get()->ActiveStaticLightingSystem)
|
|
{
|
|
FStaticLightingManager::Get()->ClearCurrentNotification();
|
|
FStaticLightingManager::Get()->ActiveStaticLightingSystem->CurrentBuildStage = FStaticLightingSystem::Finished;
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::SendBuildDoneNotification( bool AutoApplyFailed )
|
|
{
|
|
FText CompletedText = LOCTEXT("LightBuildDoneMessage", "Lighting build completed");
|
|
|
|
if (ActiveStaticLightingSystem != StaticLightingSystems.Last().Get() && ActiveStaticLightingSystem->LightingScenario)
|
|
{
|
|
FString PackageName = FPackageName::GetShortName(ActiveStaticLightingSystem->LightingScenario->GetOutermost()->GetName());
|
|
CompletedText = FText::Format(LOCTEXT("LightScenarioBuildDoneMessage", "{0} Lighting Scenario completed"), FText::FromString(PackageName));
|
|
}
|
|
|
|
FNotificationInfo Info(CompletedText);
|
|
Info.bFireAndForget = false;
|
|
Info.bUseThrobber = false;
|
|
|
|
FNotificationButtonInfo ApplyNow = FNotificationButtonInfo(
|
|
LOCTEXT( "LightBuildKeep", "Apply Now" ),
|
|
LOCTEXT( "LightBuildKeepToolTip", "Keeps and applies built lighting data." ),
|
|
FSimpleDelegate::CreateStatic( &FStaticLightingManager::ImportRequested ) );
|
|
ApplyNow.VisibilityOnSuccess = EVisibility::Collapsed;
|
|
|
|
FNotificationButtonInfo Discard = FNotificationButtonInfo(
|
|
LOCTEXT( "LightBuildDiscard", "Discard" ),
|
|
LOCTEXT( "LightBuildDiscardToolTip", "Ignores the built lighting data generated." ),
|
|
FSimpleDelegate::CreateStatic( &FStaticLightingManager::DiscardRequested ) );
|
|
Discard.VisibilityOnSuccess = EVisibility::Collapsed;
|
|
|
|
Info.ButtonDetails.Add( ApplyNow );
|
|
Info.ButtonDetails.Add( Discard );
|
|
|
|
FEditorDelegates::OnLightingBuildSucceeded.Broadcast();
|
|
|
|
LightBuildNotification = FSlateNotificationManager::Get().AddNotification( Info );
|
|
if ( LightBuildNotification.IsValid() )
|
|
{
|
|
LightBuildNotification.Pin()->SetCompletionState( AutoApplyFailed ? SNotificationItem::CS_Pending : SNotificationItem::CS_Success );
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::CreateStaticLightingSystem(const FLightingBuildOptions& Options)
|
|
{
|
|
if (StaticLightingSystems.Num() == 0)
|
|
{
|
|
check(!ActiveStaticLightingSystem);
|
|
|
|
for (ULevel* Level : GWorld->GetLevels())
|
|
{
|
|
if (Level->bIsLightingScenario && Level->bIsVisible)
|
|
{
|
|
StaticLightingSystems.Emplace(new FStaticLightingSystem(Options, GWorld, Level));
|
|
}
|
|
}
|
|
|
|
if (StaticLightingSystems.Num() == 0)
|
|
{
|
|
StaticLightingSystems.Emplace(new FStaticLightingSystem(Options, GWorld, nullptr));
|
|
}
|
|
|
|
ActiveStaticLightingSystem = StaticLightingSystems[0].Get();
|
|
|
|
bool bSuccess = ActiveStaticLightingSystem->BeginLightmassProcess();
|
|
|
|
if (bSuccess)
|
|
{
|
|
SendProgressNotification();
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingManager::Get()->FailLightingBuild();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Tell the user that they must close their current build first.
|
|
FStaticLightingManager::Get()->FailLightingBuild(
|
|
LOCTEXT("LightBuildInProgressWarning", "A lighting build is already in progress! Please cancel it before triggering a new build."));
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::UpdateBuildLighting()
|
|
{
|
|
if (ActiveStaticLightingSystem != NULL)
|
|
{
|
|
// Note: UpdateLightingBuild can change ActiveStaticLightingSystem
|
|
ActiveStaticLightingSystem->UpdateLightingBuild();
|
|
|
|
if (ActiveStaticLightingSystem && ActiveStaticLightingSystem->CurrentBuildStage == FStaticLightingSystem::Finished)
|
|
{
|
|
ActiveStaticLightingSystem = nullptr;
|
|
StaticLightingSystems.RemoveAt(0);
|
|
|
|
if (StaticLightingSystems.Num() > 0)
|
|
{
|
|
ActiveStaticLightingSystem = StaticLightingSystems[0].Get();
|
|
|
|
bool bSuccess = ActiveStaticLightingSystem->BeginLightmassProcess();
|
|
|
|
if (bSuccess)
|
|
{
|
|
SendProgressNotification();
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingManager::Get()->FailLightingBuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ActiveStaticLightingSystem)
|
|
{
|
|
FinishLightingBuild();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticLightingManager::FailLightingBuild( FText ErrorText)
|
|
{
|
|
FStaticLightingManager::Get()->ClearCurrentNotification();
|
|
|
|
if (GEditor->GetMapBuildCancelled())
|
|
{
|
|
ErrorText = LOCTEXT("LightBuildCanceledMessage", "Lighting build canceled.");
|
|
}
|
|
else
|
|
{
|
|
// Override failure message if one provided
|
|
if (ErrorText.IsEmpty())
|
|
{
|
|
ErrorText = LOCTEXT("LightBuildFailedMessage", "Lighting build failed.");
|
|
}
|
|
}
|
|
|
|
FNotificationInfo Info( ErrorText );
|
|
Info.ExpireDuration = 4.f;
|
|
|
|
FEditorDelegates::OnLightingBuildFailed.Broadcast();
|
|
|
|
LightBuildNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (LightBuildNotification.IsValid())
|
|
{
|
|
LightBuildNotification.Pin()->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("Failed to build lighting!!! %s"),*ErrorText.ToString());
|
|
|
|
FMessageLog("LightingResults").Open();
|
|
|
|
DestroyStaticLightingSystems();
|
|
}
|
|
|
|
void FStaticLightingManager::FinishLightingBuild()
|
|
{
|
|
UWorld* World = GWorld;
|
|
|
|
GetRendererModule().UpdateMapNeedsLightingFullyRebuiltState(World);
|
|
GEngine->DeferredCommands.AddUnique(TEXT("MAP CHECK NOTIFYRESULTS"));
|
|
|
|
if (World->Scene)
|
|
{
|
|
// Everything should be built at this point, dump unbuilt interactions for debugging
|
|
World->Scene->DumpUnbuiltLightInteractions(*GLog);
|
|
}
|
|
|
|
GEditor->BuildReflectionCaptures(World);
|
|
}
|
|
|
|
void FStaticLightingManager::DestroyStaticLightingSystems()
|
|
{
|
|
ActiveStaticLightingSystem = NULL;
|
|
StaticLightingSystems.Empty();
|
|
}
|
|
|
|
bool FStaticLightingManager::IsLightingBuildCurrentlyRunning() const
|
|
{
|
|
return ActiveStaticLightingSystem != NULL;
|
|
}
|
|
|
|
bool FStaticLightingManager::IsLightingBuildCurrentlyExporting() const
|
|
{
|
|
return ActiveStaticLightingSystem != NULL && ActiveStaticLightingSystem->IsAmortizedExporting();
|
|
}
|
|
|
|
FStaticLightingSystem::FStaticLightingSystem(const FLightingBuildOptions& InOptions, UWorld* InWorld, ULevel* InLightingScenario)
|
|
: Options(InOptions)
|
|
, bBuildCanceled(false)
|
|
, DeterministicIndex(0)
|
|
, NextVisibilityId(0)
|
|
, CurrentBuildStage(FStaticLightingSystem::NotRunning)
|
|
, World(InWorld)
|
|
, LightingScenario(InLightingScenario)
|
|
, LightmassProcessor(NULL)
|
|
{
|
|
}
|
|
|
|
FStaticLightingSystem::~FStaticLightingSystem()
|
|
{
|
|
if (LightmassProcessor)
|
|
{
|
|
delete LightmassProcessor;
|
|
}
|
|
}
|
|
|
|
bool FStaticLightingSystem::BeginLightmassProcess()
|
|
{
|
|
StartTime = FPlatformTime::Seconds();
|
|
|
|
CurrentBuildStage = FStaticLightingSystem::Startup;
|
|
|
|
bool bRebuildDirtyGeometryForLighting = true;
|
|
bool bForceNoPrecomputedLighting = false;
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather StartupStatScope(LightmassStatistics.StartupTime);
|
|
|
|
// Flip the results page
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("TimeStamp"), FText::AsDateTime(FDateTime::Now()));
|
|
FText LightingResultsPageName(FText::Format(LOCTEXT("LightingResultsPageName", "Lighting Build - {TimeStamp}"), Arguments));
|
|
FMessageLog("LightingResults").NewPage(LightingResultsPageName);
|
|
|
|
|
|
FStatsViewerModule& StatsViewerModule = FModuleManager::Get().LoadModuleChecked<FStatsViewerModule>(TEXT("StatsViewer"));
|
|
StatsViewerModule.GetPage(EStatsPage::LightingBuildInfo)->Clear();
|
|
|
|
GLightmapCounter = 0;
|
|
GNumLightmapTotalTexels = 0;
|
|
GNumLightmapTotalTexelsNonPow2 = 0;
|
|
GNumLightmapTextures = 0;
|
|
GNumLightmapMappedTexels = 0;
|
|
GNumLightmapUnmappedTexels = 0;
|
|
GLightmapTotalSize = 0;
|
|
GLightmapTotalStreamingSize = 0;
|
|
|
|
GNumShadowmapTotalTexels = 0;
|
|
GNumShadowmapTextures = 0;
|
|
GNumShadowmapMappedTexels = 0;
|
|
GNumShadowmapUnmappedTexels = 0;
|
|
GShadowmapTotalSize = 0;
|
|
GShadowmapTotalStreamingSize = 0;
|
|
|
|
for( TObjectIterator<UPrimitiveComponent> It ; It ; ++It )
|
|
{
|
|
UPrimitiveComponent* Component = *It;
|
|
Component->VisibilityId = INDEX_NONE;
|
|
}
|
|
|
|
FString SkippedLevels;
|
|
for ( int32 LevelIndex=0; LevelIndex < World->GetNumLevels(); LevelIndex++ )
|
|
{
|
|
ULevel* Level = World->GetLevel(LevelIndex);
|
|
|
|
if (ShouldOperateOnLevel(Level))
|
|
{
|
|
Level->LightmapTotalSize = 0.0f;
|
|
Level->ShadowmapTotalSize = 0.0f;
|
|
ULevelStreaming* LevelStreaming = NULL;
|
|
if ( World->PersistentLevel != Level )
|
|
{
|
|
LevelStreaming = FLevelUtils::FindStreamingLevel( Level );
|
|
}
|
|
if (!Options.ShouldBuildLightingForLevel(Level))
|
|
{
|
|
if (SkippedLevels.Len() > 0)
|
|
{
|
|
SkippedLevels += FString(TEXT(", "));
|
|
}
|
|
SkippedLevels += Level->GetName();
|
|
}
|
|
}
|
|
}
|
|
|
|
for( int32 LevelIndex = 0 ; LevelIndex < World->StreamingLevels.Num() ; ++LevelIndex )
|
|
{
|
|
ULevelStreaming* CurStreamingLevel = World->StreamingLevels[ LevelIndex ];
|
|
if (CurStreamingLevel && CurStreamingLevel->GetLoadedLevel() && !CurStreamingLevel->bShouldBeVisibleInEditor)
|
|
{
|
|
if (SkippedLevels.Len() > 0)
|
|
{
|
|
SkippedLevels += FString(TEXT(", ")) + CurStreamingLevel->GetWorldAssetPackageName();
|
|
}
|
|
else
|
|
{
|
|
SkippedLevels += CurStreamingLevel->GetWorldAssetPackageName();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SkippedLevels.Len() > 0 && !IsRunningCommandlet())
|
|
{
|
|
// Warn when some levels are not visible and therefore will not be built, because that indicates that only a partial build will be done,
|
|
// Lighting will still be unbuilt for some areas when playing through the level.
|
|
const FText SkippedLevelsWarning = FText::Format( LOCTEXT("SkippedLevels", "The following levels will not have the lighting rebuilt because of your selected lighting build options: {0}"), FText::FromString( SkippedLevels ) );
|
|
FSuppressableWarningDialog::FSetupInfo Info( SkippedLevelsWarning, LOCTEXT("SkippedLevelsDialogTitle", "Rebuild Lighting - Warning" ), "WarnOnHiddenLevelsBeforeRebuild" );
|
|
Info.ConfirmText = LOCTEXT("SkippedWarningConfirm", "Build");
|
|
|
|
FSuppressableWarningDialog WarnAboutSkippedLevels( Info );
|
|
WarnAboutSkippedLevels.ShowModal();
|
|
}
|
|
|
|
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
|
|
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
|
|
bForceNoPrecomputedLighting = World->GetWorldSettings()->bForceNoPrecomputedLighting || !bAllowStaticLighting;
|
|
GConfig->GetFloat( TEXT("TextureStreaming"), TEXT("MaxLightmapRadius"), GMaxLightmapRadius, GEngineIni );
|
|
GConfig->GetBool( TEXT("TextureStreaming"), TEXT("AllowStreamingLightmaps"), GAllowStreamingLightmaps, GEngineIni );
|
|
|
|
if (!bForceNoPrecomputedLighting)
|
|
{
|
|
// Begin the static lighting progress bar.
|
|
GWarn->BeginSlowTask( LOCTEXT("BeginBuildingStaticLightingTaskStatus", "Building lighting"), false );
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("WorldSettings.bForceNoPrecomputedLighting is true, Skipping Lighting Build!"));
|
|
}
|
|
|
|
FConfigCacheIni::LoadGlobalIniFile(GLightmassIni, TEXT("Lightmass"), NULL, true);
|
|
verify(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bUseBilinearFilterLightmaps"), GUseBilinearLightmaps, GLightmassIni));
|
|
verify(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bAllowCropping"), GAllowLightmapCropping, GLightmassIni));
|
|
verify(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bRebuildDirtyGeometryForLighting"), bRebuildDirtyGeometryForLighting, GLightmassIni));
|
|
verify(GConfig->GetBool(TEXT("DevOptions.StaticLighting"), TEXT("bCompressLightmaps"), GCompressLightmaps, GLightmassIni));
|
|
|
|
GCompressLightmaps = GCompressLightmaps && World->GetWorldSettings()->LightmassSettings.bCompressLightmaps;
|
|
|
|
GAllowLightmapPadding = true;
|
|
FMemory::Memzero(&LightingMeshBounds, sizeof(FBox));
|
|
FMemory::Memzero(&AutomaticImportanceVolumeBounds, sizeof(FBox));
|
|
|
|
GLightingBuildQuality = Options.QualityLevel;
|
|
|
|
}
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather CollectStatScope(LightmassStatistics.CollectTime);
|
|
|
|
// Prepare lights for rebuild.
|
|
{
|
|
FLightmassStatistics::FScopedGather PrepareStatScope(LightmassStatistics.PrepareLightsTime);
|
|
|
|
if (!Options.bOnlyBuildVisibility)
|
|
{
|
|
// Delete all AGeneratedMeshAreaLight's, since new ones will be created after the build with updated properties.
|
|
USelection* EditorSelection = GEditor->GetSelectedActors();
|
|
for(TObjectIterator<AGeneratedMeshAreaLight> LightIt;LightIt;++LightIt)
|
|
{
|
|
if (ShouldOperateOnLevel((*LightIt)->GetLevel()))
|
|
{
|
|
if (EditorSelection)
|
|
{
|
|
EditorSelection->Deselect(*LightIt);
|
|
}
|
|
(*LightIt)->GetWorld()->DestroyActor(*LightIt);
|
|
}
|
|
}
|
|
|
|
for (TObjectIterator<ULightComponentBase> LightIt(RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::PendingKill); LightIt; ++LightIt)
|
|
{
|
|
ULightComponentBase* const Light = *LightIt;
|
|
const bool bLightIsInWorld = Light->GetOwner()
|
|
&& World->ContainsActor(Light->GetOwner())
|
|
&& !Light->GetOwner()->IsPendingKill();
|
|
|
|
if (bLightIsInWorld && ShouldOperateOnLevel(Light->GetOwner()->GetLevel()))
|
|
{
|
|
if (Light->bAffectsWorld
|
|
&& Light->IsRegistered()
|
|
&& (Light->HasStaticShadowing() || Light->HasStaticLighting()))
|
|
{
|
|
// Make sure the light GUIDs are up-to-date.
|
|
Light->ValidateLightGUIDs();
|
|
|
|
// Add the light to the system's list of lights in the world.
|
|
Lights.Add(Light);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather GatherStatScope(LightmassStatistics.GatherLightingInfoTime);
|
|
|
|
if (IsTexelDebuggingEnabled())
|
|
{
|
|
// Clear reference to the selected lightmap
|
|
GCurrentSelectedLightmapSample.Lightmap = NULL;
|
|
GDebugStaticLightingInfo = FDebugLightingOutput();
|
|
}
|
|
|
|
GatherStaticLightingInfo(bRebuildDirtyGeometryForLighting, bForceNoPrecomputedLighting);
|
|
}
|
|
|
|
// Sort the mappings - and tag meshes if doing deterministic mapping
|
|
if (GLightmassDebugOptions.bSortMappings)
|
|
{
|
|
struct FCompareNumTexels
|
|
{
|
|
FORCEINLINE bool operator()( const FStaticLightingMappingSortHelper& A, const FStaticLightingMappingSortHelper& B ) const
|
|
{
|
|
return B.NumTexels < A.NumTexels;
|
|
}
|
|
};
|
|
UnSortedMappings.Sort( FCompareNumTexels() );
|
|
|
|
for (int32 SortIndex = 0; SortIndex < UnSortedMappings.Num(); SortIndex++)
|
|
{
|
|
FStaticLightingMapping* Mapping = UnSortedMappings[SortIndex].Mapping;
|
|
Mappings.Add(Mapping);
|
|
|
|
if (Mapping->bProcessMapping)
|
|
{
|
|
if (Mapping->Mesh)
|
|
{
|
|
Mapping->Mesh->Guid = FGuid(0,0,0,DeterministicIndex++);
|
|
}
|
|
}
|
|
}
|
|
UnSortedMappings.Empty();
|
|
}
|
|
|
|
// Verify deterministic lighting setup, if it is enabled...
|
|
for (int32 CheckMapIdx = 0; CheckMapIdx < Mappings.Num(); CheckMapIdx++)
|
|
{
|
|
if (Mappings[CheckMapIdx]->bProcessMapping)
|
|
{
|
|
FGuid CheckGuid = Mappings[CheckMapIdx]->Mesh->Guid;
|
|
if ((CheckGuid.A != 0) ||
|
|
(CheckGuid.B != 0) ||
|
|
(CheckGuid.C != 0) ||
|
|
(CheckGuid.D >= (uint32)(Mappings.Num()))
|
|
)
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("Lightmass: Error in deterministic lighting for %s:%s"),
|
|
*(Mappings[CheckMapIdx]->Mesh->Guid.ToString()), *(Mappings[CheckMapIdx]->GetDescription()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we are dumping binary results, clear up any existing ones
|
|
if (Options.bDumpBinaryResults)
|
|
{
|
|
FStaticLightingSystem::ClearBinaryDumps();
|
|
}
|
|
}
|
|
|
|
ProcessingStartTime = FPlatformTime::Seconds();
|
|
|
|
bool bLightingSuccessful = false;
|
|
if (!bForceNoPrecomputedLighting)
|
|
{
|
|
bool bSavedUpdateStatus_LightMap = FLightMap2D::GetStatusUpdate();
|
|
if (GLightmassDebugOptions.bImmediateProcessMappings)
|
|
{
|
|
FLightMap2D::SetStatusUpdate(false);
|
|
}
|
|
|
|
bLightingSuccessful = CreateLightmassProcessor();
|
|
if (bLightingSuccessful)
|
|
{
|
|
GatherScene();
|
|
bLightingSuccessful = InitiateLightmassProcessor();
|
|
}
|
|
|
|
if (GLightmassDebugOptions.bImmediateProcessMappings)
|
|
{
|
|
FLightMap2D::SetStatusUpdate(bSavedUpdateStatus_LightMap);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InvalidateStaticLighting();
|
|
ApplyNewLightingData(true);
|
|
}
|
|
|
|
if (!bForceNoPrecomputedLighting)
|
|
{
|
|
// End the static lighting progress bar.
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
return bLightingSuccessful;
|
|
}
|
|
|
|
void FStaticLightingSystem::InvalidateStaticLighting()
|
|
{
|
|
FLightmassStatistics::FScopedGather InvalidationScopeStat(LightmassStatistics.InvalidationTime);
|
|
|
|
for( int32 LevelIndex=0; LevelIndex<World->GetNumLevels(); LevelIndex++ )
|
|
{
|
|
bool bMarkLevelDirty = false;
|
|
ULevel* Level = World->GetLevel(LevelIndex);
|
|
|
|
if (!ShouldOperateOnLevel(Level))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bBuildLightingForLevel = Options.ShouldBuildLightingForLevel( Level );
|
|
|
|
if (bBuildLightingForLevel)
|
|
{
|
|
if (!Options.bOnlyBuildVisibility)
|
|
{
|
|
Level->ReleaseRenderingResources();
|
|
|
|
if (Level->MapBuildData)
|
|
{
|
|
Level->MapBuildData->InvalidateStaticLighting(World);
|
|
}
|
|
}
|
|
if (Level == World->PersistentLevel)
|
|
{
|
|
Level->PrecomputedVisibilityHandler.Invalidate(World->Scene);
|
|
Level->PrecomputedVolumeDistanceField.Invalidate(World->Scene);
|
|
}
|
|
|
|
// Mark any existing cached lightmap data as transient. This allows the derived data cache to purge it more aggressively.
|
|
// It is safe to do so even if some of these lightmaps are needed. It just means compressed data will have to be retrieved
|
|
// from the network cache or rebuilt.
|
|
if (GPurgeOldLightmaps != 0 && Level->MapBuildData)
|
|
{
|
|
UPackage* MapDataPackage = Level->MapBuildData->GetOutermost();
|
|
|
|
for (TObjectIterator<ULightMapTexture2D> It; It; ++It)
|
|
{
|
|
ULightMapTexture2D* LightMapTexture = *It;
|
|
if (LightMapTexture->GetOutermost() == MapDataPackage)
|
|
{
|
|
LightMapTexture->MarkPlatformDataTransient();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateStaticLightingHLODTreeIndices(TMultiMap<AActor*, FStaticLightingMesh*>& ActorMeshMap, ALODActor* LODActor, uint32 HLODTreeIndex, uint32& HLODLeafIndex)
|
|
{
|
|
check(LODActor && HLODTreeIndex > 0);
|
|
|
|
uint32 LeafStartIndex = HLODLeafIndex;
|
|
++HLODLeafIndex;
|
|
|
|
for (AActor* SubActor : LODActor->SubActors)
|
|
{
|
|
if (ALODActor* LODSubActor = Cast<ALODActor>(SubActor))
|
|
{
|
|
UpdateStaticLightingHLODTreeIndices(ActorMeshMap, LODSubActor, HLODTreeIndex, HLODLeafIndex);
|
|
}
|
|
else
|
|
{
|
|
TArray<FStaticLightingMesh*> SubActorMeshes;
|
|
ActorMeshMap.MultiFind(SubActor,SubActorMeshes);
|
|
|
|
for (FStaticLightingMesh* SubActorMesh : SubActorMeshes)
|
|
{
|
|
if (SubActorMesh->HLODTreeIndex == 0)
|
|
{
|
|
SubActorMesh->HLODTreeIndex = HLODTreeIndex;
|
|
SubActorMesh->HLODChildStartIndex = HLODLeafIndex;
|
|
SubActorMesh->HLODChildEndIndex = HLODLeafIndex;
|
|
++HLODLeafIndex;
|
|
}
|
|
else
|
|
{
|
|
// Output error to message log containing tokens to the problematic objects
|
|
FMessageLog("LightingResults").Warning()
|
|
->AddToken(FUObjectToken::Create(SubActorMesh->Component->GetOwner()))
|
|
->AddToken(FTextToken::Create(LOCTEXT("LightmassError_InvalidHLODTreeIndex", "will not be correctly lit since it is part of another Hierarchical LOD cluster besides ")))
|
|
->AddToken(FUObjectToken::Create(LODActor));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FStaticLightingMesh*> LODActorMeshes;
|
|
ActorMeshMap.MultiFind(LODActor, LODActorMeshes);
|
|
for (FStaticLightingMesh* LODActorMesh : LODActorMeshes)
|
|
{
|
|
LODActorMesh->HLODTreeIndex = HLODTreeIndex;
|
|
LODActorMesh->HLODChildStartIndex = LeafStartIndex;
|
|
LODActorMesh->HLODChildEndIndex = HLODLeafIndex - 1;
|
|
check(LODActorMesh->HLODChildEndIndex >= LODActorMesh->HLODChildStartIndex);
|
|
}
|
|
}
|
|
|
|
void FStaticLightingSystem::GatherStaticLightingInfo(bool bRebuildDirtyGeometryForLighting, bool bForceNoPrecomputedLighting)
|
|
{
|
|
uint32 ActorsInvalidated = 0;
|
|
uint32 ActorsToInvalidate = 0;
|
|
for( int32 LevelIndex=0; LevelIndex<World->GetNumLevels(); LevelIndex++ )
|
|
{
|
|
ActorsToInvalidate += World->GetLevel(LevelIndex)->Actors.Num();
|
|
}
|
|
const int32 ProgressUpdateFrequency = FMath::Max<int32>(ActorsToInvalidate / 20, 1);
|
|
|
|
GWarn->StatusUpdate( ActorsInvalidated, ActorsToInvalidate, LOCTEXT("GatheringSceneGeometryStatus", "Gathering scene geometry...") );
|
|
|
|
bool bObjectsToBuildLightingForFound = false;
|
|
// Gather static lighting info from actor components.
|
|
for (int32 LevelIndex = 0; LevelIndex < World->GetNumLevels(); LevelIndex++)
|
|
{
|
|
bool bMarkLevelDirty = false;
|
|
ULevel* Level = World->GetLevel(LevelIndex);
|
|
|
|
if (!ShouldOperateOnLevel(Level))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the geometry is dirty and we're allowed to automatically clean it up, do so
|
|
if (Level->bGeometryDirtyForLighting)
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("WARNING: Lighting build detected that geometry needs to be rebuilt to avoid incorrect lighting (due to modifying a lighting property)."));
|
|
if (bRebuildDirtyGeometryForLighting)
|
|
{
|
|
// This will go ahead and clean up lighting on all dirty levels (not just this one)
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("WARNING: Lighting build automatically rebuilding geometry.") );
|
|
GEditor->Exec(World, TEXT("MAP REBUILD ALLDIRTYFORLIGHTING"));
|
|
}
|
|
}
|
|
|
|
const bool bBuildLightingForLevel = Options.ShouldBuildLightingForLevel(Level);
|
|
|
|
// Gather static lighting info from BSP.
|
|
bool bBuildBSPLighting = bBuildLightingForLevel;
|
|
|
|
TArray<FNodeGroup*> NodeGroupsToBuild;
|
|
TArray<UModelComponent*> SelectedModelComponents;
|
|
if (bBuildBSPLighting && !Options.bOnlyBuildVisibility)
|
|
{
|
|
if (Options.bOnlyBuildSelected)
|
|
{
|
|
UModel* Model = Level->Model;
|
|
GLightmassDebugOptions.bGatherBSPSurfacesAcrossComponents = false;
|
|
Model->GroupAllNodes(Level, Lights);
|
|
bBuildBSPLighting = false;
|
|
// Build only selected brushes/surfaces
|
|
TArray<ABrush*> SelectedBrushes;
|
|
for (int32 ActorIndex = 0; ActorIndex < Level->Actors.Num(); ActorIndex++)
|
|
{
|
|
AActor* Actor = Level->Actors[ActorIndex];
|
|
if (Actor)
|
|
{
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush && Brush->IsSelected())
|
|
{
|
|
SelectedBrushes.Add(Brush);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<int32> SelectedSurfaceIndices;
|
|
// Find selected surfaces...
|
|
for (int32 SurfIdx = 0; SurfIdx < Model->Surfs.Num(); SurfIdx++)
|
|
{
|
|
bool bSurfaceSelected = false;
|
|
FBspSurf& Surf = Model->Surfs[SurfIdx];
|
|
if ((Surf.PolyFlags & PF_Selected) != 0)
|
|
{
|
|
SelectedSurfaceIndices.Add(SurfIdx);
|
|
bSurfaceSelected = true;
|
|
}
|
|
else
|
|
{
|
|
int32 DummyIdx;
|
|
if (SelectedBrushes.Find(Surf.Actor, DummyIdx) == true)
|
|
{
|
|
SelectedSurfaceIndices.Add(SurfIdx);
|
|
bSurfaceSelected = true;
|
|
}
|
|
}
|
|
|
|
if (bSurfaceSelected == true)
|
|
{
|
|
// Find it's model component...
|
|
for (int32 NodeIdx = 0; NodeIdx < Model->Nodes.Num(); NodeIdx++)
|
|
{
|
|
const FBspNode& Node = Model->Nodes[NodeIdx];
|
|
if (Node.iSurf == SurfIdx)
|
|
{
|
|
UModelComponent* SomeModelComponent = Level->ModelComponents[Node.ComponentIndex];
|
|
if (SomeModelComponent)
|
|
{
|
|
SelectedModelComponents.AddUnique(SomeModelComponent);
|
|
for (int32 InnerNodeIndex = 0; InnerNodeIndex < SomeModelComponent->Nodes.Num(); InnerNodeIndex++)
|
|
{
|
|
FBspNode& InnerNode = Model->Nodes[SomeModelComponent->Nodes[InnerNodeIndex]];
|
|
SelectedSurfaceIndices.AddUnique(InnerNode.iSurf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2...
|
|
if (SelectedSurfaceIndices.Num() > 0)
|
|
{
|
|
for (int32 SSIdx = 0; SSIdx < SelectedSurfaceIndices.Num(); SSIdx++)
|
|
{
|
|
int32 SurfIdx = SelectedSurfaceIndices[SSIdx];
|
|
// Find it's model component...
|
|
for (int32 NodeIdx = 0; NodeIdx < Model->Nodes.Num(); NodeIdx++)
|
|
{
|
|
const FBspNode& Node = Model->Nodes[NodeIdx];
|
|
if (Node.iSurf == SurfIdx)
|
|
{
|
|
UModelComponent* SomeModelComponent = Level->ModelComponents[Node.ComponentIndex];
|
|
if (SomeModelComponent)
|
|
{
|
|
SelectedModelComponents.AddUnique(SomeModelComponent);
|
|
for (int32 InnerNodeIndex = 0; InnerNodeIndex < SomeModelComponent->Nodes.Num(); InnerNodeIndex++)
|
|
{
|
|
FBspNode& InnerNode = Model->Nodes[SomeModelComponent->Nodes[InnerNodeIndex]];
|
|
SelectedSurfaceIndices.AddUnique(InnerNode.iSurf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SelectedSurfaceIndices.Num() > 0)
|
|
{
|
|
// Fill in a list of all the node group to rebuild...
|
|
bBuildBSPLighting = false;
|
|
for (TMap<int32, FNodeGroup*>::TIterator It(Model->NodeGroups); It; ++It)
|
|
{
|
|
FNodeGroup* NodeGroup = It.Value();
|
|
if (NodeGroup && (NodeGroup->Nodes.Num() > 0))
|
|
{
|
|
for (int32 GroupNodeIdx = 0; GroupNodeIdx < NodeGroup->Nodes.Num(); GroupNodeIdx++)
|
|
{
|
|
int32 CheckIdx;
|
|
if (SelectedSurfaceIndices.Find(Model->Nodes[NodeGroup->Nodes[GroupNodeIdx]].iSurf, CheckIdx) == true)
|
|
{
|
|
NodeGroupsToBuild.AddUnique(NodeGroup);
|
|
bBuildBSPLighting = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bBuildBSPLighting && !bForceNoPrecomputedLighting)
|
|
{
|
|
if (!Options.bOnlyBuildSelected || Options.bOnlyBuildVisibility)
|
|
{
|
|
// generate BSP mappings across the whole level
|
|
AddBSPStaticLightingInfo(Level, bBuildBSPLighting);
|
|
}
|
|
else
|
|
{
|
|
if (NodeGroupsToBuild.Num() > 0)
|
|
{
|
|
bObjectsToBuildLightingForFound = true;
|
|
AddBSPStaticLightingInfo(Level, NodeGroupsToBuild);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gather HLOD primitives
|
|
TMultiMap<AActor*, UPrimitiveComponent*> PrimitiveActorMap;
|
|
TMultiMap<UPrimitiveComponent*, UStaticMeshComponent*> PrimitiveSubStaticMeshMap;
|
|
|
|
for (int32 ActorIndex = 0; ActorIndex < Level->Actors.Num(); ActorIndex++)
|
|
{
|
|
AActor* Actor = Level->Actors[ActorIndex];
|
|
if (Actor)
|
|
{
|
|
ALODActor* LODActor = Cast<ALODActor>(Actor);
|
|
if (LODActor && LODActor->GetStaticMeshComponent())
|
|
{
|
|
|
|
UPrimitiveComponent* PrimitiveParent = LODActor->GetStaticMeshComponent()->GetLODParentPrimitive();
|
|
|
|
for (auto SubActor : LODActor->SubActors)
|
|
{
|
|
PrimitiveActorMap.Add(SubActor, LODActor->GetStaticMeshComponent());
|
|
|
|
if (PrimitiveParent)
|
|
{
|
|
PrimitiveActorMap.Add(SubActor, PrimitiveParent);
|
|
}
|
|
|
|
TArray<UStaticMeshComponent*> SubStaticMeshComponents;
|
|
SubActor->GetComponents<UStaticMeshComponent>(SubStaticMeshComponents);
|
|
for (auto SMC : SubStaticMeshComponents)
|
|
{
|
|
PrimitiveSubStaticMeshMap.Add(LODActor->GetStaticMeshComponent(), SMC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TMultiMap<AActor*, FStaticLightingMesh*> ActorMeshMap;
|
|
TArray<ALODActor*> LODActors;
|
|
|
|
// Gather static lighting info from actors.
|
|
for (int32 ActorIndex = 0; ActorIndex < Level->Actors.Num(); ActorIndex++)
|
|
{
|
|
AActor* Actor = Level->Actors[ActorIndex];
|
|
if (Actor)
|
|
{
|
|
const bool bBuildActorLighting =
|
|
bBuildLightingForLevel &&
|
|
(!Options.bOnlyBuildSelected || Actor->IsSelected());
|
|
|
|
TInlineComponentArray<UPrimitiveComponent*> Components;
|
|
Actor->GetComponents(Components);
|
|
|
|
if (bBuildActorLighting)
|
|
{
|
|
bObjectsToBuildLightingForFound = true;
|
|
}
|
|
|
|
TArray<UPrimitiveComponent*> HLODPrimitiveParents;
|
|
PrimitiveActorMap.MultiFind(Actor, HLODPrimitiveParents);
|
|
|
|
ALODActor* LODActor = Cast<ALODActor>(Actor);
|
|
if (LODActor)
|
|
{
|
|
LODActors.Add(LODActor);
|
|
}
|
|
|
|
// Gather static lighting info from each of the actor's components.
|
|
for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++)
|
|
{
|
|
UPrimitiveComponent* Primitive = Components[ComponentIndex];
|
|
if (Primitive->IsRegistered() && !bForceNoPrecomputedLighting)
|
|
{
|
|
// Find the lights relevant to the primitive.
|
|
TArray<ULightComponent*> PrimitiveRelevantLights;
|
|
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
|
|
{
|
|
ULightComponentBase* LightBase = Lights[LightIndex];
|
|
ULightComponent* Light = Cast<ULightComponent>(LightBase);
|
|
|
|
// Only add enabled lights
|
|
if (Light && Light->AffectsPrimitive(Primitive))
|
|
{
|
|
PrimitiveRelevantLights.Add(Light);
|
|
}
|
|
}
|
|
|
|
// Query the component for its static lighting info.
|
|
FStaticLightingPrimitiveInfo PrimitiveInfo;
|
|
Primitive->GetStaticLightingInfo(PrimitiveInfo, PrimitiveRelevantLights, Options);
|
|
if (PrimitiveInfo.Meshes.Num() > 0 && (Primitive->Mobility == EComponentMobility::Static))
|
|
{
|
|
if (World->GetWorldSettings()->bPrecomputeVisibility)
|
|
{
|
|
// Make sure the level gets dirtied since we are changing the visibility Id of a component in it
|
|
bMarkLevelDirty = true;
|
|
}
|
|
|
|
PrimitiveInfo.VisibilityId = Primitive->VisibilityId = NextVisibilityId;
|
|
NextVisibilityId++;
|
|
}
|
|
|
|
TArray<UStaticMeshComponent*> LODSubActorSMComponents;
|
|
|
|
if (LODActor)
|
|
{
|
|
PrimitiveSubStaticMeshMap.MultiFind(Primitive, LODSubActorSMComponents);
|
|
}
|
|
|
|
for (auto Mesh : PrimitiveInfo.Meshes)
|
|
{
|
|
ActorMeshMap.Add(Actor, Mesh);
|
|
}
|
|
|
|
AddPrimitiveStaticLightingInfo(PrimitiveInfo, bBuildActorLighting);
|
|
}
|
|
}
|
|
}
|
|
|
|
ActorsInvalidated++;
|
|
|
|
if (ActorsInvalidated % ProgressUpdateFrequency == 0)
|
|
{
|
|
GWarn->UpdateProgress(ActorsInvalidated, ActorsToInvalidate);
|
|
}
|
|
}
|
|
|
|
// Recurse through HLOD trees, group actors and calculate child ranges
|
|
uint32 HLODTreeIndex = 1;
|
|
uint32 HLODLeafIndex;
|
|
|
|
for (ALODActor* LODActor : LODActors)
|
|
{
|
|
// Only process fully merged (root) HLOD nodes
|
|
if (LODActor->GetStaticMeshComponent() && !LODActor->GetStaticMeshComponent()->GetLODParentPrimitive())
|
|
{
|
|
HLODLeafIndex = 0;
|
|
|
|
UpdateStaticLightingHLODTreeIndices(ActorMeshMap, LODActor, HLODTreeIndex, HLODLeafIndex);
|
|
|
|
++HLODTreeIndex;
|
|
}
|
|
}
|
|
|
|
if (bMarkLevelDirty)
|
|
{
|
|
Level->MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
if (Options.bOnlyBuildSelected)
|
|
{
|
|
FMessageLog("LightingResults").Warning(LOCTEXT("LightmassError_BuildSelected", "Building selected actors only, lightmap memory and quality will be sub-optimal until the next full rebuild."));
|
|
|
|
if (!bObjectsToBuildLightingForFound)
|
|
{
|
|
FMessageLog("LightingResults").Error(LOCTEXT("LightmassError_BuildSelectedNothingSelected", "Building selected actors and BSP only, but no actors or BSP selected!"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticLightingSystem::EncodeTextures(bool bLightingSuccessful)
|
|
{
|
|
FLightmassStatistics::FScopedGather EncodeStatScope(LightmassStatistics.EncodingTime);
|
|
|
|
FScopedSlowTask SlowTask(2);
|
|
{
|
|
FLightmassStatistics::FScopedGather EncodeStatScope2(LightmassStatistics.EncodingLightmapsTime);
|
|
// Flush pending shadow-map and light-map encoding.
|
|
SlowTask.EnterProgressFrame(1, LOCTEXT("EncodingImportedStaticLightMapsStatusMessage", "Encoding imported static light maps."));
|
|
FLightMap2D::EncodeTextures(World, bLightingSuccessful, GMultithreadedLightmapEncode ? true : false);
|
|
}
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather EncodeStatScope2(LightmassStatistics.EncodingShadowMapsTime);
|
|
SlowTask.EnterProgressFrame(1, LOCTEXT("EncodingImportedStaticShadowMapsStatusMessage", "Encoding imported static shadow maps."));
|
|
FShadowMap2D::EncodeTextures(World, LightingScenario, bLightingSuccessful, GMultithreadedShadowmapEncode ? true : false);
|
|
}
|
|
}
|
|
|
|
void FStaticLightingSystem::ApplyNewLightingData(bool bLightingSuccessful)
|
|
{
|
|
{
|
|
FLightmassStatistics::FScopedGather ApplyStatScope(LightmassStatistics.ApplyTime);
|
|
// Now that the lighting is done, we can tell the model components to use their new elements,
|
|
// instead of the pre-lighting ones
|
|
UModelComponent::ApplyTempElements(bLightingSuccessful);
|
|
}
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather FinishStatScope(LightmassStatistics.FinishingTime);
|
|
|
|
// Mark lights of the computed level to have valid precomputed lighting.
|
|
for (int32 LevelIndex = 0; LevelIndex < World->GetNumLevels(); LevelIndex++)
|
|
{
|
|
ULevel* Level = World->GetLevel(LevelIndex);
|
|
|
|
if (!ShouldOperateOnLevel(Level))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ULevel* StorageLevel = LightingScenario ? LightingScenario : Level;
|
|
UMapBuildDataRegistry* Registry = StorageLevel->GetOrCreateMapBuildData();
|
|
|
|
// Notify level about new lighting data
|
|
Level->OnApplyNewLightingData(bLightingSuccessful);
|
|
|
|
Level->InitializeRenderingResources();
|
|
|
|
if (World->PersistentLevel == Level)
|
|
{
|
|
Level->PrecomputedVisibilityHandler.UpdateScene(World->Scene);
|
|
Level->PrecomputedVolumeDistanceField.UpdateScene(World->Scene);
|
|
}
|
|
|
|
uint32 ActorCount = Level->Actors.Num();
|
|
|
|
for (uint32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex)
|
|
{
|
|
AActor* Actor = Level->Actors[ActorIndex];
|
|
|
|
if (Actor && bLightingSuccessful && !Options.bOnlyBuildSelected)
|
|
{
|
|
TInlineComponentArray<ULightComponent*> Components;
|
|
Actor->GetComponents(Components);
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++)
|
|
{
|
|
ULightComponent* LightComponent = Components[ComponentIndex];
|
|
if (LightComponent && (LightComponent->HasStaticShadowing() || LightComponent->HasStaticLighting()))
|
|
{
|
|
if (!Registry->GetLightBuildData(LightComponent->LightGuid))
|
|
{
|
|
// Add a dummy entry for ULightComponent::IsPrecomputedLightingValid()
|
|
Registry->FindOrAllocateLightBuildData(LightComponent->LightGuid, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bBuildLightingForLevel = Options.ShouldBuildLightingForLevel( Level );
|
|
|
|
// Store off the quality of the lighting for the level if lighting was successful and we build lighting for this level.
|
|
if( bLightingSuccessful && bBuildLightingForLevel )
|
|
{
|
|
Registry->LevelLightingQuality = Options.QualityLevel;
|
|
Registry->MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
// Ensure all primitives which were marked dirty by the lighting build are updated.
|
|
// First clear all components so that any references to static lighting assets held
|
|
// by scene proxies will be fully released before any components are reregistered.
|
|
// We do not rerun construction scripts - nothing should have changed that requires that, and
|
|
// want to know which components were not moved during lighting rebuild
|
|
{
|
|
FGlobalComponentRecreateRenderStateContext RecreateRenderState;
|
|
}
|
|
|
|
// Clean up old shadow-map and light-map data.
|
|
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );
|
|
|
|
// Commit the changes to the world's BSP surfaces.
|
|
World->CommitModelSurfaces();
|
|
}
|
|
|
|
// Report failed lighting build (don't count cancelled builds as failure).
|
|
if ( !bLightingSuccessful && !bBuildCanceled )
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("LightingBuildFailedDialogMessage", "The lighting build failed! See the log for more information!") );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reports lighting build statistics to the log.
|
|
*/
|
|
void FStaticLightingSystem::ReportStatistics()
|
|
{
|
|
extern UNREALED_API bool GLightmassStatsMode;
|
|
if ( GLightmassStatsMode )
|
|
{
|
|
double TrackedTime =
|
|
LightmassStatistics.StartupTime
|
|
+ LightmassStatistics.CollectTime
|
|
+ LightmassStatistics.ProcessingTime
|
|
+ LightmassStatistics.ImportTime
|
|
+ LightmassStatistics.ApplyTime
|
|
+ LightmassStatistics.EncodingTime
|
|
+ LightmassStatistics.InvalidationTime
|
|
+ LightmassStatistics.FinishingTime;
|
|
double UntrackedTime = LightmassStatistics.TotalTime - TrackedTime;
|
|
UE_LOG(LogStaticLightingSystem, Log,
|
|
TEXT("Illumination: %s total\n")
|
|
TEXT(" %3.1f%%\t%8.1fs Untracked time\n")
|
|
, *FPlatformTime::PrettyTime(LightmassStatistics.TotalTime)
|
|
, UntrackedTime / LightmassStatistics.TotalTime * 100.0
|
|
, UntrackedTime
|
|
);
|
|
UE_LOG(LogStaticLightingSystem, Log,
|
|
TEXT("Breakdown of Illumination time\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tStarting up\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tCollecting\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \t--> Preparing lights\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \t--> Gathering lighting info\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tProcessing\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tImporting\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tApplying\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tEncoding\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tInvalidating\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tFinishing\n")
|
|
, LightmassStatistics.StartupTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.StartupTime
|
|
, LightmassStatistics.CollectTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.CollectTime
|
|
, LightmassStatistics.PrepareLightsTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.PrepareLightsTime
|
|
, LightmassStatistics.GatherLightingInfoTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.GatherLightingInfoTime
|
|
, LightmassStatistics.ProcessingTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ProcessingTime
|
|
, LightmassStatistics.ImportTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ImportTime
|
|
, LightmassStatistics.ApplyTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ApplyTime
|
|
, LightmassStatistics.EncodingTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.EncodingTime
|
|
, LightmassStatistics.InvalidationTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.InvalidationTime
|
|
, LightmassStatistics.FinishingTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.FinishingTime
|
|
);
|
|
UE_LOG(LogStaticLightingSystem, Log,
|
|
TEXT("Breakdown of Processing time\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tCollecting Lightmass scene\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tExporting\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tLightmass\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tSwarm startup\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tSwarm callback\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tSwarm job open\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tSwarm job close\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tImporting\n")
|
|
TEXT(" %3.1f%%\t%8.1fs \tApplying\n")
|
|
, LightmassStatistics.CollectLightmassSceneTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.CollectLightmassSceneTime
|
|
, LightmassStatistics.ExportTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ExportTime
|
|
, LightmassStatistics.LightmassTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.LightmassTime
|
|
, LightmassStatistics.SwarmStartupTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.SwarmStartupTime
|
|
, LightmassStatistics.SwarmCallbackTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.SwarmCallbackTime
|
|
, LightmassStatistics.SwarmJobOpenTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.SwarmJobOpenTime
|
|
, LightmassStatistics.SwarmJobCloseTime / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.SwarmJobCloseTime
|
|
, LightmassStatistics.ImportTimeInProcessing / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ImportTimeInProcessing
|
|
, LightmassStatistics.ApplyTimeInProcessing / LightmassStatistics.TotalTime * 100.0
|
|
, LightmassStatistics.ApplyTimeInProcessing
|
|
);
|
|
|
|
UE_LOG(LogStaticLightingSystem, Log,
|
|
TEXT("Breakdown of Export Times\n")
|
|
TEXT(" %8.1fs\tVisibility Data\n")
|
|
TEXT(" %8.1fs\tVolumetricLightmap Data\n")
|
|
TEXT(" %8.1fs\tLights\n")
|
|
TEXT(" %8.1fs\tModels\n")
|
|
TEXT(" %8.1fs\tStatic Meshes\n")
|
|
TEXT(" %8.1fs\tMaterials\n")
|
|
TEXT(" %8.1fs\tMesh Instances\n")
|
|
TEXT(" %8.1fs\tLandscape Instances\n")
|
|
TEXT(" %8.1fs\tMappings\n")
|
|
, LightmassStatistics.ExportVisibilityDataTime
|
|
, LightmassStatistics.ExportVolumetricLightmapDataTime
|
|
, LightmassStatistics.ExportLightsTime
|
|
, LightmassStatistics.ExportModelsTime
|
|
, LightmassStatistics.ExportStaticMeshesTime
|
|
, LightmassStatistics.ExportMaterialsTime
|
|
, LightmassStatistics.ExportMeshInstancesTime
|
|
, LightmassStatistics.ExportLandscapeInstancesTime
|
|
, LightmassStatistics.ExportMappingsTime
|
|
);
|
|
|
|
UE_LOG(LogStaticLightingSystem, Log,
|
|
TEXT("Scratch counters\n")
|
|
TEXT(" %3.1f%%\tScratch0\n")
|
|
TEXT(" %3.1f%%\tScratch1\n")
|
|
TEXT(" %3.1f%%\tScratch2\n")
|
|
TEXT(" %3.1f%%\tScratch3\n")
|
|
, LightmassStatistics.Scratch0
|
|
, LightmassStatistics.Scratch1
|
|
, LightmassStatistics.Scratch2
|
|
, LightmassStatistics.Scratch3
|
|
);
|
|
|
|
float NumLightmapTotalTexels = float(FMath::Max<uint64>(GNumLightmapTotalTexels,1));
|
|
float NumShadowmapTotalTexels = float(FMath::Max<uint64>(GNumShadowmapTotalTexels,1));
|
|
float LightmapTexelsToMT = float(NUM_HQ_LIGHTMAP_COEF)/float(NUM_STORED_LIGHTMAP_COEF)/1024.0f/1024.0f; // Strip out the SimpleLightMap
|
|
float ShadowmapTexelsToMT = 1.0f/1024.0f/1024.0f;
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Lightmap textures: %.1f M texels (%.1f%% mapped, %.1f%% unmapped, %.1f%% wasted by packing, %.1f M non-pow2 texels)")
|
|
, NumLightmapTotalTexels * LightmapTexelsToMT
|
|
, 100.0f * float(GNumLightmapMappedTexels) / NumLightmapTotalTexels
|
|
, 100.0f * float(GNumLightmapUnmappedTexels) / NumLightmapTotalTexels
|
|
, 100.0f * float(GNumLightmapTotalTexels - GNumLightmapMappedTexels - GNumLightmapUnmappedTexels) / NumLightmapTotalTexels
|
|
, GNumLightmapTotalTexelsNonPow2 * LightmapTexelsToMT
|
|
);
|
|
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Shadowmap textures: %.1f M texels (%.1f%% mapped, %.1f%% unmapped, %.1f%% wasted by packing)")
|
|
, NumShadowmapTotalTexels * ShadowmapTexelsToMT
|
|
, 100.0f * float(GNumShadowmapMappedTexels) / NumShadowmapTotalTexels
|
|
, 100.0f * float(GNumShadowmapUnmappedTexels) / NumShadowmapTotalTexels
|
|
, 100.0f * float(GNumShadowmapTotalTexels - GNumShadowmapMappedTexels - GNumShadowmapUnmappedTexels) / NumShadowmapTotalTexels
|
|
);
|
|
|
|
for ( int32 LevelIndex=0; LevelIndex < World->GetNumLevels(); LevelIndex++ )
|
|
{
|
|
ULevel* Level = World->GetLevel(LevelIndex);
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Level %2d - Lightmaps: %.1f MB. Shadowmaps: %.1f MB."), LevelIndex, Level->LightmapTotalSize/1024.0f, Level->ShadowmapTotalSize/1024.0f );
|
|
}
|
|
}
|
|
else //if ( GLightmassStatsMode)
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Illumination: %s (%s encoding lightmaps, %s encoding shadowmaps)"), *FPlatformTime::PrettyTime(LightmassStatistics.TotalTime), *FPlatformTime::PrettyTime(LightmassStatistics.EncodingLightmapsTime), *FPlatformTime::PrettyTime(LightmassStatistics.EncodingShadowMapsTime));
|
|
}
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Lightmap texture memory: %.1f MB (%.1f MB streaming, %.1f MB non-streaming), %d textures"),
|
|
GLightmapTotalSize/1024.0f/1024.0f,
|
|
GLightmapTotalStreamingSize/1024.0f/1024.0f,
|
|
(GLightmapTotalSize - GLightmapTotalStreamingSize)/1024.0f/1024.0f,
|
|
GNumLightmapTextures);
|
|
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Shadowmap texture memory: %.1f MB (%.1f MB streaming, %.1f MB non-streaming), %d textures"),
|
|
GShadowmapTotalSize/1024.0f/1024.0f,
|
|
GShadowmapTotalStreamingSize/1024.0f/1024.0f,
|
|
(GShadowmapTotalSize - GShadowmapTotalStreamingSize)/1024.0f/1024.0f,
|
|
GNumShadowmapTextures);
|
|
}
|
|
|
|
void FStaticLightingSystem::CompleteDeterministicMappings(class FLightmassProcessor* InLightmassProcessor)
|
|
{
|
|
check(InLightmassProcessor != NULL);
|
|
if (InLightmassProcessor && GLightmassDebugOptions.bUseImmediateImport && GLightmassDebugOptions.bImmediateProcessMappings)
|
|
{
|
|
// Already completed in the Lightmass Run function...
|
|
return;
|
|
}
|
|
|
|
double ImportAndApplyStartTime = FPlatformTime::Seconds();
|
|
double ApplyTime = 0.0;
|
|
|
|
int32 CurrentStep = Mappings.Num();
|
|
int32 TotalSteps = Mappings.Num() * 2;
|
|
const int32 ProgressUpdateFrequency = FMath::Max<int32>(TotalSteps / 20, 1);
|
|
GWarn->StatusUpdate( CurrentStep, TotalSteps, LOCTEXT("CompleteDeterministicMappingsStatusMessage", "Importing and applying deterministic mappings...") );
|
|
|
|
// Process all the texture mappings first...
|
|
for (int32 MappingIndex = 0; MappingIndex < Mappings.Num(); MappingIndex++)
|
|
{
|
|
FStaticLightingTextureMapping* TextureMapping = Mappings[MappingIndex]->GetTextureMapping();
|
|
if (TextureMapping)
|
|
{
|
|
//UE_LOG(LogStaticLightingSystem, Log, TEXT("%32s Completed - %s"), *(TextureMapping->GetDescription()), *(TextureMapping->GetLightingGuid().ToString()));
|
|
|
|
if (!GLightmassDebugOptions.bUseImmediateImport)
|
|
{
|
|
InLightmassProcessor->ImportMapping(TextureMapping->GetLightingGuid(), true);
|
|
}
|
|
else
|
|
{
|
|
double ApplyStartTime = FPlatformTime::Seconds();
|
|
InLightmassProcessor->ProcessMapping(TextureMapping->GetLightingGuid());
|
|
ApplyTime += FPlatformTime::Seconds() - ApplyStartTime;
|
|
}
|
|
|
|
CurrentStep++;
|
|
|
|
if (CurrentStep % ProgressUpdateFrequency == 0)
|
|
{
|
|
GWarn->UpdateProgress(CurrentStep , TotalSteps);
|
|
}
|
|
}
|
|
}
|
|
|
|
LightmassStatistics.ImportTimeInProcessing += FPlatformTime::Seconds() - ImportAndApplyStartTime - ApplyTime;
|
|
LightmassStatistics.ApplyTimeInProcessing += ApplyTime;
|
|
}
|
|
|
|
struct FCompareByArrayCount
|
|
{
|
|
FORCEINLINE bool operator()( const TArray<ULightComponent*>& A, const TArray<ULightComponent*>& B ) const
|
|
{
|
|
// Sort by descending array count
|
|
return B.Num() < A.Num();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generates mappings/meshes for all BSP in the given level
|
|
*
|
|
* @param Level Level to build BSP lighting info for
|
|
* @param bBuildLightingForBSP If true, we need BSP mappings generated as well as the meshes
|
|
*/
|
|
void FStaticLightingSystem::AddBSPStaticLightingInfo(ULevel* Level, bool bBuildLightingForBSP)
|
|
{
|
|
// For BSP, we aren't Component-centric, so we can't use the GetStaticLightingInfo
|
|
// function effectively. Instead, we look across all nodes in the Level's model and
|
|
// generate NodeGroups - which are groups of nodes that are coplanar, adjacent, and
|
|
// have the same lightmap resolution (henceforth known as being "conodes"). Each
|
|
// NodeGroup will get a mapping created for it
|
|
|
|
// cache the model
|
|
UModel* Model = Level->Model;
|
|
|
|
// reset the number of incomplete groups
|
|
Model->NumIncompleteNodeGroups = 0;
|
|
Model->CachedMappings.Empty();
|
|
Model->bInvalidForStaticLighting = false;
|
|
|
|
// create all NodeGroups
|
|
Model->GroupAllNodes(Level, Lights);
|
|
|
|
// now we need to make the mappings/meshes
|
|
bool bMarkLevelDirty = false;
|
|
for (TMap<int32, FNodeGroup*>::TIterator It(Model->NodeGroups); It; ++It)
|
|
{
|
|
FNodeGroup* NodeGroup = It.Value();
|
|
|
|
if (NodeGroup->Nodes.Num())
|
|
{
|
|
// get one of the surfaces/components from the NodeGroup
|
|
// @todo UE4: Remove need for GetSurfaceLightMapResolution to take a surfaceindex, or a ModelComponent :)
|
|
UModelComponent* SomeModelComponent = Level->ModelComponents[Model->Nodes[NodeGroup->Nodes[0]].ComponentIndex];
|
|
int32 SurfaceIndex = Model->Nodes[NodeGroup->Nodes[0]].iSurf;
|
|
|
|
// fill out the NodeGroup/mapping, as UModelComponent::GetStaticLightingInfo did
|
|
SomeModelComponent->GetSurfaceLightMapResolution(SurfaceIndex, true, NodeGroup->SizeX, NodeGroup->SizeY, NodeGroup->WorldToMap, &NodeGroup->Nodes);
|
|
|
|
// Make sure mapping will have valid size
|
|
NodeGroup->SizeX = FMath::Max(NodeGroup->SizeX, 1);
|
|
NodeGroup->SizeY = FMath::Max(NodeGroup->SizeY, 1);
|
|
|
|
NodeGroup->MapToWorld = NodeGroup->WorldToMap.InverseFast();
|
|
|
|
// Cache the surface's vertices and triangles.
|
|
NodeGroup->BoundingBox.Init();
|
|
|
|
TArray<int32> ComponentVisibilityIds;
|
|
for(int32 NodeIndex = 0;NodeIndex < NodeGroup->Nodes.Num();NodeIndex++)
|
|
{
|
|
const FBspNode& Node = Model->Nodes[NodeGroup->Nodes[NodeIndex]];
|
|
const FBspSurf& NodeSurf = Model->Surfs[Node.iSurf];
|
|
const FVector& TextureBase = Model->Points[NodeSurf.pBase];
|
|
const FVector& TextureX = Model->Vectors[NodeSurf.vTextureU];
|
|
const FVector& TextureY = Model->Vectors[NodeSurf.vTextureV];
|
|
const int32 BaseVertexIndex = NodeGroup->Vertices.Num();
|
|
// Compute the surface's tangent basis.
|
|
FVector NodeTangentX = Model->Vectors[NodeSurf.vTextureU].GetSafeNormal();
|
|
FVector NodeTangentY = Model->Vectors[NodeSurf.vTextureV].GetSafeNormal();
|
|
FVector NodeTangentZ = Model->Vectors[NodeSurf.vNormal].GetSafeNormal();
|
|
|
|
// Generate the node's vertices.
|
|
for(uint32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++)
|
|
{
|
|
const FVert& Vert = Model->Verts[Node.iVertPool + VertexIndex];
|
|
const FVector& VertexWorldPosition = Model->Points[Vert.pVertex];
|
|
|
|
FStaticLightingVertex* DestVertex = new(NodeGroup->Vertices) FStaticLightingVertex;
|
|
DestVertex->WorldPosition = VertexWorldPosition;
|
|
DestVertex->TextureCoordinates[0].X = ((VertexWorldPosition - TextureBase) | TextureX) / UModel::GetGlobalBSPTexelScale();
|
|
DestVertex->TextureCoordinates[0].Y = ((VertexWorldPosition - TextureBase) | TextureY) / UModel::GetGlobalBSPTexelScale();
|
|
DestVertex->TextureCoordinates[1].X = NodeGroup->WorldToMap.TransformPosition(VertexWorldPosition).X;
|
|
DestVertex->TextureCoordinates[1].Y = NodeGroup->WorldToMap.TransformPosition(VertexWorldPosition).Y;
|
|
DestVertex->WorldTangentX = NodeTangentX;
|
|
DestVertex->WorldTangentY = NodeTangentY;
|
|
DestVertex->WorldTangentZ = NodeTangentZ;
|
|
|
|
// Include the vertex in the surface's bounding box.
|
|
NodeGroup->BoundingBox += VertexWorldPosition;
|
|
}
|
|
|
|
// Generate the node's vertex indices.
|
|
for(uint32 VertexIndex = 2;VertexIndex < Node.NumVertices;VertexIndex++)
|
|
{
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + 0);
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + VertexIndex);
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + VertexIndex - 1);
|
|
|
|
// track the source surface for each triangle
|
|
NodeGroup->TriangleSurfaceMap.Add(Node.iSurf);
|
|
}
|
|
|
|
UModelComponent* Component = Level->ModelComponents[Node.ComponentIndex];
|
|
if (Component->VisibilityId == INDEX_NONE)
|
|
{
|
|
if (World->GetWorldSettings()->bPrecomputeVisibility)
|
|
{
|
|
// Make sure the level gets dirtied since we are changing the visibility Id of a component in it
|
|
bMarkLevelDirty = true;
|
|
}
|
|
Component->VisibilityId = NextVisibilityId;
|
|
NextVisibilityId++;
|
|
}
|
|
ComponentVisibilityIds.AddUnique(Component->VisibilityId);
|
|
}
|
|
|
|
// Continue only if the component accepts lights (all components in a node group have the same value)
|
|
// TODO: If we expose CastShadow for BSP in the future, reenable this condition and make sure
|
|
// node grouping logic is updated to account for CastShadow as well
|
|
//if (SomeModelComponent->bAcceptsLights || SomeModelComponent->CastShadow)
|
|
{
|
|
// Create the object to represent the surface's mapping/mesh to the static lighting system,
|
|
// the model is now the owner, and all nodes have the same
|
|
FBSPSurfaceStaticLighting* SurfaceStaticLighting = new FBSPSurfaceStaticLighting(NodeGroup, Model, SomeModelComponent);
|
|
// Give the surface mapping the visibility Id's of all components that have nodes in it
|
|
// This results in fairly ineffective precomputed visibility with BSP but is necessary since BSP mappings contain geometry from multiple components
|
|
SurfaceStaticLighting->VisibilityIds = ComponentVisibilityIds;
|
|
|
|
Meshes.Add(SurfaceStaticLighting);
|
|
LightingMeshBounds += SurfaceStaticLighting->BoundingBox;
|
|
|
|
if (SomeModelComponent->CastShadow)
|
|
{
|
|
UpdateAutomaticImportanceVolumeBounds( SurfaceStaticLighting->BoundingBox );
|
|
}
|
|
|
|
FStaticLightingMapping* CurrentMapping = SurfaceStaticLighting;
|
|
if (GLightmassDebugOptions.bSortMappings)
|
|
{
|
|
int32 InsertIndex = UnSortedMappings.AddZeroed();
|
|
FStaticLightingMappingSortHelper& Helper = UnSortedMappings[InsertIndex];
|
|
Helper.Mapping = CurrentMapping;
|
|
Helper.NumTexels = CurrentMapping->GetTexelCount();
|
|
}
|
|
else
|
|
{
|
|
Mappings.Add(CurrentMapping);
|
|
if (bBuildLightingForBSP)
|
|
{
|
|
CurrentMapping->Mesh->Guid = FGuid(0,0,0,DeterministicIndex++);
|
|
}
|
|
}
|
|
|
|
if (bBuildLightingForBSP)
|
|
{
|
|
CurrentMapping->bProcessMapping = true;
|
|
}
|
|
|
|
// count how many node groups have yet to come back as complete
|
|
Model->NumIncompleteNodeGroups++;
|
|
|
|
// add this mapping to the list of mappings to be applied later
|
|
Model->CachedMappings.Add(SurfaceStaticLighting);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bMarkLevelDirty)
|
|
{
|
|
Level->MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates mappings/meshes for the given NodeGroups
|
|
*
|
|
* @param Level Level to build BSP lighting info for
|
|
* @param NodeGroupsToBuild The node groups to build the BSP lighting info for
|
|
*/
|
|
void FStaticLightingSystem::AddBSPStaticLightingInfo(ULevel* Level, TArray<FNodeGroup*>& NodeGroupsToBuild)
|
|
{
|
|
// For BSP, we aren't Component-centric, so we can't use the GetStaticLightingInfo
|
|
// function effectively. Instead, we look across all nodes in the Level's model and
|
|
// generate NodeGroups - which are groups of nodes that are coplanar, adjacent, and
|
|
// have the same lightmap resolution (henceforth known as being "conodes"). Each
|
|
// NodeGroup will get a mapping created for it
|
|
|
|
// cache the model
|
|
UModel* Model = Level->Model;
|
|
|
|
// reset the number of incomplete groups
|
|
Model->NumIncompleteNodeGroups = 0;
|
|
Model->CachedMappings.Empty();
|
|
Model->bInvalidForStaticLighting = false;
|
|
|
|
// now we need to make the mappings/meshes
|
|
for (int32 NodeGroupIdx = 0; NodeGroupIdx < NodeGroupsToBuild.Num(); NodeGroupIdx++)
|
|
{
|
|
FNodeGroup* NodeGroup = NodeGroupsToBuild[NodeGroupIdx];
|
|
if (NodeGroup && NodeGroup->Nodes.Num())
|
|
{
|
|
// get one of the surfaces/components from the NodeGroup
|
|
// @todo UE4: Remove need for GetSurfaceLightMapResolution to take a surfaceindex, or a ModelComponent :)
|
|
UModelComponent* SomeModelComponent = Level->ModelComponents[Model->Nodes[NodeGroup->Nodes[0]].ComponentIndex];
|
|
int32 SurfaceIndex = Model->Nodes[NodeGroup->Nodes[0]].iSurf;
|
|
|
|
// fill out the NodeGroup/mapping, as UModelComponent::GetStaticLightingInfo did
|
|
SomeModelComponent->GetSurfaceLightMapResolution(SurfaceIndex, true, NodeGroup->SizeX, NodeGroup->SizeY, NodeGroup->WorldToMap, &NodeGroup->Nodes);
|
|
NodeGroup->MapToWorld = NodeGroup->WorldToMap.InverseFast();
|
|
|
|
// Cache the surface's vertices and triangles.
|
|
NodeGroup->BoundingBox.Init();
|
|
|
|
for(int32 NodeIndex = 0;NodeIndex < NodeGroup->Nodes.Num();NodeIndex++)
|
|
{
|
|
const FBspNode& Node = Model->Nodes[NodeGroup->Nodes[NodeIndex]];
|
|
const FBspSurf& NodeSurf = Model->Surfs[Node.iSurf];
|
|
const FVector& TextureBase = Model->Points[NodeSurf.pBase];
|
|
const FVector& TextureX = Model->Vectors[NodeSurf.vTextureU];
|
|
const FVector& TextureY = Model->Vectors[NodeSurf.vTextureV];
|
|
const int32 BaseVertexIndex = NodeGroup->Vertices.Num();
|
|
// Compute the surface's tangent basis.
|
|
FVector NodeTangentX = Model->Vectors[NodeSurf.vTextureU].GetSafeNormal();
|
|
FVector NodeTangentY = Model->Vectors[NodeSurf.vTextureV].GetSafeNormal();
|
|
FVector NodeTangentZ = Model->Vectors[NodeSurf.vNormal].GetSafeNormal();
|
|
|
|
// Generate the node's vertices.
|
|
for(uint32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++)
|
|
{
|
|
const FVert& Vert = Model->Verts[Node.iVertPool + VertexIndex];
|
|
const FVector& VertexWorldPosition = Model->Points[Vert.pVertex];
|
|
|
|
FStaticLightingVertex* DestVertex = new(NodeGroup->Vertices) FStaticLightingVertex;
|
|
DestVertex->WorldPosition = VertexWorldPosition;
|
|
DestVertex->TextureCoordinates[0].X = ((VertexWorldPosition - TextureBase) | TextureX) / UModel::GetGlobalBSPTexelScale();
|
|
DestVertex->TextureCoordinates[0].Y = ((VertexWorldPosition - TextureBase) | TextureY) / UModel::GetGlobalBSPTexelScale();
|
|
DestVertex->TextureCoordinates[1].X = NodeGroup->WorldToMap.TransformPosition(VertexWorldPosition).X;
|
|
DestVertex->TextureCoordinates[1].Y = NodeGroup->WorldToMap.TransformPosition(VertexWorldPosition).Y;
|
|
DestVertex->WorldTangentX = NodeTangentX;
|
|
DestVertex->WorldTangentY = NodeTangentY;
|
|
DestVertex->WorldTangentZ = NodeTangentZ;
|
|
|
|
// Include the vertex in the surface's bounding box.
|
|
NodeGroup->BoundingBox += VertexWorldPosition;
|
|
}
|
|
|
|
// Generate the node's vertex indices.
|
|
for(uint32 VertexIndex = 2;VertexIndex < Node.NumVertices;VertexIndex++)
|
|
{
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + 0);
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + VertexIndex);
|
|
NodeGroup->TriangleVertexIndices.Add(BaseVertexIndex + VertexIndex - 1);
|
|
|
|
// track the source surface for each triangle
|
|
NodeGroup->TriangleSurfaceMap.Add(Node.iSurf);
|
|
}
|
|
}
|
|
|
|
// Continue only if the component accepts lights (all components in a node group have the same value)
|
|
// TODO: If we expose CastShadow for BSP in the future, reenable this condition and make sure
|
|
// node grouping logic is updated to account for CastShadow as well
|
|
//if (SomeModelComponent->bAcceptsLights || SomeModelComponent->CastShadow)
|
|
{
|
|
// Create the object to represent the surface's mapping/mesh to the static lighting system,
|
|
// the model is now the owner, and all nodes have the same
|
|
FBSPSurfaceStaticLighting* SurfaceStaticLighting = new FBSPSurfaceStaticLighting(NodeGroup, Model, SomeModelComponent);
|
|
Meshes.Add(SurfaceStaticLighting);
|
|
LightingMeshBounds += SurfaceStaticLighting->BoundingBox;
|
|
|
|
if (SomeModelComponent->CastShadow)
|
|
{
|
|
UpdateAutomaticImportanceVolumeBounds( SurfaceStaticLighting->BoundingBox );
|
|
}
|
|
|
|
FStaticLightingMapping* CurrentMapping = SurfaceStaticLighting;
|
|
if (GLightmassDebugOptions.bSortMappings)
|
|
{
|
|
int32 InsertIndex = UnSortedMappings.AddZeroed();
|
|
FStaticLightingMappingSortHelper& Helper = UnSortedMappings[InsertIndex];
|
|
Helper.Mapping = CurrentMapping;
|
|
Helper.NumTexels = CurrentMapping->GetTexelCount();
|
|
}
|
|
else
|
|
{
|
|
Mappings.Add(CurrentMapping);
|
|
CurrentMapping->Mesh->Guid = FGuid(0,0,0,DeterministicIndex++);
|
|
}
|
|
|
|
CurrentMapping->bProcessMapping = true;
|
|
|
|
// count how many node groups have yet to come back as complete
|
|
Model->NumIncompleteNodeGroups++;
|
|
|
|
// add this mapping to the list of mappings to be applied later
|
|
Model->CachedMappings.Add(SurfaceStaticLighting);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FStaticLightingSystem::AddPrimitiveStaticLightingInfo(FStaticLightingPrimitiveInfo& PrimitiveInfo, bool bBuildActorLighting)
|
|
{
|
|
// Verify a one to one relationship between mappings and meshes
|
|
//@todo - merge FStaticLightingMesh and FStaticLightingMapping
|
|
check(PrimitiveInfo.Meshes.Num() == PrimitiveInfo.Mappings.Num());
|
|
|
|
// Add the component's shadow casting meshes to the system.
|
|
for(int32 MeshIndex = 0;MeshIndex < PrimitiveInfo.Meshes.Num();MeshIndex++)
|
|
{
|
|
FStaticLightingMesh* Mesh = PrimitiveInfo.Meshes[MeshIndex];
|
|
if (Mesh)
|
|
{
|
|
Mesh->VisibilityIds.Add(PrimitiveInfo.VisibilityId);
|
|
if (!GLightmassDebugOptions.bSortMappings && bBuildActorLighting)
|
|
{
|
|
Mesh->Guid = FGuid(0, 0, 0, DeterministicIndex++);
|
|
}
|
|
Meshes.Add(Mesh);
|
|
LightingMeshBounds += Mesh->BoundingBox;
|
|
|
|
if (Mesh->bCastShadow)
|
|
{
|
|
UpdateAutomaticImportanceVolumeBounds(Mesh->BoundingBox);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If lighting is being built for this component, add its mappings to the system.
|
|
for(int32 MappingIndex = 0;MappingIndex < PrimitiveInfo.Mappings.Num();MappingIndex++)
|
|
{
|
|
FStaticLightingMapping* CurrentMapping = PrimitiveInfo.Mappings[MappingIndex];
|
|
if (GbLogAddingMappings)
|
|
{
|
|
FStaticLightingMesh* SLMesh = CurrentMapping->Mesh;
|
|
if (SLMesh)
|
|
{
|
|
//UE_LOG(LogStaticLightingSystem, Log, TEXT("Adding %32s: 0x%08p - %s"), *(CurrentMapping->GetDescription()), (PTRINT)(SLMesh->Component), *(SLMesh->Guid.ToString()));
|
|
}
|
|
else
|
|
{
|
|
//UE_LOG(LogStaticLightingSystem, Log, TEXT("Adding %32s: 0x%08x - %s"), *(CurrentMapping->GetDescription()), 0, TEXT("NO MESH????"));
|
|
}
|
|
}
|
|
|
|
if (bBuildActorLighting)
|
|
{
|
|
CurrentMapping->bProcessMapping = true;
|
|
}
|
|
|
|
if (GLightmassDebugOptions.bSortMappings)
|
|
{
|
|
int32 InsertIndex = UnSortedMappings.AddZeroed();
|
|
FStaticLightingMappingSortHelper& Helper = UnSortedMappings[InsertIndex];
|
|
Helper.Mapping = CurrentMapping;
|
|
Helper.NumTexels = Helper.Mapping->GetTexelCount();
|
|
}
|
|
else
|
|
{
|
|
Mappings.Add(CurrentMapping);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FStaticLightingSystem::CreateLightmassProcessor()
|
|
{
|
|
FLightmassStatistics::FScopedGather SwarmStartStatScope(LightmassProcessStatistics.SwarmStartupTime);
|
|
|
|
GWarn->StatusForceUpdate( -1, -1, LOCTEXT("StartingSwarmConnectionStatus", "Starting up Swarm Connection...") );
|
|
|
|
if (Options.bOnlyBuildVisibility && !World->GetWorldSettings()->bPrecomputeVisibility)
|
|
{
|
|
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "BuildFailed_VisibilityOnlyButVisibilityDisabled", "'Build Only Visibility' option was enabled but precomputed visibility is disabled! Aborting build."));
|
|
return false;
|
|
}
|
|
|
|
NSwarm::FSwarmInterface::Initialize(*(FString(FPlatformProcess::BaseDir()) + TEXT("..\\DotNET\\SwarmInterface.dll")));
|
|
|
|
// Create the processor
|
|
check(LightmassProcessor == NULL);
|
|
LightmassProcessor = new FLightmassProcessor(*this, Options.bDumpBinaryResults, Options.bOnlyBuildVisibility);
|
|
check(LightmassProcessor);
|
|
if (LightmassProcessor->IsSwarmConnectionIsValid() == false)
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("Failed to connect to Swarm."));
|
|
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("FailedToConnectToSwarmDialogMessage", "Failed to connect to Swarm."));
|
|
delete LightmassProcessor;
|
|
LightmassProcessor = NULL;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FStaticLightingSystem::GatherScene()
|
|
{
|
|
LightmassProcessStatistics = FLightmassStatistics();
|
|
|
|
GWarn->StatusUpdate( 0, Meshes.Num() + Mappings.Num(), LOCTEXT("GatherSceneStatusMessage", "Collecting the scene...") );
|
|
|
|
FLightmassStatistics::FScopedGather SceneStatScope(LightmassProcessStatistics.CollectLightmassSceneTime);
|
|
|
|
// Grab the exporter and fill in the meshes
|
|
//@todo. This should be exported to the 'processor' as it will be used on the input side as well...
|
|
FLightmassExporter* LightmassExporter = LightmassProcessor->GetLightmassExporter();
|
|
check(LightmassExporter);
|
|
|
|
// The Level settings...
|
|
AWorldSettings* WorldSettings = World->GetWorldSettings();
|
|
if (WorldSettings)
|
|
{
|
|
LightmassExporter->SetLevelSettings(WorldSettings->LightmassSettings);
|
|
}
|
|
else
|
|
{
|
|
FLightmassWorldInfoSettings TempSettings;
|
|
LightmassExporter->SetLevelSettings(TempSettings);
|
|
}
|
|
LightmassExporter->SetNumUnusedLocalCores(Options.NumUnusedLocalCores);
|
|
LightmassExporter->SetQualityLevel(Options.QualityLevel);
|
|
|
|
if (World->PersistentLevel && Options.ShouldBuildLightingForLevel( World->PersistentLevel ))
|
|
{
|
|
LightmassExporter->SetLevelName(World->PersistentLevel->GetPathName());
|
|
}
|
|
|
|
LightmassExporter->ClearImportanceVolumes();
|
|
for( TObjectIterator<ALightmassImportanceVolume> It ; It ; ++It )
|
|
{
|
|
ALightmassImportanceVolume* LMIVolume = *It;
|
|
if (World->ContainsActor(LMIVolume) && !LMIVolume->IsPendingKill() && ShouldOperateOnLevel(LMIVolume->GetLevel()))
|
|
{
|
|
LightmassExporter->AddImportanceVolume(LMIVolume);
|
|
}
|
|
}
|
|
|
|
for( TObjectIterator<ALightmassCharacterIndirectDetailVolume> It ; It ; ++It )
|
|
{
|
|
ALightmassCharacterIndirectDetailVolume* LMDetailVolume = *It;
|
|
if (World->ContainsActor(LMDetailVolume) && !LMDetailVolume->IsPendingKill() && ShouldOperateOnLevel(LMDetailVolume->GetLevel()))
|
|
{
|
|
LightmassExporter->AddCharacterIndirectDetailVolume(LMDetailVolume);
|
|
}
|
|
}
|
|
|
|
for( TObjectIterator<ULightmassPortalComponent> It ; It ; ++It )
|
|
{
|
|
ULightmassPortalComponent* LMPortal = *It;
|
|
if (LMPortal->GetOwner() && World->ContainsActor(LMPortal->GetOwner()) && !LMPortal->IsPendingKill() && ShouldOperateOnLevel(LMPortal->GetOwner()->GetLevel()))
|
|
{
|
|
LightmassExporter->AddPortal(LMPortal);
|
|
}
|
|
}
|
|
|
|
float MinimumImportanceVolumeExtentWithoutWarning = 0.0f;
|
|
verify(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("MinimumImportanceVolumeExtentWithoutWarning"), MinimumImportanceVolumeExtentWithoutWarning, GLightmassIni));
|
|
|
|
// If we have no importance volumes, then we'll synthesize one now. A scene without any importance volumes will not yield
|
|
// expected lighting results, so it's important to have a volume to pass to Lightmass.
|
|
if (LightmassExporter->GetImportanceVolumes().Num() == 0)
|
|
{
|
|
FBox ReasonableSceneBounds = AutomaticImportanceVolumeBounds;
|
|
if (ReasonableSceneBounds.GetExtent().SizeSquared() > (MinimumImportanceVolumeExtentWithoutWarning * MinimumImportanceVolumeExtentWithoutWarning))
|
|
{
|
|
// Emit a serious warning to the user about performance.
|
|
FMessageLog("LightingResults").PerformanceWarning(LOCTEXT("LightmassError_MissingImportanceVolume", "No importance volume found and the scene is so large that the automatically synthesized volume will not yield good results. Please add a tightly bounding lightmass importance volume to optimize your scene's quality and lighting build times."));
|
|
|
|
// Clamp the size of the importance volume we create to a reasonable size
|
|
ReasonableSceneBounds = FBox(ReasonableSceneBounds.GetCenter() - MinimumImportanceVolumeExtentWithoutWarning, ReasonableSceneBounds.GetCenter() + MinimumImportanceVolumeExtentWithoutWarning);
|
|
}
|
|
else
|
|
{
|
|
// The scene isn't too big, so we'll use the scene's bounds as a synthetic importance volume
|
|
// NOTE: We don't want to pop up a message log for this common case when creating a new level, so we just spray a log message. It's not very important to a user.
|
|
UE_LOG(LogStaticLightingSystem, Warning, TEXT("No importance volume found, so the scene bounding box was used. You can optimize your scene's quality and lighting build times by adding importance volumes."));
|
|
|
|
float AutomaticImportanceVolumeExpandBy = 0.0f;
|
|
verify(GConfig->GetFloat(TEXT("DevOptions.StaticLightingSceneConstants"), TEXT("AutomaticImportanceVolumeExpandBy"), AutomaticImportanceVolumeExpandBy, GLightmassIni));
|
|
|
|
// Expand the scene's bounds a bit to make sure volume lighting samples placed on surfaces are inside
|
|
ReasonableSceneBounds = ReasonableSceneBounds.ExpandBy(AutomaticImportanceVolumeExpandBy);
|
|
}
|
|
|
|
LightmassExporter->AddImportanceVolumeBoundingBox(ReasonableSceneBounds);
|
|
}
|
|
|
|
const int32 NumMeshesAndMappings = Meshes.Num() + Mappings.Num();
|
|
const int32 ProgressUpdateFrequency = FMath::Max<int32>(NumMeshesAndMappings / 20, 1);
|
|
|
|
// Meshes
|
|
for( int32 MeshIdx=0; !GEditor->GetMapBuildCancelled() && MeshIdx < Meshes.Num(); MeshIdx++ )
|
|
{
|
|
Meshes[MeshIdx]->ExportMeshInstance(LightmassExporter);
|
|
|
|
if (MeshIdx % ProgressUpdateFrequency == 0)
|
|
{
|
|
GWarn->UpdateProgress( MeshIdx, NumMeshesAndMappings );
|
|
}
|
|
}
|
|
|
|
// Mappings
|
|
for( int32 MappingIdx=0; !GEditor->GetMapBuildCancelled() && MappingIdx < Mappings.Num(); MappingIdx++ )
|
|
{
|
|
Mappings[MappingIdx]->ExportMapping(LightmassExporter);
|
|
|
|
if (MappingIdx % ProgressUpdateFrequency == 0)
|
|
{
|
|
GWarn->UpdateProgress( Meshes.Num() + MappingIdx, NumMeshesAndMappings );
|
|
}
|
|
}
|
|
|
|
for (int32 LightIndex = 0; LightIndex < Lights.Num(); LightIndex++)
|
|
{
|
|
ULightComponentBase* LightBase = Lights[LightIndex];
|
|
USkyLightComponent* SkyLight = Cast<USkyLightComponent>(LightBase);
|
|
|
|
if (SkyLight && (SkyLight->Mobility == EComponentMobility::Static || SkyLight->Mobility == EComponentMobility::Stationary))
|
|
{
|
|
LightmassExporter->AddLight(SkyLight);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FStaticLightingSystem::InitiateLightmassProcessor()
|
|
{
|
|
// Run!
|
|
bool bSuccessful = false;
|
|
bool bOpenJobSuccessful = false;
|
|
if ( !GEditor->GetMapBuildCancelled() )
|
|
{
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Running Lightmass w/ ImmediateImport mode %s"), GLightmassDebugOptions.bUseImmediateImport ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
LightmassProcessor->SetImportCompletedMappingsImmediately(GLightmassDebugOptions.bUseImmediateImport);
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Running Lightmass w/ ImmediateProcess mode %s"), GLightmassDebugOptions.bImmediateProcessMappings ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Running Lightmass w/ Sorting mode %s"), GLightmassDebugOptions.bSortMappings ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Running Lightmass w/ Mapping paddings %s"), GLightmassDebugOptions.bPadMappings ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
UE_LOG(LogStaticLightingSystem, Log, TEXT("Running Lightmass w/ Mapping debug paddings %s"), GLightmassDebugOptions.bDebugPaddings ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather OpenJobStatScope(LightmassProcessStatistics.SwarmJobOpenTime);
|
|
bOpenJobSuccessful = LightmassProcessor->OpenJob();
|
|
}
|
|
|
|
if (bOpenJobSuccessful)
|
|
{
|
|
LightmassProcessor->InitiateExport();
|
|
bSuccessful = true;
|
|
CurrentBuildStage = FStaticLightingSystem::AmortizedExport;
|
|
}
|
|
}
|
|
|
|
return bSuccessful;
|
|
}
|
|
|
|
void FStaticLightingSystem::KickoffSwarm()
|
|
{
|
|
bool bSuccessful = LightmassProcessor->BeginRun();
|
|
|
|
if (bSuccessful)
|
|
{
|
|
CurrentBuildStage = FStaticLightingSystem::AsynchronousBuilding;
|
|
}
|
|
else
|
|
{
|
|
FStaticLightingManager::Get()->FailLightingBuild(LOCTEXT("SwarmKickoffFailedMessage", "Lighting build failed. Swarm failed to kick off. Compile Unreal Lightmass."));
|
|
}
|
|
}
|
|
|
|
bool FStaticLightingSystem::FinishLightmassProcess()
|
|
{
|
|
bool bSuccessful = false;
|
|
|
|
GEditor->ResetTransaction( LOCTEXT("KeepLightingTransReset", "Applying Lighting") );
|
|
|
|
CurrentBuildStage = FStaticLightingSystem::Import;
|
|
|
|
double TimeWaitingOnUserToAccept = FPlatformTime::Seconds() - WaitForUserAcceptStartTime;
|
|
|
|
{
|
|
FScopedSlowTask SlowTask(7);
|
|
SlowTask.MakeDialog();
|
|
|
|
SlowTask.EnterProgressFrame(1, LOCTEXT("InvalidatingPreviousLightingStatus", "Invalidating previous lighting"));
|
|
InvalidateStaticLighting();
|
|
|
|
SlowTask.EnterProgressFrame(1, LOCTEXT("ImportingBuiltStaticLightingStatus", "Importing built static lighting"));
|
|
bSuccessful = LightmassProcessor->CompleteRun();
|
|
|
|
SlowTask.EnterProgressFrame();
|
|
if (bSuccessful)
|
|
{
|
|
CompleteDeterministicMappings(LightmassProcessor);
|
|
|
|
if (!Options.bOnlyBuildVisibility)
|
|
{
|
|
FLightmassStatistics::FScopedGather FinishStatScope(LightmassStatistics.FinishingTime);
|
|
ULightComponent::ReassignStationaryLightChannels(GWorld, true, LightingScenario);
|
|
}
|
|
}
|
|
|
|
|
|
SlowTask.EnterProgressFrame(1, LOCTEXT("EncodingTexturesStaticLightingStatis", "Encoding textures"));
|
|
EncodeTextures(bSuccessful);
|
|
|
|
|
|
SlowTask.EnterProgressFrame();
|
|
{
|
|
FLightmassStatistics::FScopedGather CloseJobStatScope(LightmassProcessStatistics.SwarmJobCloseTime);
|
|
bSuccessful = LightmassProcessor->CloseJob() && bSuccessful;
|
|
}
|
|
|
|
{
|
|
FLightmassStatistics::FScopedGather FinishStatScope(LightmassStatistics.FinishingTime);
|
|
// Add in the time measurements from the LightmassProcessor
|
|
LightmassStatistics += LightmassProcessor->GetStatistics();
|
|
|
|
// A final update on the lighting build warnings and errors dialog, now that everything is finished
|
|
FMessageLog("LightingResults").Open();
|
|
|
|
// Check the for build cancellation.
|
|
bBuildCanceled = bBuildCanceled || GEditor->GetMapBuildCancelled();
|
|
bSuccessful = bSuccessful && !bBuildCanceled;
|
|
|
|
FStatsViewerModule& StatsViewerModule = FModuleManager::Get().LoadModuleChecked<FStatsViewerModule>(TEXT("StatsViewer"));
|
|
if (bSuccessful)
|
|
{
|
|
StatsViewerModule.GetPage(EStatsPage::LightingBuildInfo)->Refresh();
|
|
}
|
|
|
|
bool bShowLightingBuildInfo = false;
|
|
GConfig->GetBool( TEXT("LightingBuildOptions"), TEXT("ShowLightingBuildInfo"), bShowLightingBuildInfo, GEditorPerProjectIni );
|
|
if( bShowLightingBuildInfo )
|
|
{
|
|
StatsViewerModule.GetPage(EStatsPage::LightingBuildInfo)->Show();
|
|
}
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame();
|
|
ApplyNewLightingData(bSuccessful);
|
|
|
|
SlowTask.EnterProgressFrame();
|
|
|
|
// Finish up timing statistics
|
|
LightmassStatistics += LightmassProcessStatistics;
|
|
LightmassStatistics.TotalTime += FPlatformTime::Seconds() - StartTime - TimeWaitingOnUserToAccept;
|
|
}
|
|
|
|
ReportStatistics();
|
|
|
|
return bSuccessful;
|
|
}
|
|
|
|
void FStaticLightingSystem::UpdateLightingBuild()
|
|
{
|
|
if (CurrentBuildStage == FStaticLightingSystem::AmortizedExport)
|
|
{
|
|
bool bCompleted = LightmassProcessor->ExecuteAmortizedMaterialExport();
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("PercentDone"), FText::AsPercent( LightmassProcessor->GetAmortizedExportPercentDone() ) );
|
|
FText Text = FText::Format( LOCTEXT("LightExportProgressMessage", "Exporting lighting data: {PercentDone} Done"), Args );
|
|
|
|
FStaticLightingManager::Get()->SetNotificationText( Text );
|
|
|
|
if (bCompleted)
|
|
{
|
|
CurrentBuildStage = FStaticLightingSystem::SwarmKickoff;
|
|
}
|
|
}
|
|
else if (CurrentBuildStage == FStaticLightingSystem::SwarmKickoff)
|
|
{
|
|
FText Text = LOCTEXT("LightKickoffSwarmMessage", "Kicking off Swarm");
|
|
FStaticLightingManager::Get()->SetNotificationText( Text );
|
|
KickoffSwarm();
|
|
}
|
|
else if (CurrentBuildStage == FStaticLightingSystem::AsynchronousBuilding)
|
|
{
|
|
bool bFinished = LightmassProcessor->Update();
|
|
|
|
FString ScenarioString;
|
|
|
|
if (LightingScenario)
|
|
{
|
|
FString PackageName = FPackageName::GetShortName(LightingScenario->GetOutermost()->GetName());
|
|
ScenarioString = FString(TEXT(" for ")) + PackageName;
|
|
}
|
|
|
|
FText Text = FText::Format(LOCTEXT("LightBuildProgressMessage", "Building lighting{0}: {1}%"), FText::FromString(ScenarioString), FText::AsNumber(LightmassProcessor->GetAsyncPercentDone()));
|
|
FStaticLightingManager::Get()->SetNotificationText( Text );
|
|
|
|
if (bFinished)
|
|
{
|
|
LightmassStatistics.ProcessingTime += FPlatformTime::Seconds() - ProcessingStartTime;
|
|
WaitForUserAcceptStartTime = FPlatformTime::Seconds();
|
|
|
|
FStaticLightingManager::Get()->ClearCurrentNotification();
|
|
|
|
if (LightmassProcessor->IsProcessingCompletedSuccessfully())
|
|
{
|
|
CurrentBuildStage = FStaticLightingSystem::AutoApplyingImport;
|
|
}
|
|
else
|
|
{
|
|
// automatically fail lighting build (discard)
|
|
FStaticLightingManager::Get()->FailLightingBuild();
|
|
CurrentBuildStage = FStaticLightingSystem::Finished;
|
|
}
|
|
}
|
|
}
|
|
else if ( CurrentBuildStage == FStaticLightingSystem::AutoApplyingImport )
|
|
{
|
|
if ( CanAutoApplyLighting() || IsRunningCommandlet() )
|
|
{
|
|
bool bAutoApplyFailed = false;
|
|
FStaticLightingManager::Get()->SendBuildDoneNotification(bAutoApplyFailed);
|
|
|
|
FStaticLightingManager::ProcessLightingData();
|
|
CurrentBuildStage = FStaticLightingSystem::Finished;
|
|
}
|
|
else
|
|
{
|
|
bool bAutoApplyFailed = true;
|
|
FStaticLightingManager::Get()->SendBuildDoneNotification(bAutoApplyFailed);
|
|
|
|
CurrentBuildStage = FStaticLightingSystem::WaitingForImport;
|
|
}
|
|
}
|
|
else if (CurrentBuildStage == FStaticLightingSystem::ImportRequested)
|
|
{
|
|
FStaticLightingManager::ProcessLightingData();
|
|
CurrentBuildStage = FStaticLightingSystem::Finished;
|
|
}
|
|
}
|
|
|
|
void FStaticLightingSystem::UpdateAutomaticImportanceVolumeBounds( const FBox& MeshBounds )
|
|
{
|
|
// Note: skyboxes will be excluded if they are properly setup to not cast shadows
|
|
AutomaticImportanceVolumeBounds += MeshBounds;
|
|
}
|
|
|
|
bool FStaticLightingSystem::CanAutoApplyLighting() const
|
|
{
|
|
const bool bAutoApplyEnabled = GetDefault<ULevelEditorMiscSettings>()->bAutoApplyLightingEnable;
|
|
const bool bSlowTask = GIsSlowTask;
|
|
const bool bInterpEditMode = GLevelEditorModeTools().IsModeActive( FBuiltinEditorModes::EM_InterpEdit );
|
|
const bool bPlayWorldValid = GEditor->PlayWorld != nullptr;
|
|
const bool bAnyMenusVisible = (FSlateApplication::IsInitialized() && FSlateApplication::Get().AnyMenusVisible());
|
|
//const bool bIsInteratcting = false;// FSlateApplication::Get().GetMouseCaptor().IsValid() || GEditor->IsUserInteracting();
|
|
const bool bHasGameOrProjectLoaded = FApp::HasProjectName();
|
|
|
|
return ( bAutoApplyEnabled && !bSlowTask && !bInterpEditMode && !bPlayWorldValid && !bAnyMenusVisible/* && !bIsInteratcting */&& !GIsDemoMode && bHasGameOrProjectLoaded );
|
|
}
|
|
|
|
/**
|
|
* Clear out all the binary dump log files, so the next run will have just the needed files for rendering
|
|
*/
|
|
void FStaticLightingSystem::ClearBinaryDumps()
|
|
{
|
|
IFileManager::Get().DeleteDirectory(*FString::Printf(TEXT("%sLogs/Lighting_%s"), *FPaths::ProjectDir(), TEXT("Lightmass")), false, true);
|
|
}
|
|
|
|
/** Marks all lights used in the calculated lightmap as used in a lightmap, and calls Apply on the texture mapping. */
|
|
void FStaticLightingSystem::ApplyMapping(
|
|
FStaticLightingTextureMapping* TextureMapping,
|
|
FQuantizedLightmapData* QuantizedData,
|
|
const TMap<ULightComponent*,FShadowMapData2D*>& ShadowMapData) const
|
|
{
|
|
TextureMapping->Apply(QuantizedData, ShadowMapData, LightingScenario);
|
|
}
|
|
|
|
UWorld* FStaticLightingSystem::GetWorld() const
|
|
{
|
|
return World;
|
|
}
|
|
|
|
bool FStaticLightingSystem::IsAsyncBuilding() const
|
|
{
|
|
return CurrentBuildStage == FStaticLightingSystem::AsynchronousBuilding;
|
|
}
|
|
|
|
bool FStaticLightingSystem::IsAmortizedExporting() const
|
|
{
|
|
return CurrentBuildStage == FStaticLightingSystem::AmortizedExport;
|
|
}
|
|
|
|
void UEditorEngine::BuildLighting(const FLightingBuildOptions& Options)
|
|
{
|
|
// Forcibly shut down all texture property windows as they become invalid during a light build
|
|
FAssetEditorManager& AssetEditorManager = FAssetEditorManager::Get();
|
|
TArray<UObject*> EditedAssets = AssetEditorManager.GetAllEditedAssets();
|
|
|
|
for (int32 AssetIdx = 0; AssetIdx < EditedAssets.Num(); AssetIdx++)
|
|
{
|
|
UObject* EditedAsset = EditedAssets[AssetIdx];
|
|
|
|
if (EditedAsset->IsA(UTexture2D::StaticClass()))
|
|
{
|
|
IAssetEditorInstance* Editor = AssetEditorManager.FindEditorForAsset(EditedAsset, false);
|
|
if (Editor)
|
|
{
|
|
Editor->CloseWindow();
|
|
}
|
|
}
|
|
}
|
|
|
|
FEditorDelegates::OnLightingBuildStarted.Broadcast();
|
|
|
|
FStaticLightingManager::Get()->CreateStaticLightingSystem(Options);
|
|
}
|
|
|
|
void UEditorEngine::UpdateBuildLighting()
|
|
{
|
|
FStaticLightingManager::Get()->UpdateBuildLighting();
|
|
}
|
|
|
|
bool UEditorEngine::IsLightingBuildCurrentlyRunning() const
|
|
{
|
|
return FStaticLightingManager::Get()->IsLightingBuildCurrentlyRunning();
|
|
}
|
|
|
|
bool UEditorEngine::IsLightingBuildCurrentlyExporting() const
|
|
{
|
|
return FStaticLightingManager::Get()->IsLightingBuildCurrentlyExporting();
|
|
}
|
|
|
|
bool UEditorEngine::WarnIfLightingBuildIsCurrentlyRunning()
|
|
{
|
|
bool bFailure = IsLightingBuildCurrentlyRunning();
|
|
if (bFailure)
|
|
{
|
|
FNotificationInfo Info( LOCTEXT("LightBuildUnderwayWarning", "Static light is currently building! Please cancel it to proceed!") );
|
|
Info.ExpireDuration = 5.0f;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (Notification.IsValid())
|
|
{
|
|
Notification->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
}
|
|
else if (FEditorBuildUtils::IsBuildCurrentlyRunning())
|
|
{
|
|
// Another, non-lighting editor build is running.
|
|
FNotificationInfo Info( LOCTEXT("EditorBuildUnderwayWarning", "A build process is currently underway! Please cancel it to proceed!") );
|
|
Info.ExpireDuration = 5.0f;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (Notification.IsValid())
|
|
{
|
|
Notification->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
|
|
bFailure = true;
|
|
}
|
|
return bFailure;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|