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 Change 3110660 on 2016/9/1 by Andrew.Grant Moved performance/quality warnings out of DrawStatsHUD into new function and now display them in everything other than shipping builds (unless disabled, or screenshot/movie dumping is in progress. HLOD warning is updated every 20 secs to deal with streaing levels. Moved debug warnings into a separate Draw function (still disabled in test, but would like to make this an option in Orion soon). #rb Michael.Noland #tests verified we see our unbuilt HLOD warning in v31 :( Change 3106649 on 2016/08/30 by Cody.Haskell #Orion - Input Axis Work #rb DanH #tests PIE Change 3106299 on 2016/08/30 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 31.2 @ CL 3105865 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3105969 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change3106213on 2016/08/30 by Ben.Marsh BuildGraph: Include UAT, UBT, and UHT binaries in precompiled binaries zip file. #rb none #tests preflight Change 3105994 on 2016/08/30 by Martin.Wilson Stop recompression happening when additive frame index is changed "interactively" (recompression will occur at end of interactive input) #jira UE-35289 #rb Thomas.Sarkanen #tests Tested UI in editor Change 3105331 on 2016/08/29 by Uriel.Doyon Allowed texture to ignore streaming MipBias with UTexture2D::bIgnoreStreamingMipBias Used this new flag when assigning texture to UImage::SetBrushFromTexture to prevent having low quality UI in low texture budget. #rb marcus.wassmer #tests launched editor and played game #jira OR-25814 Change 3105143 on 2016/08/29 by Josh.Markiewicz #UE4 - added assert when histogram input parameters don't match #rb none #tests launched/ran/won game golden path #codereview dmitry.rekman, michael.noland, bart.bressler Change 3104976 on 2016/08/29 by Jon.Lietz pickup refector - fixed a big that would allow mixed replication to call a gameplay cue's added twice. - All pickups now use the pick up manager, consolidated all pick up code into the manager. - added to the XP set so we can define the CXP bounty for targets. #RB Dave.Ratti #tests Bot match, test maps, spawning coins and pickups. Change 3103480 on 2016/08/26 by Josh.Markiewicz #UE4 - added GetSessionIdStr to FOnlineSessionSearchResult and FOnlineSession #rb none #tests golden path matchmaking #codereview paul.moore, eric.newman Change 3103410 on 2016/08/26 by Max.Chen Movie Capture: Fix commandline burnin option. #rb none #tests Render movie with commandline -UseBurnIn=yes option. Change 3102134 on 2016/08/25 by Brian.Karis Fix for HDR output exposure. Added 1000nit output option. #rb marcus #tests agora Change 3101276 on 2016/08/25 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_31 - Merging CL 3100347 (head revision of 2 files :o ) #RB:none #Tests:none [CodeReviewed]: matt.schembari, max.preussner #R@BOMERGE-SOURCE: CL 3101273 in //Orion/Release-31/... via CL 3101274 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3101267 on 2016/08/25 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_DUI - Integrating Media changes from 4.13 (head revision) #RB:none #Tests:none /Engine/Plugins/Media /Engine/Source/Runtime/Media /Engine/Source/Runtime/MediaAssets [CodeReviewed] matt.schembari, max.preussner #R@BOMERGE-SOURCE: CL 3099267 in //Orion/Dev-UI/... via CL 3101266 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3100378 on 2016/08/24 by John.Pollard Fix a crash that can occur when scrubbing in replays #codereview David.Ratti #tests Replays #rb DavidR This is the output: [2016.08.24-21.35.05:973][603]LogAbilitySystem:Warning: OnRep_ReplicatedAnimMontage: PlayMontageSimulated failed. Name: AbilitySystemComponent0, AnimMontage: LevelStart_Montage Change 3100375 on 2016/08/24 by Laurent.Delayen Added AimOffsetLookAt node. AimOffset node that drives its inputs automatically from a Target Location (and a Source Socket). #rb none #codereview lina.halper #tests Tacticia's RMB Targeting Change 3100278 on 2016/08/24 by Laurent.Delayen Fix for fast path struct copy being broken for FVectors. #rb lina.halper #codereview thomas.sarkanen #tests Chains' hook, Tacticia's LaserBeam and OrientationWarping Change 3100161 on 2016/08/24 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Fix issue with refresh viewer command failing due to backend congestion #rb RyanG #tests Replays Change 3100114 on 2016/08/24 by jason.bestimt #ORION_MAIN - Merge DUI @ CL 3098849 #RB:none #Tests:none #CodeReview: kerrington.smith, matt.schembari #R@BOMERGE-SOURCE: CL 3100078 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3100015 on 2016/08/24 by Michael.Noland Don't allow the consideration of nodes that won't be processed to affect the live aspect of the active sound containing a cross fade node #jira UE-34998 #rb Aaron.McLeran [re-implementing CL# 3098559 originaly by Marc.Audy in Release 4.13] #tests Compiled and ran a golden path match with headphones on Change 3100012 on 2016/08/24 by Michael.Noland UE-34951 - Zero-volume vorbis decoded sounds are too expensive -Adding an audio settings parameter to disable zero-volume playback globally -Adding a new bool on sound waves to allow opt-in to virtualize when at zero-volume #rb marc.audy [re-implementing CL# 3094893 from Dev-Framework, originally by Aaron McLeran] #tests Compiled and ran a golden path match with headphones on Change 3099889 on 2016/08/24 by Max.Chen Sequencer: Added command line option to enable burnin #rb none #tests Render movie from command line wtih -UseBurnIn=yes Change 3099801 on 2016/08/24 by Lina.Halper Removed unnecessary comment #rb: none #code review: Benn.Gallagher #tests: compile Change 3099787 on 2016/08/24 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani #http - fix for cancelled requests that have not been started never triggering a completion delegate - fixes soft lock when handling disconnects during login OR-26945 The client stays on the "downloading profile" screen when rejoining after disconnecting #rb josh.markiewicz, alex.fennell #tests none #R@BOMERGE-SOURCE: CL 3099782 in //Orion/Release-30.2/... via CL 3099784 via CL 3099785 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3099252 on 2016/08/24 by Lina.Halper Fixed menu text #rb: none #code review: Thomas.Sarkanen #tests: open editor and create child montage and replaced the animation Change 3099251 on 2016/08/24 by Lina.Halper Deterministic cooking of skeleton - abandon all guid from GuidMap. GuidMap is still important since we have to generate UID from it, but GuidMap only contains name once cooked #jira: UE-34834 #rb: Martin.Wilson #tests: cooking orion and make sure it works Change 3098504 on 2016/08/23 by Bart.Bressler Add server time between sending packets monitoring histogram #rb dmitry.rekman #tests ran server locally and made sure analytics events were sent Change 3098494 on 2016/08/23 by Michael.Noland Engine: Added UWorld::SetTimeUntilNextGarbageCollection to change the GC timer for use when doing automated performance capture measurements - Note: Things that force a GC will still force a GC after using this method (and they will also reset the timer) - Fixed a bug where UWorld::ForceGarbageCollection might not force a GC immediately if run on a server with no clients connected #tests Tested by calling while stat dumphitches was active and confirmed that the interval changed #codereview ben.salem, gil.gribb #rb none Change 3098491 on 2016/08/23 by Mieszko.Zielinski Expanded BTDecorator_IsAtLocation with an option to use AIDataProvider #UE4 #rb Lukasz.Furman #test golden path Change 3098070 on 2016/08/23 by Lina.Halper Fix crash with UI update reconstructing - will have to come up with a better solution than this. #rb: Martin.Wilson #tests: child anim montage Change 3097914 on 2016/08/23 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel Merging CL #3097879 from //WEX/Main/Engine/Source/Runtime/Online/NotForLicensees/OnlineSubsystemMcp/... to //Orion/Main/Engine/Plugins/Online/NotForLicensees/OnlineSubsystemMcp/Source/... #Analytics #OSS: Adjusted cohort selection algorithm and test cases [CodeReviewed]: Philip.Buuck #TESTS: unit tests #RB: none #R@BOMERGE-SOURCE: CL 3097911 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3097745 on 2016/08/23 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Megre 30.2/31 @ CL 3096895 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3097716 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3097722 on 2016/08/23 by Chris.Bunner Update texture expression properties before triggering parent material recompile. #rb John.Billon #tests Editor replace references, Golden path #jira OR-27531 Change 3097694 on 2016/08/23 by Lina.Halper #Child Anim Montage - Duplicate from parent of the information it cares to get - Currently it is selective on copying what data - Modified GetAllAnimationSequencesReferred to get a partial data - Added ParentAsset/AssetMappingTable in AnimationAsset - Sequence Browser opening would also add to history - AnimNotify - CanBeplaced virtual function lets you filter which asset it's placed on #code review: Benn.Gallagher, Thomas.Sarkanen, David.Ratti #rb:Martin.Wilson #tests: creating child montage, editing, lots of UI functionality, notifies placement Change 3097513 on 2016/08/23 by Thomas.Sarkanen Non-POD structs can now be copied using the fast path We now always use CPP struct ops to perform copies when dealing with struct properties. #jira UE-34571 - Support struct member access on AnimBP fast path #rb Laurent.Delayen #tests OrionEntry with Tacticia, confirming orientation warping works correctly and fast path is enabled. Agora_P with Tacticia & bots, played two games. Change 3096729 on 2016/08/22 by Mieszko.Zielinski Fixes to EQS scoring function preview #UE4 #rb Lukasz.Furman #test golden path Change 3096596 on 2016/08/22 by Jason.Bestimt #ORION_DG - Fixes from 4.13 to video playback (CL# 3075761 & 3083970) #RB:none #Tests:none #CodeReview: matt.schembari, max.preussner #R@BOMERGE: MAIN Change 3096550 on 2016/08/22 by Jurre.deBaare Fix for HLOD dirty clusters PIE warning message #tests Simulated Origin with built HLOD clusters, and with one dirty cluster #rb none Change 3096532 on 2016/08/22 by Mieszko.Zielinski Modified GameplayTask_WaitDelay to allow specifying task's priority #UE4 As part of the change introduced UGameplayTask::NewTaskUninitialized that's basically a redirect of NewObject, but clearly indicates that a task needs to be manually initialized #codereview Lukasz.Furman #rb none #test golden path Change 3096455 on 2016/08/22 by Jason.Bestimt #R@BOMERGE-AUTHOR: keli.hlodversson #CEF: Copy upgraded CEF binaries from //Portal/Main to fix crash issues with Sofort purchases #RB David.Nikdel #TESTS none #R@BOMERGE-SOURCE: CL 3096452 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3096316 on 2016/08/22 by Sammy.James Resave to fix log warnings. BPC changes to ensure type saves. #rb #tests editor Change 3096040 on 2016/08/22 by bruce.nesbit Revised fix for landscape crash #rb GarethM #tests Game #codereview Bart.Bressler Change 3096015 on 2016/08/22 by bruce.nesbit Fixed a crash in ALandscapeProxy::PostLoad when running an editor build with -server #rb none #tests game #codereview Bart.Bressler Change 3095578 on 2016/08/19 by Mieszko.Zielinski Made NavigationSystem call TickAsyncBuild on all navigation data instances is there was an ongoing navigation build in progress in the editor #UE4 This was causing Orion's flow field to not build if auto navmesh update was disabled in the editor #rb none #test golden path #codereview Lukasz.Furman Change 3095397 on 2016/08/19 by Lina.Halper Fix issue with crash when deleting all segment #rb: Laurent.Delayen #tests: delete segment and make sure it doesn't crash #jira: UE-34830 Change 3095060 on 2016/08/19 by Bart.Bressler Don't load ULandscapeComponent objects on dedicated servers to save memory. #tests cooked server data and played a Solo vs. AI game #rb gareth.martin #codereview james.golding Change 3095037 on 2016/08/19 by Lina.Halper Potential fix with montage trigger ensure on marker sync group #jira: OR-27685 #rb: Benn.Gallagher #code review: Martin.Wilson #tests: attack primhelilx with knock up Change 3094962 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #Merging #OSS - Added FUserOnlineAccountMcp::SelectCohort #RB: None #TESTS: test suite in source [CodeReviewed]: Philip.Buuck #R@BOMERGE-SOURCE: CL 3094961 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3094950 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #Merge #UE4 - Made FMD5 const-correct #RB: none #TEST: none #R@BOMERGE-SOURCE: CL 3094949 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3094619 on 2016/08/19 by Lina.Halper #DUPEFIX - ANIM: SmartNAME: the cooking doesn't guarantee the package is saved in the order, so we'll still have to regenerate list without GUID. - assumed the name is all set by now #rb: Benn.Gallagher #jira : UE-34886 #tests: cooking infiltrator that showed same issue and run game. Change 3094532 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3094498 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3094528 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3093260 on 2016/08/18 by Josh.Markiewicz #UE4 - changed how party reset occurs to skip relying on AGameState which could very rarely be null (during server travel) - removed unnecessary cast #rb bart.bressler #codereview bart.bressler, rob.cannaday #tests launched game, some basic party testing Change 3093224 on 2016/08/18 by Josh.Markiewicz #UE4 - added a chatroom class that does some basic chat room join/create/leave functionality to share between games #rb paul.moore #codereview anthony.carter #tests solo vs ai chat with 2 players, coop vs ai chat with 2 players, one leaving and rejoining Change 3092597 on 2016/08/17 by Daniel.Lamb Added Ben Crocker to the rebuild lighting emails. #rb Trivial #Test none Change 3092063 on 2016/08/17 by andrew.grant Merging using ROBO://Orion/Release-Candidate->//Orion/Main #rb #tests none Change 3091081 on 2016/08/16 by Jurre.deBaare Fixing non-Editor build errors #fix Wrapped parts in WITH_EDITOR and added IsBuilt to check if the LODActor has a valid static mesh (thus is not dirty) #tests Build Editor + Game #rb none Change 3091009 on 2016/08/16 by Mieszko.Zielinski Added a way to configure a map to not spawn AISystem instance at all #UE4 #rb none #test golden path Change 3090932 on 2016/08/16 by Michael.Noland Vixen: Added indication to the analytics and FPS charts #rb marcus.wassmer #tests Compiled for the platform Change 3090844 on 2016/08/16 by Laurent.Delayen Replicated CL 3090734 from Fortnite. --- Fix AbilitySystemComponent not ticking while playing a montage, and ticking when we're not playing a montage Here's the issue in the version of the code prior to this checkin: - UpdateShouldTick calls GetShouldTick, which checks the value of RepAnimMontageInfo.IsStopped - When we call UpdateShouldTick within AnimMontage_UpdateReplicatedData, we haven't set RepAnimMontageInfo.IsStopped yet to the correct value - So when we aren't playing any montages but are starting a new one, we were saying we shouldn't tick - It also means if we were playing a montage, and then stop, we'll start ticking - Ticking calls AnimMontage_UpdateReplicatedData, which should be called while we're playing #codereview john.abercrombie #rb none #tests golden path Change 3090832 on 2016/08/16 by Michael.Noland Windows: Fixed a whitespace issue #rb none #tests Compiled for windows Change 3090688 on 2016/08/16 by Jason.Bestimt #R@BOMERGE-AUTHOR: andrew.grant Merging using ROBO://Orion/Release-Candidate->//Orion/Main #rb none #tests built #R@BOMERGE-SOURCE: CL 3090687 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3090547 on 2016/08/16 by Jurre.deBaare Need a warning message similar to lighting unbuilt when HLOD cluster is not built #fix Added HLOD clusters need to be rebuilt message similar to the lighting one during PIE and game-time, and cleaned/changed "'DisableAllScreenMessages' to suppress" behaviour #jira UE-34335 #rb none #codereview Michael.Noland #tests pie Agora with and without dirty HLOD clusters Change 3090285 on 2016/08/16 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3090267 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3090282 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3089413 on 2016/08/15 by paul.mader Agora 2.0 assets Change 3089266 on 2016/08/15 by Max.Chen Sequencer: Add Convert to Possessable Copy from Dev-Sequencer #jira UE-32139 #rb none #tests Convert steel to possessable in Gameplay_PS4 map. Change 3089136 on 2016/08/15 by Mieszko.Zielinski Fixed AISense_Sight's time slicing unintentionally skipping queue aging if given time limit is reached #UE4 #rb Lukasz.Furman #codereview Dan.Youhon #test golden path Change 3089118 on 2016/08/15 by Mieszko.Zielinski Fixed a rare crash in UBlackboardData::GetKeyType resulting from a key selector referencing a type that has been removed from the project's source code #UE4 #rb none #test golden path Change 3088976 on 2016/08/15 by Andrew.Grant Fixed issue with PS4 toolchain ignoring ModuleRules.CodeOptimization.Never / ModuleRules.CodeOptimization.Always when determining optimization level of modules. Fixed issue with VC toolchain ignoring ModuleRules.CodeOptimization.Never setting. Removed superflous /Os from VC debugg settings #rb none #tests verified module built with 'Never' on PS4/Win is built without optimizations. #codereview Marcus.Wassmer, Ben.Marsh Change 3088830 on 2016/08/15 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3088807 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3088829 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3088597 on 2016/08/14 by Jason.Bestimt #ORION_DG - Trying to resolve R@BOMERGE collision (DUI to MAIN -> DG) #RB:none #Tests:none #CodeReview: andrew.grant, david.ratti, matt.schembari Change 3087827 on 2016/08/12 by Bart.Bressler Updates to skeletal mesh memory saving on dedicated server #rb lina.halper #tests Cooked server data, played a game for a while in Solo vs. AI Change 3087351 on 2016/08/12 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) #jira OR-27406 #rb RyanG #tests Replays Change 3087118 on 2016/08/12 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3086747 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3087117 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3086176 on 2016/08/11 by Marcus.Wassmer Fix PS4 ShaderPipelines not matching pixel/vertex shader properly. #rb Rolando.Caloca #tests Broken PS4 content before/after Change 3085992 on 2016/08/11 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Unclog R@BOMERGE #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3085987 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3085911 on 2016/08/11 by Laurent.Delayen Added FBoneContainer::BoneIsChildOf for FCompactPoseBoneIndex #rb none #tests Orientation Warping Change 3085614 on 2016/08/11 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3085547 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3085598 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3084507 on 2016/08/10 by Marcus.Wassmer Duplicate 3070376 and 3078879 to fix corrupted decals on Vixen. #rb none #tests paragon ps4/vixen #codereview Olaf.Piesche Change 3084136 on 2016/08/10 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3083799 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3083814 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3083424 on 2016/08/09 by Max.Chen Sequence Recorder: Fix crash when actor class to record is null. #tests Use sequence recorder to record a skeletal mesh actor #rb none Change 3083134 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani #online,store,ps4 - creating one offer entry per entitlement #rb david.nikdel, ian.fox #tests MTX purhcase on PS4 #lockdown: andrew.grant #R@BOMERGE-SOURCE: CL 3083127 in //Orion/Release-30.1/... via CL 3083128 via CL 3083131 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3083069 on 2016/08/09 by Marcus.Wassmer Vixen scalability changes #rb Michael.Noland #tests vixen/ps4 #codereview jordan.walker Change 3083063 on 2016/08/09 by Marcus.Wassmer Most games will probably run out of memory if setup to do auto-4k. Make this a setting that's off by default. #rb Michael.Noland #codereview Luke.Thatcher, Lee.Clark #tests vixen on 4k. Change 3082778 on 2016/08/09 by Marcus.Wassmer Duplicate fix for Vixen GPU page faults and rendertarget errors (3066087) #rb none #tests Agora on vixen. Change 3082772 on 2016/08/09 by Marcus.Wassmer Duplicate fix for detail mode reregistration (3065543) #rb none #tests Toggled detail mode, observe proper items spawning Change 3082765 on 2016/08/09 by Marcus.Wassmer Don't crash when trying to use windowed vsync on vixen #rb Michael.Noland #test ran paragon on vixen #codereview Luke.Thatcher,Lee.Clark Change 3082764 on 2016/08/09 by Marcus.Wassmer fix HLOD distance scale not working properly when components are re-registered. #rb michael.noland #codereview jurre.debarre #tests setting multiple times, setting on boot via deviceprofile Change 3082429 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani Merging //Orion/Release-30.1 to Main (//Orion/Main) Change: 3082419 #online,store,PS4 OR-25384 [PS4] "There is no content. It might not be for sale yet, or might no longer be for sale" at main menu and at post match screen - added config option for toggling store on PS4 [OnlineSubsystemPS4] bStoreEnabled=true - can also override via title specific json values in <titleid>\title.json allow_mtx=true [CodeReviewed]: andrew.grant, phillip.buck, ian.fox #lockdown: andrew.grant #rb none #tests ps4 run with titleid=CUSA3609_00 (which has mtx disabled for PS4 since that title has no store support) #R@BOMERGE-SOURCE: CL 3082428 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3082194 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3082105 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3082192 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3080984 on 2016/08/08 by Lina.Halper Issue with not being able to set static animation data via BP - artists were using SetAnimation/PlayAnimation, but they are not safe to be used in construction script, so made sure the other serializable properties are exposed via BP - also since they want it to work in level viewport, I have to tick/refresh whenever it's getting called. #rb: Martin.Wilson #tests: Sword Beauty map Change 3080665 on 2016/08/08 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3080081 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3080543 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3080565 on 2016/08/08 by Laurent.Delayen Fix for curve values during URO interpolation. Fixes flashing of materials and Twinblast's ult weapon. https://jira.ol.epicgames.net/browse/OR-27107 https://jira.ol.epicgames.net/browse/OR-24358 #rb lina.halper, martin.wilson #tests Twinblast's ult and Coil's primary. Change 3079832 on 2016/08/05 by Jason.Bestimt #R@BOMERGE-AUTHOR: marcus.wassmer Fix for PS4 crash reports not attaching the minidump when trying to force full crash dumps via commandline #rb none [CodeReviewed] Chris.Wood #tests checked crashcontext on PC/PS4 #lockdown Andrew.Grant #R@BOMERGE-SOURCE: CL 3078933 in //Orion/Release-30/... via CL 3078934 via CL 3078935 via CL 3079831 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3079045 on 2016/08/05 by Lina.Halper Adding more log to figure out why ActivePlayers.Count becomes inconsistent. #rb: Martin.Wilson #tests: PIE with bots Change 3078944 on 2016/08/05 by Rolando.Caloca O - Update blacklisted driver #jira OR-27051 #rb Marcus.Wassmer #tests Run with AMD card Change 3078735 on 2016/08/05 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3078670 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3078734 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3078122 on 2016/08/04 by Dmitry.Rekman Linux: treat abort() / SIGABRT as crash. - Rationale: certain code not under our control (most notably, stack smashing protector) may call abort(), which would previously terminate the engine without any chance to even enter the crash handler. - Rewrote RequestExit() because it used abort() itself. - Also removed -fstack-protector. The logic behind this is: stack protector calls abort() on detecting a smash (which is suspected to contribute to missing reports), but does it at an inappropriate place, that causes stack unwinding to crash later. As bad as it sounds, it may be better to allow stack to be corrupted and crash later - hopefully outside of libc code - to some other reason. #rb Mark.Satterthwaite #codereview Mark.Satterthwaite, Michael.Noland, Andrew.Grant #review-3078104 @Mark.Satterthwaite, @Michael.Noland, @Andrew.Grant #tests Ran Linux server, crashed using different methods. Change 3077887 on 2016/08/04 by Dmitry.Rekman Initialize StackCount to 0 (kills valgrind warning). #rb David.Ratti #codereview David.Ratti #tests Ran Linux server. Change 3077257 on 2016/08/04 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3077193 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3077256 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3077242 on 2016/08/04 by Dmitry.Rekman Linux: stop heartbeat thread before handling the crash. #rb Robert.Manuszewski #codereview Robert.Manuszewski, Andrew.Grant #tests Compiled and ran Linux server, crashed it. Change 3076676 on 2016/08/03 by Dmitry.Rekman Linux: print details about memory access (read or write). - Also print all the 16 digits of the pointer. - Read/write detection only implemented for x86_64. #rb Andrew.Grant #codereview Andrew.Grant #tests Compiled (natively) and ran Linux server. Change 3076675 on 2016/08/03 by Dmitry.Rekman Print a bit more info about the array in assert. #rb Andrew.Grant #codereview Andrew.Grant #test Compiled and ran Linux server. Change3076010on 2016/08/03 by Laurent.Delayen Moved OrionAnimNode_LegIK from Paragon to Engine. #codereview lina.halper #rb none #tests Grim.exe + Iggy & Scorch Change 3075512 on 2016/08/03 by Matt.Kuhlenschmidt Reimplemented 3070766 for Orion: Make sure richtooltips are not generated for hidden enum items so that there is not a mismatch between rich tooltips and enum items (causing a crash) #rb none #tests none Change 3075446 on 2016/08/03 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3075422 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3075445 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3075394 on 2016/08/03 by HaarmPieter.Duiker Adding a shadows max and highlights min parameters to allow the user to control when the 'shadows' controls fall off and when the 'highlights' controls ramp in. #rb marcus.wassmer #tests post process color correction Change 3074314 on 2016/08/02 by Dmitry.Rekman Linux: change optimization from -O2 to -O1 (temporarily?). - The purpose is to make callstacks easier to follow and possibly catch stack smashing (if it happens) earlier. - Also adds a line to UBT output during compilation to draw attention. #rb Michael.Noland #codereview Michael.Noland, Andrew.Grant, Bart.Bressler #tests Compiled and ran Linux server. Change 3073553 on 2016/08/02 by jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3073360 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3073481 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) #R@BOMERGE-SAYS: Beep boop! I couldn't merge this change. Please do it yourself, human. //Orion/Dev-General/OrionGame/Content/Characters/Heroes/BP_Hero.uasset - can't integrate exclusive file already opened #CodeReview: jason.bestimt Change 3073505 on 2016/08/02 by Daniel.Lamb Added cook modification delegate stats to cooker stats. #rb Wes.Hunt #test cook paragon. Change 3072440 on 2016/08/01 by Aaron.Eady PlayerController Force Feedback (Debug only); Adding #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) around the use of the debug only variable ForceFeedbackEffectHistoryEntries. #rb none #tests SHIPPING Change 3072259 on 2016/08/01 by Aaron.Eady PlayerController Force Feedback (Debug only); Added more information to the things displayed on the screen for force feedback when we do ShowDebug ForceFeedback. #rb Michael.Noland #tests PIE Change 3071908 on 2016/08/01 by John.Pollard Fix null reference crash #rb DavidR #tests Live game + replays Change 3071876 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Assertion failed: WriterState.Changed.Num() == 0 occurs when a Pitcher Husk hits the Player #rb none #tests FN + Paragon live game + replays #codereview Andrew.Grant Change 3071875 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Finalize replay version system * No longer use changelist to filter replays (so we will only filter by engine/game version now, which need to be hand cranked to invalidate old versions) * Submit actual changelist when uploading (rather than locking to previous versions). We can do this now since we don't filter by changelist anymore. * Removed unnecessary 'bShowAllVersions' property from replay browser code, using cvar instead (orion.ShowAllReplayVersions) #rb RyanG #tests Live game + replays #codereview Andrew.Grant Change 3071874 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Fix gameplay tags to work better with backwards compatibility in replays * We use the net field export group system in the package map to export tag names as a packed index * This will allow us to see the names of tags that no longer exists on the remote side #rb RyanG #tests Live game + replays #codereview Andrew.Grant Change 3071776 on 2016/08/01 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3071738 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3071775 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3071258 on 2016/07/30 by Aaron.McLeran OR-26580 CRASH: FXAudio2SoundSource::GetChannelVolumes - Silent Crash during gameplay - Removed voice operation set since it was causing crashes when stopping voices. Still a good idea, but need to make sure the async OnBufferEnd and stopping an FSoundSource can work together. - Added a proxy object that wraps the FAsyncTask used for async decoding. Calling IsDone() and EnsureCompletion() can't happen at the same time from different threads now. #rb none #tests ran paragon soaking for a long time with constant AI combat and observed no crashes or audio issues. Change 3071099 on 2016/07/30 by Aaron.McLeran OR-26580 CRASH: FXAudio2SoundSource::GetChannelVolumes - Silent Crash during gameplay - Temporary revert of a portion of CL 3067560 which exacerbates an issue with the async decoding tasks and calling IsDone and EnsureComplete on different threads. #rb none #tests ran paragon with change and noticed no change in audio quality Change 3070916 on 2016/07/29 by Andrew.Grant Missed file! #rb #tests na Change 3070915 on 2016/07/29 by Andrew.Grant Merging //UE4/Main @ 3070724 through //UE4/Orion-Staging #rb none #tests Engine QA, Orion QA smoke Change 3070576 on 2016/07/29 by Uriel.Doyon Fixed initialization of the defrag pool size. Now controlled by r.PS4DefragPoolSize. #review-3070386 @marcus.wassmer #jira OR-25941 #rb marcus.wassmer #tests Run Game on PS4, and in editor Change 3070086 on 2016/07/29 by Martin.Wilson Fixed ensure triggering during sequencer playback due to double update. #jira UE-33938 #rb Thomas.Sarkanen #tests opened affected asset and verified problem no longer occured Change 3070016 on 2016/07/29 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30 @ CL 3069935 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3069976 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3069435 on 2016/07/28 by Ian.Fox #Orion, #Mcp - Check if Price Engine is configured before attempting query #rb Sam.Zamani #tests none #codereview Sam.Zamani Change3069381on 2016/07/28 by Michael.Noland Animation: Demoted a check() in anim sync group code to an ensure() to unblock others #rb nick.penwarden #tests Loaded Paragon cine asset that was crashing #codereview lina.halper, martin.wilson Change 3069203 on 2016/07/28 by Dmitry.Rekman Headless client: do not draw windows. - Disables a bunch of code, including reaching into font cache to estimate width. - Should be probably disabled on a higher level, but cutting out the whole Slate application is infeasible (according to BradA/BenM, due to some logic requiring widgets). #rb Nick.Atamas #review-3068983 @Nick.Atamas, @Michael.Noland, @Brad.Angelcyk, @Ben.Salem #codereview Nick.Atamas, Michael.Noland, Brad.Angelcyk, Ben.Salem #tests Compiled and ran Orion Linux client. Change 3069181 on 2016/07/28 by Lina.Halper Fix struct redirector for Orion anim node moving to engine #rb: Maciej.Mroz #code review:Laurent.Delayen #tests: editor loading the anim BP that caused the name conversion Change 3069092 on 2016/07/28 by Aaron.McLeran OR-26161 Client hitches indefinitely when using Stat soundcues / soundwaves - Not all active sounds have sound classes, was causing a crash #codereview marc.audy #rb zabir.hoque #tests Run game with stat soundcues and not crash Change 3068969 on 2016/07/28 by David.Ratti Move test for invalid gameplaycue instance up, since calling IsPendingKill() on garbage can cause crash too. #rb none #tests compile Change 3068902 on 2016/07/28 by David.Ratti Code for tracking down UGameplayCueManager::GetInstancedCueActor crash. #rb none #tests compile Change 3068831 on 2016/07/28 by Aaron.McLeran OR-26417 Reverb is too loud in-game in Dev-General - Initializing prev reverb to 0s so that the first default reverb gets set when no audio volume is set. #rb Jeff.Campeau #tests run a map with no reverb audio volume and reverb is not super wet Change 3068529 on 2016/07/28 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #OSS #PurchaseMcp: Use GameService->CreateOnlineHttpRequest instead of McpSubsystem->CreateRequest to query receipts (uses subsystem config) #RB: none #TESTS: none #R@BOMERGE-SOURCE: CL 3068465 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3068399 on 2016/07/28 by Andrew.Rodham Sequencer: Changed animation tracks to allow more animation types (such as anim montages) - APIs now accept UAnimSequenceBases rather than UAnimSequences to afford more flexibility #jira OR-25769 #tests Tested all combinations of animation with sequencer (montage+sequence on asset/BP driven animation components) and matinee. Tested running a game and playing back the announce trailer. Rendered out some movies to ensure that trails work correctly. #rb Benn.Gallagher Change 3068138 on 2016/07/28 by Marcus.Wassmer Disable mallocleak testing by default #rb none #test none Change 3068121 on 2016/07/28 by Marcus.Wassmer Make sure we always do fast stack captures when USE_FAST_STACKTRACE is enabled. Fixes game becoming unresponsive on Windows after 'mallocleak' dumps data. Any other tool that uses stacktraces could become 700 - 1000x slower after any stack symbolication also. #rb Robert.Manuszewski #tests stack tracing / symbolication with mallocleak on windows. Change 3068119 on 2016/07/28 by Marcus.Wassmer Fix MallocLeakProxy deadlock #rb Robert.Manuszewski #tests mallocleak start/stop/dump on windows Change 3067752 on 2016/07/27 by Michael.Noland Engine: Refactored FPS chart creation to make it modular so many performance data consumers can be active at once, allowing greater flexibility and decoupling game analytics from FPS chart exec commands - IPerformanceDataConsumer is an interface for all consumers of per-frame performance tracking data, and instances can be registered/unregisted with the engine using AddPerformanceDataConsumer/RemovePerformanceDataConsumer - The implementation of the 'standard' frame time and hitch histogram tracking is FPerformanceTrackingChart, while the per-frame logging .csv is split into a separate FFineGrainedPerformanceTracker class. - The calculation of frame time breakdowns and hitch detection now occur as long as at least one IPerformanceDataConsumer is registered - Internally the code has been cleaned up a bit to use FHistogram for data storage instead of custom binning code Upgrade Notes: - DumpFPSChartAnalytics has been removed, games that used it should switch to creating their own instance of FPerformanceTrackingChart and call DumpChartToAnalyticsParams on it directly - In general games should have no reason to programmatically call GEngine->StartFPSChart anymore, instead creating their own instance (this prevents conflicts when using the engine console commands) - HTML output for stopfpschart is now generated to a single file rather than two duplicate files (using both map name and capture time as part of the file name) - Removed PauseFPSChart, IsFPSChartActive, and GetFPSChartBoundByFrameCounts to reflect that the GEngine instances aren't meant for external use (Start/Stop are left public for automated testing that wants to use them to do logging, but may also be moved private in the future) Paragon: - Updated to use a separate FPerformanceTrackingChart for gameplay versus in-game menus and removed the duplicated code and GameThreadHitchChart event - Removed partial USE_SERVER_PERF_COUNTERS code in ChartCreation.cpp, splitting it out into a separate observer, which currently lives in Paragon but will be moved to shared code in a separate checkin. The code was only useful in the first place along with other Paragon-side code that was consuming it. #rb dmitry.rekman #codereview bob.tellez, peter.knepley, andrew.grant, john.mauney #review-3067607 @Dmitry.Rekman, @Bob.Tellez #tests Tested manual startfpschart/stopfpschart as well as Paragon match analytics via golden path solo vs AI Change 3067654 on 2016/07/27 by Michael.Noland FString - Fix divide overload path concatenation for empty paths since there are several places in the engine that expect using that doing { path / "" } will append a / onto path. #rb steve.robb #jira UE-31959 [duplicating CL# 3039827] #tests Tried moving a folder in the editor Change 3067644 on 2016/07/27 by Aaron.McLeran OR-24537 Looping audio sometimes persists in Agora Adding stopping sounds if audio component is destroyed while playing a looping sound #rb jeff.campeau #tests audio component stops looping sound if audio component is destroyed prematurely Change 3067560 on 2016/07/27 by Aaron.McLeran OR-26322 Client Hang in FXAudio2EffectsManager::SetReverbEffectParameters - Only applying reverb parameters if they've changed from previous reverb params to avoid unnecessarily spamming the XAudio2 API call - using xaudio2 operation sets to ensure that voice and effect params are executing in sequence - only calling destroy voice after all voice and effect changes have been committed to avoid destroy voice interfering with those commands - Don't call EnsureCompletion on pending async tasks on teardown #rb Jeff.Campeau #tests play paragon with change, notice no changes to audio behavior, no crashes. Created testmap with several reverb zones and demonstrated reverb effect transitions Change 3067420 on 2016/07/27 by jason.bestimt #ORION_MAIN - Merge 29.2/30 @ CL 3067312 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3067400 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3067316 on 2016/07/27 by jason.bestimt #ORION_MAIN - Merge DUI @ CL 3065602 #RB:none #Tests:none [CodeReviewed]: matt.schembari #R@BOMERGE-SOURCE: CL 3067079 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change3067025on 2016/07/27 by Michael.Noland Core: Corrected the initial value of FLightweightTimeGuard::FrameTimeThresholdMS to be in MS rather than seconds and did a few coding standards fixes #rb none #tests Compiled Change 3067020 on 2016/07/27 by Michael.Noland Core: Various improvements to FHistogram and split it out into separate files - Added the ability to use a separate thresholding key than the actual measurement value being recorded (e.g., when accumulating frame time spent in a chart keyed on framerate) - Added O(1) getters for total sample counts and sum of all measurements - Removed encapsulation-breaking SetBinCountByIndex / SetBinSumByIndex - Added support for specifying explicit histogram bucket thresholds #rb dmitry.rekman #tests Tested with another pending changelist that moves FPS charts to use FHistogram for the underlying storage Change 3066681 on 2016/07/27 by Frank.Gigliotti Camera anim field of view fix; * The FOV is now reset on the PlayerCameraManager camera actor when it's initialized. This fixes cases of stale FOV values after playing camera anims that don't end with the FOV at it's base value. * Base FOV can now be edited in the CameraAnim properties. This allows you to specify what the FOV keys are relative to. Previously it was always using a base FOV of 90 degrees. #RB None #CodeReview Jeff.Farris #Tests Multiple camera animations in PIE Change 3066508 on 2016/07/27 by Lina.Halper Smartname guid will be discarded during cooking, and once it's cooked, it's trusted to have correct name. #code review:Martin.Wilson, Benn.Gallagher #rb: Martin.Wilson #tests: cooked test map, run test map, PIE, saving content, loading standalone game Change 3066246 on 2016/07/27 by Jason.Bestimt #R@BOMERGE-AUTHOR: andrew.grant Fix for non-unity error #rb none #tests compiled #R@BOMERGE-SOURCE: CL 3066245 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3066167 on 2016/07/27 by Benn.Gallagher Fixed clothing corruption seen on Twinblast after mesh updates. We were copying a u32 index buffer into a multisize container but CopyIndexBuffer doesn't change the data size when copying - only when rebuilding. #rb Ori.Cohen #tests Editor, PIE, Applying clothing to characters. Change 3065868 on 2016/07/27 by Michael.Noland Blueprints: Fixing non-editor build (missing WITH_EDITOR) #rb none #tests Compiled PS4 Change 3065749 on 2016/07/26 by Michael.Noland Blueprints: Prevent a crash on load in RemoveNodeAndPromoteChildren when removing a corrupted SCS node if it has no parent link (the children are moved to the root node instead) #codereview mike.beach, marc.audy #tests Loaded and recovered a corrupted Blueprint on Cameron's machine #rb Phillip.Kavan Change 3065706 on 2016/07/26 by Josh.Markiewicz #UE4 - changed default values for bLogoutOnSessionTimeout for reservation beacons - fixed non shipping cmd line override to be correct #rb none #codereview andrew.grant, paul.moore #tests none Change 3065359 on 2016/07/26 by Rob.Cannaday Limit external id querying to 100 ids per call. The backend currently enforces this and is returning an error when we exceed this limit. Break up calls in batches of 100 ids. #jira OR-20674 #rb ian.fox #tests login to front end with PC, PS4. forced tests to simulate > 100 requests. Change 3065197 on 2016/07/26 by Bart.Bressler Change how PS4 sessions work: - We now will only try to join somebody's PS4 session only if we accepted an invite from the PS4 system software. This means that an MCP party can have members in different PS4 sessions. - Refactored a lot of the delegates in UOrionParty to lambdas to try to make it more readable - Added comments, other misc. code cleanup. #rb josh.markiewicz, sam.zamani, rob.cannaday #tests created cross play parties with multiple pc + ps4 players #jira OR-20332 Change 3065158 on 2016/07/26 by Lina.Halper Fix the guid keep generated by adding to the database. - This caused worse problem with non-deterministic cooking - This doesn't fix UE-33454 for 100%, but this was the main reason why this was so visible #rb: Martin.Wilson #jira: UE-33772, UE-33454 #tests: cooked AI_Test map, editor rename curves Change 3064735 on 2016/07/26 by Dmitry.Rekman Linux: added WebRTC libs. - Compiled against glibc 2.12 / CentOS 6.x environment (see howto in a separate doc). #rb none #tests Tested OrionClient in Dev-General, and UE4Editor in Dev-Platform. (Edigrating 3063715 from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064727 on 2016/07/26 by Dmitry.Rekman Fix crash on cooker exit (UE-33583). - Global/static tickable objects could outlive the collection and trigger asserts when removing themselves from it. #rb none #tests Compiled and ran Linux server and Linux client. (Edigrating 3058779 from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064725 on 2016/07/26 by Dmitry.Rekman Linux: use libc++ instead of libstdc++. - Needed to solve problems with third-party C++ libraries (e.g. WebRTC). - Bundled libc++ 3.8.1 (TPS cleared). - Turned off ICU compilation (needs recompile against libc++). - Some libraries (e.g. FBX sdk) still need libstdc++, so in practice it is going to be a mix. #rb none #tests Built and ran a number of Linux targets. (Edigrating3057152from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064572 on 2016/07/26 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 29.2 @ CL 3064545 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3064569 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3064523 on 2016/07/26 by Jon.Lietz Fixing it so gameplay effects with execution none will no longer keep the BP in a dirty state. Only call EmptyArray() on CalculationModifiersArrayPropHandle if it has any elements. #RB none #tests BP compiles and stays not dirty #codereview dave.ratti@epicgames.com [CL 3111290 by Andrew Grant in Main branch]
3161 lines
114 KiB
C++
3161 lines
114 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
SceneVisibility.cpp: Scene visibility determination.
|
|
=============================================================================*/
|
|
|
|
#include "RendererPrivate.h"
|
|
#include "Engine.h"
|
|
#include "ScenePrivate.h"
|
|
#include "FXSystem.h"
|
|
#include "SceneUtils.h"
|
|
#include "PostProcessing.h"
|
|
#include "PlanarReflectionSceneProxy.h"
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Globals
|
|
------------------------------------------------------------------------------*/
|
|
|
|
static float GWireframeCullThreshold = 5.0f;
|
|
static FAutoConsoleVariableRef CVarWireframeCullThreshold(
|
|
TEXT("r.WireframeCullThreshold"),
|
|
GWireframeCullThreshold,
|
|
TEXT("Threshold below which objects in ortho wireframe views will be culled."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForLights = 0.03f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForLights(
|
|
TEXT("r.MinScreenRadiusForLights"),
|
|
GMinScreenRadiusForLights,
|
|
TEXT("Threshold below which lights will be culled."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForDepthPrepass = 0.03f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForDepthPrepass(
|
|
TEXT("r.MinScreenRadiusForDepthPrepass"),
|
|
GMinScreenRadiusForDepthPrepass,
|
|
TEXT("Threshold below which meshes will be culled from depth only pass."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GMinScreenRadiusForCSMDepth = 0.01f;
|
|
static FAutoConsoleVariableRef CVarMinScreenRadiusForCSMDepth(
|
|
TEXT("r.MinScreenRadiusForCSMDepth"),
|
|
GMinScreenRadiusForCSMDepth,
|
|
TEXT("Threshold below which meshes will be culled from CSM depth pass."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarTemporalAASamples(
|
|
TEXT("r.TemporalAASamples"),
|
|
8,
|
|
TEXT("Number of jittered positions for temporal AA (4, 8=default, 16, 32, 64)."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
#if PLATFORM_MAC // @todo: disabled until rendering problems with HZB occlusion in OpenGL are solved
|
|
static int32 GHZBOcclusion = 0;
|
|
#else
|
|
static int32 GHZBOcclusion = 0;
|
|
#endif
|
|
static FAutoConsoleVariableRef CVarHZBOcclusion(
|
|
TEXT("r.HZBOcclusion"),
|
|
GHZBOcclusion,
|
|
TEXT("Defines which occlusion system is used.\n")
|
|
TEXT(" 0: Hardware occlusion queries\n")
|
|
TEXT(" 1: Use HZB occlusion system (default, less GPU and CPU cost, more conservative results)")
|
|
TEXT(" 2: Force HZB occlusion system (overrides rendering platform preferences)"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GVisualizeOccludedPrimitives = 0;
|
|
static FAutoConsoleVariableRef CVarVisualizeOccludedPrimitives(
|
|
TEXT("r.VisualizeOccludedPrimitives"),
|
|
GVisualizeOccludedPrimitives,
|
|
TEXT("Draw boxes for all occluded primitives"),
|
|
ECVF_RenderThreadSafe | ECVF_Cheat
|
|
);
|
|
|
|
static int32 GAllowSubPrimitiveQueries = 1;
|
|
static FAutoConsoleVariableRef CVarAllowSubPrimitiveQueries(
|
|
TEXT("r.AllowSubPrimitiveQueries"),
|
|
GAllowSubPrimitiveQueries,
|
|
TEXT("Enables sub primitive queries, currently only used by hierarchical instanced static meshes. 1: Enable, 0 Disabled. When disabled, one query is used for the entire proxy."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<float> CVarStaticMeshLODDistanceScale(
|
|
TEXT("r.StaticMeshLODDistanceScale"),
|
|
1.0f,
|
|
TEXT("Scale factor for the distance used in computing discrete LOD for static meshes. (defaults to 1)\n")
|
|
TEXT("(higher values make LODs transition earlier, e.g., 2 is twice as fast / half the distance)"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<float> CVarHLODDistanceScale(
|
|
TEXT("r.HLOD.DistanceScale"),
|
|
1.0f,
|
|
TEXT("Scale factor for the distance used in computing discrete HLOD for transition for static meshes. (defaults to 1)\n")
|
|
TEXT("(higher values make HLODs transition farther away, e.g., 2 is twice the distance)"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
static int32 GOcclusionCullParallelPrimFetch = 0;
|
|
static FAutoConsoleVariableRef CVarOcclusionCullParallelPrimFetch(
|
|
TEXT("r.OcclusionCullParallelPrimFetch"),
|
|
GOcclusionCullParallelPrimFetch,
|
|
TEXT("Enables Parallel Occlusion Cull primitive fetch."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GILCUpdatePrimTaskEnabled = 0;
|
|
static FAutoConsoleVariableRef CVarILCUpdatePrimitivesTask(
|
|
TEXT("r.Cache.UpdatePrimsTaskEnabled"),
|
|
GILCUpdatePrimTaskEnabled,
|
|
TEXT("Enable threading for ILC primitive update. Will overlap with the rest the end of InitViews."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GDoInitViewsLightingAfterPrepass = 0;
|
|
static FAutoConsoleVariableRef CVarDoInitViewsLightingAfterPrepass(
|
|
TEXT("r.DoInitViewsLightingAfterPrepass"),
|
|
GDoInitViewsLightingAfterPrepass,
|
|
TEXT("Delays the lighting part of InitViews until after the prepass. This improves the threading throughput and gets the prepass to the GPU ASAP. Experimental options; has an unknown race."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
/** Distance fade cvars */
|
|
static int32 GDisableLODFade = false;
|
|
static FAutoConsoleVariableRef CVarDisableLODFade( TEXT("r.DisableLODFade"), GDisableLODFade, TEXT("Disable fading for distance culling"), ECVF_RenderThreadSafe );
|
|
|
|
static float GFadeTime = 0.25f;
|
|
static FAutoConsoleVariableRef CVarLODFadeTime( TEXT("r.LODFadeTime"), GFadeTime, TEXT("How long LOD takes to fade (in seconds)."), ECVF_RenderThreadSafe );
|
|
|
|
static float GDistanceFadeMaxTravel = 1000.0f;
|
|
static FAutoConsoleVariableRef CVarDistanceFadeMaxTravel( TEXT("r.DistanceFadeMaxTravel"), GDistanceFadeMaxTravel, TEXT("Max distance that the player can travel during the fade time."), ECVF_RenderThreadSafe );
|
|
|
|
|
|
static TAutoConsoleVariable<int32> CVarParallelInitViews(
|
|
TEXT("r.ParallelInitViews"),
|
|
#if WITH_EDITOR
|
|
0,
|
|
#else
|
|
1,
|
|
#endif
|
|
TEXT("Toggles parallel init views. 0 = off; 1 = on"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Visibility determination.
|
|
------------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Update a primitive's fading state.
|
|
* @param FadingState - State to update.
|
|
* @param View - The view for which to update.
|
|
* @param bVisible - Whether the primitive should be visible in the view.
|
|
*/
|
|
static void UpdatePrimitiveFadingState(FPrimitiveFadingState& FadingState, FViewInfo& View, bool bVisible)
|
|
{
|
|
if (FadingState.bValid)
|
|
{
|
|
if (FadingState.bIsVisible != bVisible)
|
|
{
|
|
float CurrentRealTime = View.Family->CurrentRealTime;
|
|
|
|
// Need to kick off a fade, so make sure that we have fading state for that
|
|
if( !IsValidRef(FadingState.UniformBuffer) )
|
|
{
|
|
// Primitive is not currently fading. Start a new fade!
|
|
FadingState.EndTime = CurrentRealTime + GFadeTime;
|
|
|
|
if( bVisible )
|
|
{
|
|
// Fading in
|
|
// (Time - StartTime) / FadeTime
|
|
FadingState.FadeTimeScaleBias.X = 1.0f / GFadeTime;
|
|
FadingState.FadeTimeScaleBias.Y = -CurrentRealTime / GFadeTime;
|
|
}
|
|
else
|
|
{
|
|
// Fading out
|
|
// 1 - (Time - StartTime) / FadeTime
|
|
FadingState.FadeTimeScaleBias.X = -1.0f / GFadeTime;
|
|
FadingState.FadeTimeScaleBias.Y = 1.0f + CurrentRealTime / GFadeTime;
|
|
}
|
|
|
|
FDistanceCullFadeUniformShaderParameters Uniforms;
|
|
Uniforms.FadeTimeScaleBias = FadingState.FadeTimeScaleBias;
|
|
FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate( Uniforms, UniformBuffer_MultiFrame );
|
|
}
|
|
else
|
|
{
|
|
// Reverse fading direction but maintain current opacity
|
|
// Solve for d: a*x+b = -a*x+d
|
|
FadingState.FadeTimeScaleBias.Y = 2.0f * CurrentRealTime * FadingState.FadeTimeScaleBias.X + FadingState.FadeTimeScaleBias.Y;
|
|
FadingState.FadeTimeScaleBias.X = -FadingState.FadeTimeScaleBias.X;
|
|
|
|
if( bVisible )
|
|
{
|
|
// Fading in
|
|
// Solve for x: a*x+b = 1
|
|
FadingState.EndTime = ( 1.0f - FadingState.FadeTimeScaleBias.Y ) / FadingState.FadeTimeScaleBias.X;
|
|
}
|
|
else
|
|
{
|
|
// Fading out
|
|
// Solve for x: a*x+b = 0
|
|
FadingState.EndTime = -FadingState.FadeTimeScaleBias.Y / FadingState.FadeTimeScaleBias.X;
|
|
}
|
|
|
|
FDistanceCullFadeUniformShaderParameters Uniforms;
|
|
Uniforms.FadeTimeScaleBias = FadingState.FadeTimeScaleBias;
|
|
FadingState.UniformBuffer = FDistanceCullFadeUniformBufferRef::CreateUniformBufferImmediate( Uniforms, UniformBuffer_MultiFrame );
|
|
}
|
|
}
|
|
}
|
|
|
|
FadingState.FrameNumber = View.Family->FrameNumber;
|
|
FadingState.bIsVisible = bVisible;
|
|
FadingState.bValid = true;
|
|
}
|
|
|
|
bool FViewInfo::IsDistanceCulled( float DistanceSquared, float MinDrawDistance, float InMaxDrawDistance, const FPrimitiveSceneInfo* PrimitiveSceneInfo)
|
|
{
|
|
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
|
|
float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
|
|
float MaxDrawDistance = InMaxDrawDistance * MaxDrawDistanceScale;
|
|
|
|
// If cull distance is disabled, always show (except foliage)
|
|
if (Family->EngineShowFlags.DistanceCulledPrimitives
|
|
&& !PrimitiveSceneInfo->Proxy->IsDetailMesh())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The primitive is always culled if it exceeds the max fade distance.
|
|
if (DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius) ||
|
|
DistanceSquared < FMath::Square(MinDrawDistance))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const bool bDistanceCulled = (DistanceSquared > FMath::Square(MaxDrawDistance));
|
|
const bool bMayBeFading = (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius));
|
|
|
|
bool bStillFading = false;
|
|
if( !GDisableLODFade && bMayBeFading && State != NULL && !bDisableDistanceBasedFadeTransitions )
|
|
{
|
|
// Update distance-based visibility and fading state if it has not already been updated.
|
|
int32 PrimitiveIndex = PrimitiveSceneInfo->GetIndex();
|
|
FRelativeBitReference PrimitiveBit(PrimitiveIndex);
|
|
if (PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit) == false)
|
|
{
|
|
FPrimitiveFadingState& FadingState = ((FSceneViewState*)State)->PrimitiveFadingStates.FindOrAdd(PrimitiveSceneInfo->PrimitiveComponentId);
|
|
UpdatePrimitiveFadingState(FadingState, *this, !bDistanceCulled);
|
|
FUniformBufferRHIParamRef UniformBuffer = FadingState.UniformBuffer;
|
|
bStillFading = (UniformBuffer != NULL);
|
|
PrimitiveFadeUniformBuffers[PrimitiveIndex] = UniformBuffer;
|
|
PotentiallyFadingPrimitiveMap.AccessCorrespondingBit(PrimitiveBit) = true;
|
|
}
|
|
}
|
|
|
|
// If we're still fading then make sure the object is still drawn, even if it's beyond the max draw distance
|
|
return ( bDistanceCulled && !bStillFading );
|
|
}
|
|
|
|
static int32 FrustumCullNumWordsPerTask = 128;
|
|
static FAutoConsoleVariableRef CVarFrustumCullNumWordsPerTask(
|
|
TEXT("r.FrustumCullNumWordsPerTask"),
|
|
FrustumCullNumWordsPerTask,
|
|
TEXT("Performance tweak. Controls the granularity for the ParallelFor for frustum culling."),
|
|
ECVF_Default
|
|
);
|
|
|
|
template<bool UseCustomCulling, bool bAlsoUseSphereTest>
|
|
static int32 FrustumCull(const FScene* Scene, FViewInfo& View)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FrustumCull);
|
|
|
|
FThreadSafeCounter NumCulledPrimitives;
|
|
float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale;
|
|
|
|
//Primitives per ParallelFor task
|
|
//Using async FrustumCull. Thanks Yager! See https://udn.unrealengine.com/questions/252385/performance-of-frustumcull.html
|
|
//Performance varies on total primitive count and tasks scheduled. Check the mentioned link above for some measurements.
|
|
//There have been some changes as compared to the code measured in the link
|
|
|
|
const int32 BitArrayNum = View.PrimitiveVisibilityMap.Num();
|
|
const int32 BitArrayWords = FMath::DivideAndRoundUp(View.PrimitiveVisibilityMap.Num(), (int32)NumBitsPerDWORD);
|
|
const int32 NumTasks = FMath::DivideAndRoundUp(BitArrayWords, FrustumCullNumWordsPerTask);
|
|
|
|
ParallelFor(NumTasks,
|
|
[&NumCulledPrimitives, Scene, &View, MaxDrawDistanceScale](int32 TaskIndex)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FrustumCull_Loop);
|
|
const int32 BitArrayNumInner = View.PrimitiveVisibilityMap.Num();
|
|
FVector ViewOriginForDistanceCulling = View.ViewMatrices.ViewOrigin;
|
|
float FadeRadius = GDisableLODFade ? 0.0f : GDistanceFadeMaxTravel;
|
|
uint8 CustomVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility;
|
|
|
|
const int32 TaskWordOffset = TaskIndex * FrustumCullNumWordsPerTask;
|
|
|
|
for (int32 WordIndex = TaskWordOffset; WordIndex < TaskWordOffset + FrustumCullNumWordsPerTask && WordIndex * NumBitsPerDWORD < BitArrayNumInner; WordIndex++)
|
|
{
|
|
uint32 Mask = 0x1;
|
|
uint32 VisBits = 0;
|
|
uint32 FadingBits = 0;
|
|
for (int32 BitSubIndex = 0; BitSubIndex < NumBitsPerDWORD && WordIndex * NumBitsPerDWORD + BitSubIndex < BitArrayNumInner; BitSubIndex++, Mask <<= 1)
|
|
{
|
|
int32 Index = WordIndex * NumBitsPerDWORD + BitSubIndex;
|
|
const FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[Index];
|
|
float DistanceSquared = (Bounds.Origin - ViewOriginForDistanceCulling).SizeSquared();
|
|
float MaxDrawDistance = Bounds.MaxDrawDistance * MaxDrawDistanceScale;
|
|
int32 VisibilityId = INDEX_NONE;
|
|
|
|
if (UseCustomCulling &&
|
|
((Scene->PrimitiveOcclusionFlags[Index] & CustomVisibilityFlags) == CustomVisibilityFlags))
|
|
{
|
|
VisibilityId = Scene->PrimitiveVisibilityIds[Index].ByteIndex;
|
|
}
|
|
|
|
// If cull distance is disabled, always show (except foliage)
|
|
if (View.Family->EngineShowFlags.DistanceCulledPrimitives
|
|
&& !Scene->Primitives[Index]->Proxy->IsDetailMesh())
|
|
{
|
|
MaxDrawDistance = FLT_MAX;
|
|
}
|
|
|
|
if (DistanceSquared > FMath::Square(MaxDrawDistance + FadeRadius) ||
|
|
(DistanceSquared < Bounds.MinDrawDistanceSq) ||
|
|
(UseCustomCulling && !View.CustomVisibilityQuery->IsVisible(VisibilityId, FBoxSphereBounds(Bounds.Origin, Bounds.BoxExtent, Bounds.SphereRadius))) ||
|
|
(bAlsoUseSphereTest && View.ViewFrustum.IntersectSphere(Bounds.Origin, Bounds.SphereRadius) == false) ||
|
|
View.ViewFrustum.IntersectBox(Bounds.Origin, Bounds.BoxExtent) == false)
|
|
{
|
|
STAT(NumCulledPrimitives.Increment());
|
|
}
|
|
else
|
|
{
|
|
if (DistanceSquared > FMath::Square(MaxDrawDistance))
|
|
{
|
|
FadingBits |= Mask;
|
|
}
|
|
else
|
|
{
|
|
// The primitive is visible!
|
|
VisBits |= Mask;
|
|
if (DistanceSquared > FMath::Square(MaxDrawDistance - FadeRadius))
|
|
{
|
|
FadingBits |= Mask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (FadingBits)
|
|
{
|
|
check(!View.PotentiallyFadingPrimitiveMap.GetData()[WordIndex]); // this should start at zero
|
|
View.PotentiallyFadingPrimitiveMap.GetData()[WordIndex] = FadingBits;
|
|
}
|
|
if (VisBits)
|
|
{
|
|
check(!View.PrimitiveVisibilityMap.GetData()[WordIndex]); // this should start at zero
|
|
View.PrimitiveVisibilityMap.GetData()[WordIndex] = VisBits;
|
|
}
|
|
}
|
|
},
|
|
!FApp::ShouldUseThreadingForPerformance() || (UseCustomCulling && !View.CustomVisibilityQuery->IsThreadsafe()) || CVarParallelInitViews.GetValueOnRenderThread() == 0
|
|
);
|
|
|
|
return NumCulledPrimitives.GetValue();
|
|
}
|
|
|
|
/**
|
|
* Updated primitive fading states for the view.
|
|
*/
|
|
static void UpdatePrimitiveFading(const FScene* Scene, FViewInfo& View)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveFading);
|
|
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
if (ViewState)
|
|
{
|
|
uint32 PrevFrameNumber = ViewState->PrevFrameNumber;
|
|
float CurrentRealTime = View.Family->CurrentRealTime;
|
|
|
|
// First clear any stale fading states.
|
|
for (FPrimitiveFadingStateMap::TIterator It(ViewState->PrimitiveFadingStates); It; ++It)
|
|
{
|
|
FPrimitiveFadingState& FadingState = It.Value();
|
|
if (FadingState.FrameNumber != PrevFrameNumber ||
|
|
(IsValidRef(FadingState.UniformBuffer) && CurrentRealTime >= FadingState.EndTime))
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
// Should we allow fading transitions at all this frame? For frames where the camera moved
|
|
// a large distance or where we haven't rendered a view in awhile, it's best to disable
|
|
// fading so users don't see unexpected object transitions.
|
|
if (!GDisableLODFade && !View.bDisableDistanceBasedFadeTransitions)
|
|
{
|
|
// Do a pass over potentially fading primitives and update their states.
|
|
for (FSceneSetBitIterator BitIt(View.PotentiallyFadingPrimitiveMap); BitIt; ++BitIt)
|
|
{
|
|
bool bVisible = View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt);
|
|
FPrimitiveFadingState& FadingState = ViewState->PrimitiveFadingStates.FindOrAdd(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
UpdatePrimitiveFadingState(FadingState, View, bVisible);
|
|
FUniformBufferRHIParamRef UniformBuffer = FadingState.UniformBuffer;
|
|
if (UniformBuffer && !bVisible)
|
|
{
|
|
// If the primitive is fading out make sure it remains visible.
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
View.PrimitiveFadeUniformBuffers[BitIt.GetIndex()] = UniformBuffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FOcclusionBounds
|
|
{
|
|
FOcclusionBounds(FPrimitiveOcclusionHistory* InPrimitiveOcclusionHistory, const FVector& InBoundsOrigin, const FVector& InBoundsExtent, bool bInGroupedQuery)
|
|
: PrimitiveOcclusionHistory(InPrimitiveOcclusionHistory)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
, bGroupedQuery(bInGroupedQuery)
|
|
{}
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory;
|
|
FVector BoundsOrigin;
|
|
FVector BoundsExtent;
|
|
bool bGroupedQuery;
|
|
};
|
|
|
|
struct FHZBBound
|
|
{
|
|
FHZBBound(FPrimitiveOcclusionHistory* InTargetHistory, const FVector& InBoundsOrigin, const FVector& InBoundsExtent)
|
|
: TargetHistory(InTargetHistory)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
{}
|
|
|
|
FPrimitiveOcclusionHistory* TargetHistory;
|
|
FVector BoundsOrigin;
|
|
FVector BoundsExtent;
|
|
};
|
|
|
|
#define BALANCE_LOAD 1
|
|
#define QUERY_SANITY_CHECK 0
|
|
|
|
struct FVisForPrimParams
|
|
{
|
|
FVisForPrimParams(){}
|
|
|
|
FVisForPrimParams(const FScene* InScene,
|
|
FViewInfo* InView,
|
|
FViewElementPDI* InOcclusionPDI,
|
|
const int32 InStartIndex,
|
|
const int32 InNumToProcess,
|
|
const bool bInSubmitQueries,
|
|
const bool bInHZBOcclusion,
|
|
TArray<FPrimitiveOcclusionHistory>* OutOcclusionHistory,
|
|
TArray<FPrimitiveOcclusionHistory*>* OutQueriesToRelease,
|
|
TArray<FHZBBound>* OutHZBBounds,
|
|
TArray<FOcclusionBounds>* OutQueriesToRun,
|
|
TArray<bool>* OutSubIsOccluded)
|
|
: Scene(InScene)
|
|
, View(InView)
|
|
, OcclusionPDI(InOcclusionPDI)
|
|
, StartIndex(InStartIndex)
|
|
, NumToProcess(InNumToProcess)
|
|
, bSubmitQueries(bInSubmitQueries)
|
|
, bHZBOcclusion(bInHZBOcclusion)
|
|
, InsertPrimitiveOcclusionHistory(OutOcclusionHistory)
|
|
, QueriesToRelease(OutQueriesToRelease)
|
|
, HZBBoundsToAdd(OutHZBBounds)
|
|
, QueriesToAdd(OutQueriesToRun)
|
|
, SubIsOccluded(OutSubIsOccluded)
|
|
{
|
|
|
|
}
|
|
|
|
void Init( const FScene* InScene,
|
|
FViewInfo* InView,
|
|
FViewElementPDI* InOcclusionPDI,
|
|
const int32 InStartIndex,
|
|
const int32 InNumToProcess,
|
|
const bool bInSubmitQueries,
|
|
const bool bInHZBOcclusion,
|
|
TArray<FPrimitiveOcclusionHistory>* OutOcclusionHistory,
|
|
TArray<FPrimitiveOcclusionHistory*>* OutQueriesToRelease,
|
|
TArray<FHZBBound>* OutHZBBounds,
|
|
TArray<FOcclusionBounds>* OutQueriesToRun,
|
|
TArray<bool>* OutSubIsOccluded)
|
|
|
|
{
|
|
Scene = InScene;
|
|
View = InView;
|
|
OcclusionPDI = InOcclusionPDI;
|
|
StartIndex = InStartIndex;
|
|
NumToProcess = InNumToProcess;
|
|
bSubmitQueries = bInSubmitQueries;
|
|
bHZBOcclusion = bInHZBOcclusion;
|
|
InsertPrimitiveOcclusionHistory = OutOcclusionHistory;
|
|
QueriesToRelease = OutQueriesToRelease;
|
|
HZBBoundsToAdd = OutHZBBounds;
|
|
QueriesToAdd = OutQueriesToRun;
|
|
SubIsOccluded = OutSubIsOccluded;
|
|
}
|
|
|
|
const FScene* Scene;
|
|
FViewInfo* View;
|
|
FViewElementPDI* OcclusionPDI;
|
|
int32 StartIndex;
|
|
int32 NumToProcess;
|
|
bool bSubmitQueries;
|
|
bool bHZBOcclusion;
|
|
|
|
//occlusion history to insert into. In parallel these will be all merged back into the view's history on the main thread.
|
|
//use TChunkedArray so pointers to the new FPrimitiveOcclusionHistory's won't change if the array grows.
|
|
TArray<FPrimitiveOcclusionHistory>* InsertPrimitiveOcclusionHistory;
|
|
TArray<FPrimitiveOcclusionHistory*>* QueriesToRelease;
|
|
TArray<FHZBBound>* HZBBoundsToAdd;
|
|
TArray<FOcclusionBounds>* QueriesToAdd;
|
|
int32 NumOccludedPrims;
|
|
TArray<bool>* SubIsOccluded;
|
|
};
|
|
|
|
//This function is shared between the single and multi-threaded versions. Modifications to any primitives indexed by BitIt should be ok
|
|
//since only one of the task threads will ever reference it. However, any modifications to shared state like the ViewState must be buffered
|
|
//to be recombined later.
|
|
template<bool bSingleThreaded>
|
|
static void FetchVisibilityForPrimitives_Range(FVisForPrimParams& Params)
|
|
{
|
|
int32 NumOccludedPrimitives = 0;
|
|
|
|
const FScene* Scene = Params.Scene;
|
|
FViewInfo& View = *Params.View;
|
|
FViewElementPDI* OcclusionPDI = Params.OcclusionPDI;
|
|
const int32 StartIndex = Params.StartIndex;
|
|
const int32 NumToProcess = Params.NumToProcess;
|
|
const bool bSubmitQueries = Params.bSubmitQueries;
|
|
const bool bHZBOcclusion = Params.bHZBOcclusion;
|
|
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
const int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames();
|
|
const bool bClearQueries = !View.Family->EngineShowFlags.HitProxies;
|
|
const float CurrentRealTime = View.Family->CurrentRealTime;
|
|
uint32 OcclusionFrameCounter = ViewState->OcclusionFrameCounter;
|
|
FRenderQueryPool& OcclusionQueryPool = ViewState->OcclusionQueryPool;
|
|
FHZBOcclusionTester& HZBOcclusionTests = ViewState->HZBOcclusionTests;
|
|
|
|
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& ViewPrimitiveOcclusionHistory = ViewState->PrimitiveOcclusionHistorySet;
|
|
TArray<FPrimitiveOcclusionHistory>* InsertPrimitiveOcclusionHistory = Params.InsertPrimitiveOcclusionHistory;
|
|
TArray<FPrimitiveOcclusionHistory*>* QueriesToRelease = Params.QueriesToRelease;
|
|
TArray<FHZBBound>* HZBBoundsToAdd = Params.HZBBoundsToAdd;
|
|
TArray<FOcclusionBounds>* QueriesToAdd = Params.QueriesToAdd;
|
|
|
|
const int32 ReserveAmount = NumToProcess;
|
|
if (!bSingleThreaded)
|
|
{
|
|
check(InsertPrimitiveOcclusionHistory);
|
|
check(QueriesToRelease);
|
|
check(HZBBoundsToAdd);
|
|
check(QueriesToAdd);
|
|
|
|
//avoid doing reallocs as much as possible. Unlikely to make an entry per processed element.
|
|
InsertPrimitiveOcclusionHistory->Reserve(ReserveAmount);
|
|
QueriesToRelease->Reserve(ReserveAmount);
|
|
HZBBoundsToAdd->Reserve(ReserveAmount);
|
|
QueriesToAdd->Reserve(ReserveAmount);
|
|
}
|
|
|
|
int32 NumProcessed = 0;
|
|
int32 NumTotalPrims = View.PrimitiveVisibilityMap.Num();
|
|
int32 NumTotalDefUnoccluded = View.PrimitiveDefinitelyUnoccludedMap.Num();
|
|
|
|
//if we are load balanced then we iterate only the set bits, and the ranges have been pre-selected to evenly distribute set bits among the tasks with no overlaps.
|
|
//if not, then the entire array is evenly divided by range.
|
|
#if BALANCE_LOAD
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#else
|
|
for (TBitArray<SceneRenderingBitArrayAllocator>::FIterator BitIt(View.PrimitiveVisibilityMap, StartIndex); BitIt && (NumProcessed < NumToProcess); ++BitIt, ++NumProcessed)
|
|
#endif
|
|
{
|
|
uint8 OcclusionFlags = Scene->PrimitiveOcclusionFlags[BitIt.GetIndex()];
|
|
bool bCanBeOccluded = (OcclusionFlags & EOcclusionFlags::CanBeOccluded) != 0;
|
|
|
|
#if !BALANCE_LOAD
|
|
if (!View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
//we can't allow the prim history insertion array to realloc or it will invalidate pointers in the other output arrays.
|
|
const bool bCanAllocPrimHistory = bSingleThreaded || InsertPrimitiveOcclusionHistory->Num() < InsertPrimitiveOcclusionHistory->Max();
|
|
|
|
if (GIsEditor)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[BitIt.GetIndex()];
|
|
|
|
if (PrimitiveSceneInfo->Proxy->IsSelected())
|
|
{
|
|
// to render occluded outline for selected objects
|
|
bCanBeOccluded = false;
|
|
}
|
|
}
|
|
int32 NumSubQueries = 1;
|
|
bool bSubQueries = false;
|
|
const TArray<FBoxSphereBounds>* SubBounds = nullptr;
|
|
|
|
check(Params.SubIsOccluded);
|
|
TArray<bool>& SubIsOccluded = *Params.SubIsOccluded;
|
|
int32 SubIsOccludedStart = SubIsOccluded.Num();
|
|
if ((OcclusionFlags & EOcclusionFlags::HasSubprimitiveQueries) && GAllowSubPrimitiveQueries)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = Scene->Primitives[BitIt.GetIndex()]->Proxy;
|
|
SubBounds = Proxy->GetOcclusionQueries(&View);
|
|
NumSubQueries = SubBounds->Num();
|
|
bSubQueries = true;
|
|
if (!NumSubQueries)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
continue;
|
|
}
|
|
SubIsOccluded.Reserve(NumSubQueries);
|
|
}
|
|
|
|
bool bAllSubOcclusionStateIsDefinite = true;
|
|
bool bAllSubOccluded = true;
|
|
FPrimitiveComponentId PrimitiveId = Scene->PrimitiveComponentIds[BitIt.GetIndex()];
|
|
|
|
for (int32 SubQuery = 0; SubQuery < NumSubQueries; SubQuery++)
|
|
{
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory = ViewPrimitiveOcclusionHistory.Find(FPrimitiveOcclusionHistoryKey(PrimitiveId, SubQuery));
|
|
|
|
bool bIsOccluded = false;
|
|
bool bOcclusionStateIsDefinite = false;
|
|
|
|
if (!PrimitiveOcclusionHistory)
|
|
{
|
|
// If the primitive doesn't have an occlusion history yet, create it.
|
|
if (bSingleThreaded)
|
|
{
|
|
// In singlethreaded mode we can safely modify the view's history directly.
|
|
PrimitiveOcclusionHistory = &ViewPrimitiveOcclusionHistory[
|
|
ViewPrimitiveOcclusionHistory.Add(FPrimitiveOcclusionHistory(PrimitiveId, SubQuery))
|
|
];
|
|
}
|
|
else if (bCanAllocPrimHistory)
|
|
{
|
|
// In multithreaded mode we have to buffer the new histories and add them to the view during a post-combine
|
|
PrimitiveOcclusionHistory = &(*InsertPrimitiveOcclusionHistory)[
|
|
InsertPrimitiveOcclusionHistory->Add(FPrimitiveOcclusionHistory(PrimitiveId, SubQuery))
|
|
];
|
|
}
|
|
|
|
// If the primitive hasn't been visible recently enough to have a history, treat it as unoccluded this frame so it will be rendered as an occluder and its true occlusion state can be determined.
|
|
// already set bIsOccluded = false;
|
|
|
|
// Flag the primitive's occlusion state as indefinite, which will force it to be queried this frame.
|
|
// The exception is if the primitive isn't occludable, in which case we know that it's definitely unoccluded.
|
|
bOcclusionStateIsDefinite = bCanBeOccluded ? false : true;
|
|
}
|
|
else
|
|
{
|
|
if (View.bIgnoreExistingQueries)
|
|
{
|
|
// If the view is ignoring occlusion queries, the primitive is definitely unoccluded.
|
|
// already set bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = View.bDisableQuerySubmissions;
|
|
}
|
|
else if (bCanBeOccluded)
|
|
{
|
|
if (bHZBOcclusion)
|
|
{
|
|
if (HZBOcclusionTests.IsValidFrame(PrimitiveOcclusionHistory->HZBTestFrameNumber))
|
|
{
|
|
bIsOccluded = !HZBOcclusionTests.IsVisible(PrimitiveOcclusionHistory->HZBTestIndex);
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read the occlusion query results.
|
|
uint64 NumSamples = 0;
|
|
FRenderQueryRHIRef& PastQuery = PrimitiveOcclusionHistory->GetPastQuery(OcclusionFrameCounter, NumBufferedFrames);
|
|
if (IsValidRef(PastQuery))
|
|
{
|
|
//int32 RefCount = PastQuery.GetReference()->GetRefCount();
|
|
// NOTE: RHIGetOcclusionQueryResult should never fail when using a blocking call, rendering artifacts may show up.
|
|
//if (RHICmdList.GetRenderQueryResult(PastQuery, NumSamples, true))
|
|
if (GDynamicRHI->RHIGetRenderQueryResult(PastQuery.GetReference(), NumSamples, true))
|
|
{
|
|
// we render occlusion without MSAA
|
|
uint32 NumPixels = (uint32)NumSamples;
|
|
|
|
// The primitive is occluded if none of its bounding box's pixels were visible in the previous frame's occlusion query.
|
|
bIsOccluded = (NumPixels == 0);
|
|
|
|
|
|
if (!bIsOccluded)
|
|
{
|
|
checkSlow(View.OneOverNumPossiblePixels > 0.0f);
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = NumPixels * View.OneOverNumPossiblePixels;
|
|
}
|
|
else
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f;
|
|
}
|
|
|
|
|
|
// Flag the primitive's occlusion state as definite if it wasn't grouped.
|
|
bOcclusionStateIsDefinite = !PrimitiveOcclusionHistory->bGroupedQuery;
|
|
}
|
|
else
|
|
{
|
|
// If the occlusion query failed, treat the primitive as visible.
|
|
// already set bIsOccluded = false;
|
|
}
|
|
//checkf(RefCount == PastQuery.GetReference()->GetRefCount(), TEXT("Ref count on prim: %i, old: %i, new: %i"), PrimitiveOcclusionHistory->PrimitiveId.PrimIDValue, RefCount, PastQuery.GetReference()->GetRefCount());
|
|
}
|
|
else
|
|
{
|
|
// If there's no occlusion query for the primitive, set it's visibility state to whether it has been unoccluded recently.
|
|
bIsOccluded = (PrimitiveOcclusionHistory->LastVisibleTime + GEngine->PrimitiveProbablyVisibleTime < CurrentRealTime);
|
|
|
|
|
|
|
|
if (bIsOccluded)
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
PrimitiveOcclusionHistory->LastPixelsPercentage = GEngine->MaxOcclusionPixelsFraction;
|
|
}
|
|
|
|
// the state was definite last frame, otherwise we would have ran a query
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
}
|
|
|
|
if (GVisualizeOccludedPrimitives && OcclusionPDI && bIsOccluded)
|
|
{
|
|
const FBoxSphereBounds& Bounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[BitIt.GetIndex()];
|
|
DrawWireBox(OcclusionPDI, Bounds.GetBox(), FColor(50, 255, 50), SDPG_Foreground);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitives that aren't occludable are considered definitely unoccluded.
|
|
// already set bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
|
|
if (bClearQueries)
|
|
{
|
|
if (bSingleThreaded)
|
|
{
|
|
OcclusionQueryPool.ReleaseQuery(PrimitiveOcclusionHistory->GetPastQuery(OcclusionFrameCounter, NumBufferedFrames));
|
|
}
|
|
else
|
|
{
|
|
FRenderQueryRHIRef &Query = PrimitiveOcclusionHistory->GetPastQuery(OcclusionFrameCounter, NumBufferedFrames);
|
|
if (IsValidRef(Query))
|
|
{
|
|
check(Query.GetRefCount() > 0);
|
|
QueriesToRelease->Add(PrimitiveOcclusionHistory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PrimitiveOcclusionHistory)
|
|
{
|
|
|
|
// Set the primitive's considered time to keep its occlusion history from being trimmed.
|
|
PrimitiveOcclusionHistory->LastConsideredTime = CurrentRealTime;
|
|
|
|
if (bSubmitQueries && bCanBeOccluded)
|
|
{
|
|
bool bAllowBoundsTest;
|
|
const FBoxSphereBounds& OcclusionBounds = bSubQueries ? (*SubBounds)[SubQuery] : Scene->PrimitiveOcclusionBounds[BitIt.GetIndex()];
|
|
if (View.bHasNearClippingPlane)
|
|
{
|
|
bAllowBoundsTest = View.NearClippingPlane.PlaneDot(OcclusionBounds.Origin) <
|
|
-(FVector::BoxPushOut(View.NearClippingPlane, OcclusionBounds.BoxExtent));
|
|
|
|
}
|
|
else if (!View.IsPerspectiveProjection())
|
|
{
|
|
// Transform parallel near plane
|
|
static_assert((int32)ERHIZBuffer::IsInverted != 0, "Check equation for culling!");
|
|
bAllowBoundsTest = View.WorldToScreen(OcclusionBounds.Origin).Z - View.ViewMatrices.ProjMatrix.M[2][2] * OcclusionBounds.SphereRadius < 1;
|
|
}
|
|
else
|
|
{
|
|
bAllowBoundsTest = OcclusionBounds.SphereRadius < HALF_WORLD_MAX;
|
|
}
|
|
|
|
if (bAllowBoundsTest)
|
|
{
|
|
if (bHZBOcclusion)
|
|
{
|
|
// Always run
|
|
if (bSingleThreaded)
|
|
{
|
|
PrimitiveOcclusionHistory->HZBTestIndex = HZBOcclusionTests.AddBounds(OcclusionBounds.Origin, OcclusionBounds.BoxExtent);
|
|
}
|
|
else
|
|
{
|
|
HZBBoundsToAdd->Emplace(PrimitiveOcclusionHistory, OcclusionBounds.Origin, OcclusionBounds.BoxExtent);
|
|
}
|
|
PrimitiveOcclusionHistory->HZBTestFrameNumber = OcclusionFrameCounter;
|
|
}
|
|
else
|
|
{
|
|
// decide if a query should be run this frame
|
|
bool bRunQuery, bGroupedQuery;
|
|
|
|
if (!bSubQueries && // sub queries are never grouped, we assume the custom code knows what it is doing and will group internally if it wants
|
|
(OcclusionFlags & EOcclusionFlags::AllowApproximateOcclusion))
|
|
{
|
|
if (bIsOccluded)
|
|
{
|
|
// Primitives that were occluded the previous frame use grouped queries.
|
|
bGroupedQuery = true;
|
|
bRunQuery = true;
|
|
}
|
|
else if (bOcclusionStateIsDefinite)
|
|
{
|
|
// If the primitive's is definitely unoccluded, only requery it occasionally.
|
|
float FractionMultiplier = FMath::Max(PrimitiveOcclusionHistory->LastPixelsPercentage / GEngine->MaxOcclusionPixelsFraction, 1.0f);
|
|
bRunQuery = (FractionMultiplier * GOcclusionRandomStream.GetFraction()) < GEngine->MaxOcclusionPixelsFraction;
|
|
bGroupedQuery = false;
|
|
}
|
|
else
|
|
{
|
|
bGroupedQuery = false;
|
|
bRunQuery = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitives that need precise occlusion results use individual queries.
|
|
bGroupedQuery = false;
|
|
bRunQuery = true;
|
|
}
|
|
|
|
if (bRunQuery)
|
|
{
|
|
const FVector BoundOrigin = OcclusionBounds.Origin + View.ViewMatrices.PreViewTranslation;
|
|
const FVector BoundExtent = OcclusionBounds.BoxExtent;
|
|
|
|
if (bSingleThreaded)
|
|
{
|
|
PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
bGroupedQuery ?
|
|
View.GroupedOcclusionQueries.BatchPrimitive(BoundOrigin, BoundExtent) :
|
|
View.IndividualOcclusionQueries.BatchPrimitive(BoundOrigin, BoundExtent),
|
|
NumBufferedFrames
|
|
);
|
|
}
|
|
else
|
|
{
|
|
QueriesToAdd->Emplace(PrimitiveOcclusionHistory, BoundOrigin, BoundExtent, bGroupedQuery);
|
|
}
|
|
}
|
|
|
|
PrimitiveOcclusionHistory->bGroupedQuery = bGroupedQuery;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the primitive's bounding box intersects the near clipping plane, treat it as definitely unoccluded.
|
|
bIsOccluded = false;
|
|
bOcclusionStateIsDefinite = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (bSubQueries)
|
|
{
|
|
SubIsOccluded.Add(bIsOccluded);
|
|
if (!bIsOccluded)
|
|
{
|
|
bAllSubOccluded = false;
|
|
if (bOcclusionStateIsDefinite)
|
|
{
|
|
if (PrimitiveOcclusionHistory)
|
|
{
|
|
PrimitiveOcclusionHistory->LastVisibleTime = CurrentRealTime;
|
|
}
|
|
}
|
|
}
|
|
if (bIsOccluded || !bOcclusionStateIsDefinite)
|
|
{
|
|
bAllSubOcclusionStateIsDefinite = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (bIsOccluded)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
STAT(NumOccludedPrimitives++);
|
|
}
|
|
else if (bOcclusionStateIsDefinite)
|
|
{
|
|
if (PrimitiveOcclusionHistory)
|
|
{
|
|
PrimitiveOcclusionHistory->LastVisibleTime = CurrentRealTime;
|
|
}
|
|
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSubQueries)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = Scene->Primitives[BitIt.GetIndex()]->Proxy;
|
|
Proxy->AcceptOcclusionResults(&View, &SubIsOccluded, SubIsOccludedStart, SubIsOccluded.Num() - SubIsOccludedStart);
|
|
if (bAllSubOccluded)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
STAT(NumOccludedPrimitives++);
|
|
}
|
|
else if (bAllSubOcclusionStateIsDefinite)
|
|
{
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
check(NumTotalDefUnoccluded == View.PrimitiveDefinitelyUnoccludedMap.Num());
|
|
check(NumTotalPrims == View.PrimitiveVisibilityMap.Num());
|
|
check(!InsertPrimitiveOcclusionHistory || InsertPrimitiveOcclusionHistory->Num() <= ReserveAmount);
|
|
Params.NumOccludedPrims = NumOccludedPrimitives;
|
|
}
|
|
|
|
FAutoConsoleTaskPriority CPrio_FetchVisibilityForPrimitivesTask(
|
|
TEXT("TaskGraph.TaskPriorities.FetchVisibilityForPrimitivesTask"),
|
|
TEXT("Task and thread priority for FetchVisibilityForPrimitivesTask."),
|
|
ENamedThreads::HighThreadPriority, // if we have high priority task threads, then use them...
|
|
ENamedThreads::NormalTaskPriority, // .. at normal task priority
|
|
ENamedThreads::HighTaskPriority // if we don't have hi pri threads, then use normal priority threads at high task priority instead
|
|
);
|
|
|
|
class FetchVisibilityForPrimitivesTask
|
|
{
|
|
FVisForPrimParams& Params;
|
|
|
|
public:
|
|
|
|
FetchVisibilityForPrimitivesTask(FVisForPrimParams& InParams)
|
|
: Params(InParams)
|
|
{
|
|
}
|
|
|
|
FORCEINLINE TStatId GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FetchVisibilityForPrimitivesTask, STATGROUP_TaskGraphTasks);
|
|
}
|
|
|
|
ENamedThreads::Type GetDesiredThread()
|
|
{
|
|
return CPrio_FetchVisibilityForPrimitivesTask.Get();
|
|
}
|
|
|
|
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
|
|
|
|
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
|
|
{
|
|
FetchVisibilityForPrimitives_Range<false>(Params);
|
|
}
|
|
};
|
|
|
|
static int32 FetchVisibilityForPrimitives(const FScene* Scene, FViewInfo& View, const bool bSubmitQueries, const bool bHZBOcclusion)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitives);
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
const int32 NumBufferedSubIsOccludedArrays = 2;
|
|
static int32 SubIsOccludedArrayIndex = 0;
|
|
SubIsOccludedArrayIndex = 1 - SubIsOccludedArrayIndex;
|
|
|
|
if (GOcclusionCullParallelPrimFetch && GSupportsParallelOcclusionQueries)
|
|
{
|
|
static const int32 MaxNumCullTasks = 4;
|
|
static const int32 ActualNumCullTasks = 4;
|
|
static const int32 NumOutputArrays = MaxNumCullTasks;
|
|
|
|
FGraphEventRef TaskRefArray[NumOutputArrays];
|
|
|
|
//params for each task
|
|
FVisForPrimParams Params[NumOutputArrays];
|
|
|
|
//output arrays for each task
|
|
TArray<FPrimitiveOcclusionHistory> OutputOcclusionHistory[NumOutputArrays];
|
|
TArray<FPrimitiveOcclusionHistory*> OutQueriesToRelease[NumOutputArrays];
|
|
TArray<FHZBBound> OutHZBBounds[NumOutputArrays];
|
|
TArray<FOcclusionBounds> OutQueriesToRun[NumOutputArrays];
|
|
|
|
static TArray<bool> FrameSubIsOccluded[NumOutputArrays][NumBufferedSubIsOccludedArrays];
|
|
|
|
//optionally balance the tasks by how the visible primitives are distributed in the array rather than just breaking up the array by range.
|
|
//should make the tasks more equal length.
|
|
#if BALANCE_LOAD
|
|
int32 StartIndices[NumOutputArrays] = { 0 };
|
|
int32 ProcessRange[NumOutputArrays] = { 0 };
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesPreProcess);
|
|
int32 NumBitsSet = 0;
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt, ++NumBitsSet)
|
|
{
|
|
}
|
|
|
|
int32 BitsPerTask = NumBitsSet / ActualNumCullTasks;
|
|
int32 NumBitsForRange = 0;
|
|
int32 CurrentStartIndex = 0;
|
|
int32 RangeToSet = 0;
|
|
|
|
//accumulate set bits for each task until we reach the target, then set the start/end and move on.
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt && RangeToSet < (ActualNumCullTasks - 1); ++BitIt)
|
|
{
|
|
++NumBitsForRange;
|
|
if (NumBitsForRange == BitsPerTask)
|
|
{
|
|
StartIndices[RangeToSet] = CurrentStartIndex;
|
|
ProcessRange[RangeToSet] = NumBitsForRange;
|
|
|
|
++RangeToSet;
|
|
NumBitsForRange = 0;
|
|
CurrentStartIndex = BitIt.GetIndex() + 1;
|
|
}
|
|
}
|
|
|
|
//final range is the rest of the set bits, no matter how many there are.
|
|
StartIndices[ActualNumCullTasks - 1] = CurrentStartIndex;
|
|
ProcessRange[ActualNumCullTasks - 1] = NumBitsSet - (BitsPerTask * 3);
|
|
}
|
|
#endif
|
|
|
|
const int32 NumPrims = View.PrimitiveVisibilityMap.Num();
|
|
const int32 NumPerTask = NumPrims / ActualNumCullTasks;
|
|
int32 StartIndex = 0;
|
|
int32 NumToProcess = NumPerTask;
|
|
|
|
FGraphEventArray TaskWaitArray;
|
|
int32 NumTasks = 0;
|
|
for (int32 i = 0; i < ActualNumCullTasks && (StartIndex < NumPrims); ++i, ++NumTasks)
|
|
{
|
|
NumToProcess = (i == (ActualNumCullTasks - 1)) ? (NumPrims - StartIndex) : NumPerTask;
|
|
TArray<bool>& SubIsOccluded = FrameSubIsOccluded[i][SubIsOccludedArrayIndex];
|
|
SubIsOccluded.Reset();
|
|
|
|
Params[i].Init(
|
|
Scene,
|
|
&View,
|
|
nullptr,
|
|
#if BALANCE_LOAD
|
|
StartIndices[i],
|
|
ProcessRange[i],
|
|
#else
|
|
StartIndex,
|
|
NumToProcess,
|
|
#endif
|
|
bSubmitQueries,
|
|
bHZBOcclusion,
|
|
&OutputOcclusionHistory[i],
|
|
&OutQueriesToRelease[i],
|
|
&OutHZBBounds[i],
|
|
&OutQueriesToRun[i],
|
|
&SubIsOccluded
|
|
);
|
|
|
|
TaskRefArray[i] = TGraphTask<FetchVisibilityForPrimitivesTask>::CreateTask().ConstructAndDispatchWhenReady(Params[i]);
|
|
TaskWaitArray.Add(TaskRefArray[i]);
|
|
|
|
StartIndex += NumToProcess;
|
|
}
|
|
|
|
const int32 NumBufferedFrames = FOcclusionQueryHelpers::GetNumBufferedFrames();
|
|
uint32 OcclusionFrameCounter = ViewState->OcclusionFrameCounter;
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& ViewPrimitiveOcclusionHistory = ViewState->PrimitiveOcclusionHistorySet;
|
|
FRenderQueryPool& OcclusionQueryPool = ViewState->OcclusionQueryPool;
|
|
FHZBOcclusionTester& HZBOcclusionTests = ViewState->HZBOcclusionTests;
|
|
|
|
int32 NumOccludedPrims = 0;
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesCombine);
|
|
|
|
//wait for them all so we don't start modifying the prim histories while the gather is running
|
|
FTaskGraphInterface::Get().WaitUntilTasksComplete(TaskWaitArray, ENamedThreads::RenderThread_Local);
|
|
|
|
#if QUERY_SANITY_CHECK
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FetchVisibilityForPrimitivesSanity);
|
|
TSet<int32> ReleaseQuerySet;
|
|
TSet<int32> RunQuerySet;
|
|
TSet<int32> MasterPrimsProcessed;
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
bool bAlreadyIn = false;
|
|
for (auto ReleaseQueryIter = OutQueriesToRelease[i].CreateIterator(); ReleaseQueryIter; ++ReleaseQueryIter)
|
|
{
|
|
FPrimitiveOcclusionHistory* History = *ReleaseQueryIter;
|
|
ReleaseQuerySet.Add(History->PrimitiveId.PrimIDValue, &bAlreadyIn);
|
|
checkf(!bAlreadyIn, TEXT("Prim: %i double released query."), History->PrimitiveId.PrimIDValue);
|
|
}
|
|
|
|
for (auto RunQueriesIter = OutQueriesToRun[i].CreateIterator(); RunQueriesIter; ++RunQueriesIter)
|
|
{
|
|
FPrimitiveOcclusionHistory* History = RunQueriesIter->PrimitiveOcclusionHistory;
|
|
RunQuerySet.Add(History->PrimitiveId.PrimIDValue, &bAlreadyIn);
|
|
checkf(!bAlreadyIn, TEXT("Prim: %i double run query."), History->PrimitiveId.PrimIDValue);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//Add/Release query ops use stored PrimitiveHistory pointers. We must do ALL of these from all tasks before adding any new PrimitiveHistories to the view.
|
|
//Adding new histories to the view could cause the array to resize which would invalidate all the stored output pointers for the other operations.
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
//HZB output
|
|
for (auto HZBBoundIter = OutHZBBounds[i].CreateIterator(); HZBBoundIter; ++HZBBoundIter)
|
|
{
|
|
HZBBoundIter->TargetHistory->HZBTestIndex = HZBOcclusionTests.AddBounds(HZBBoundIter->BoundsOrigin, HZBBoundIter->BoundsExtent);
|
|
}
|
|
|
|
//Manual query release handling
|
|
for (auto ReleaseQueryIter = OutQueriesToRelease[i].CreateIterator(); ReleaseQueryIter; ++ReleaseQueryIter)
|
|
{
|
|
FPrimitiveOcclusionHistory* History = *ReleaseQueryIter;
|
|
OcclusionQueryPool.ReleaseQuery(History->GetPastQuery(OcclusionFrameCounter, NumBufferedFrames));
|
|
}
|
|
|
|
//New query batching
|
|
for (auto RunQueriesIter = OutQueriesToRun[i].CreateIterator(); RunQueriesIter; ++RunQueriesIter)
|
|
{
|
|
RunQueriesIter->PrimitiveOcclusionHistory->SetCurrentQuery(OcclusionFrameCounter,
|
|
RunQueriesIter->bGroupedQuery ?
|
|
View.GroupedOcclusionQueries.BatchPrimitive(RunQueriesIter->BoundsOrigin, RunQueriesIter->BoundsExtent) :
|
|
View.IndividualOcclusionQueries.BatchPrimitive(RunQueriesIter->BoundsOrigin, RunQueriesIter->BoundsExtent),
|
|
NumBufferedFrames
|
|
);
|
|
}
|
|
}
|
|
|
|
//now add new primitivie histories to the view. may resize the view's array.
|
|
for (int32 i = 0; i < NumTasks; ++i)
|
|
{
|
|
const TArray<FPrimitiveOcclusionHistory>& NewHistoryArray = OutputOcclusionHistory[i];
|
|
for (int32 HistoryIndex = 0; HistoryIndex < NewHistoryArray.Num(); ++HistoryIndex)
|
|
{
|
|
const FPrimitiveOcclusionHistory& CopySourceHistory = NewHistoryArray[HistoryIndex];
|
|
ViewPrimitiveOcclusionHistory.Add(CopySourceHistory);
|
|
}
|
|
|
|
//accumulate occluded prims across tasks
|
|
NumOccludedPrims += Params[i].NumOccludedPrims;
|
|
}
|
|
}
|
|
|
|
return NumOccludedPrims;
|
|
}
|
|
else
|
|
{
|
|
//SubIsOccluded stuff needs a frame's lifetime
|
|
static TArray<bool> FrameSubIsOccluded[NumBufferedSubIsOccludedArrays];
|
|
|
|
TArray<bool>& SubIsOccluded = FrameSubIsOccluded[SubIsOccludedArrayIndex];
|
|
SubIsOccluded.Reset();
|
|
|
|
FViewElementPDI OcclusionPDI(&View, NULL);
|
|
int32 StartIndex = 0;
|
|
int32 NumToProcess = View.PrimitiveVisibilityMap.Num();
|
|
FVisForPrimParams Params(
|
|
Scene,
|
|
&View,
|
|
&OcclusionPDI,
|
|
StartIndex,
|
|
NumToProcess,
|
|
bSubmitQueries,
|
|
bHZBOcclusion,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
&FrameSubIsOccluded[SubIsOccludedArrayIndex]
|
|
);
|
|
|
|
FetchVisibilityForPrimitives_Range<true>(Params);
|
|
return Params.NumOccludedPrims;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cull occluded primitives in the view.
|
|
*/
|
|
static int32 OcclusionCull(FRHICommandListImmediate& RHICmdList, const FScene* Scene, FViewInfo& View)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_OcclusionCull);
|
|
|
|
// INITVIEWS_TODO: This could be more efficient if broken up in to separate concerns:
|
|
// - What is occluded?
|
|
// - For which primitives should we render occlusion queries?
|
|
// - Generate occlusion query geometry.
|
|
|
|
int32 NumOccludedPrimitives = 0;
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
// Disable HZB on OpenGL platforms to avoid rendering artefacts
|
|
// It can be forced on by setting HZBOcclusion to 2
|
|
bool bHZBOcclusion = (!IsOpenGLPlatform(GShaderPlatformForFeatureLevel[Scene->GetFeatureLevel()]) && GHZBOcclusion) || (GHZBOcclusion == 2);
|
|
|
|
// Use precomputed visibility data if it is available.
|
|
if (View.PrecomputedVisibilityData)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_LookupPrecomputedVisibility);
|
|
|
|
FViewElementPDI OcclusionPDI(&View, NULL);
|
|
uint8 PrecomputedVisibilityFlags = EOcclusionFlags::CanBeOccluded | EOcclusionFlags::HasPrecomputedVisibility;
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if ((Scene->PrimitiveOcclusionFlags[BitIt.GetIndex()] & PrecomputedVisibilityFlags) == PrecomputedVisibilityFlags)
|
|
{
|
|
FPrimitiveVisibilityId VisibilityId = Scene->PrimitiveVisibilityIds[BitIt.GetIndex()];
|
|
if ((View.PrecomputedVisibilityData[VisibilityId.ByteIndex] & VisibilityId.BitMask) == 0)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
INC_DWORD_STAT_BY(STAT_StaticallyOccludedPrimitives,1);
|
|
STAT(NumOccludedPrimitives++);
|
|
|
|
if (GVisualizeOccludedPrimitives)
|
|
{
|
|
const FBoxSphereBounds& Bounds = Scene->PrimitiveOcclusionBounds[BitIt.GetIndex()];
|
|
DrawWireBox(&OcclusionPDI, Bounds.GetBox(), FColor(100, 50, 50), SDPG_Foreground);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float CurrentRealTime = View.Family->CurrentRealTime;
|
|
if (ViewState)
|
|
{
|
|
if (Scene->GetFeatureLevel() >= ERHIFeatureLevel::SM4)
|
|
{
|
|
bool bSubmitQueries = !View.bDisableQuerySubmissions;
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
bSubmitQueries = bSubmitQueries && !ViewState->HasViewParent() && !ViewState->bIsFrozen;
|
|
#endif
|
|
|
|
if( bHZBOcclusion )
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_MapHZBResults);
|
|
check(!ViewState->HZBOcclusionTests.IsValidFrame(ViewState->OcclusionFrameCounter));
|
|
ViewState->HZBOcclusionTests.MapResults(RHICmdList);
|
|
}
|
|
|
|
NumOccludedPrimitives += FetchVisibilityForPrimitives(Scene, View, bSubmitQueries, bHZBOcclusion);
|
|
|
|
if( bHZBOcclusion )
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_HZBUnmapResults);
|
|
|
|
ViewState->HZBOcclusionTests.UnmapResults(RHICmdList);
|
|
|
|
if( bSubmitQueries )
|
|
{
|
|
ViewState->HZBOcclusionTests.SetValidFrameNumber(ViewState->OcclusionFrameCounter);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No occlusion queries, so mark primitives as not occluded
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
View.PrimitiveDefinitelyUnoccludedMap.AccessCorrespondingBit(BitIt) = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NumOccludedPrimitives;
|
|
}
|
|
|
|
template<class T, int TAmplifyFactor = 1>
|
|
struct FRelevancePrimSet
|
|
{
|
|
enum
|
|
{
|
|
MaxInputPrims = 127, //like 128, but we leave space for NumPrims
|
|
MaxOutputPrims = MaxInputPrims * TAmplifyFactor
|
|
};
|
|
int32 NumPrims;
|
|
|
|
T Prims[MaxOutputPrims];
|
|
|
|
FORCEINLINE FRelevancePrimSet()
|
|
: NumPrims(0)
|
|
{
|
|
//FMemory::Memzero(Prims, sizeof(T) * GetMaxOutputPrim());
|
|
}
|
|
FORCEINLINE void AddPrim(T Prim)
|
|
{
|
|
checkSlow(NumPrims < MaxOutputPrims);
|
|
Prims[NumPrims++] = Prim;
|
|
}
|
|
FORCEINLINE bool IsFull() const
|
|
{
|
|
return NumPrims >= MaxOutputPrims;
|
|
}
|
|
template<class TARRAY>
|
|
FORCEINLINE void AppendTo(TARRAY& DestArray)
|
|
{
|
|
DestArray.Append(Prims, NumPrims);
|
|
}
|
|
};
|
|
|
|
struct FMarkRelevantStaticMeshesForViewData
|
|
{
|
|
FVector ViewOrigin;
|
|
float MaxDrawDistanceScaleSquared;
|
|
int32 ForcedLODLevel;
|
|
float LODScale;
|
|
float InvLODScale;
|
|
float MinScreenRadiusForCSMDepthSquared;
|
|
float MinScreenRadiusForDepthPrepassSquared;
|
|
bool bForceEarlyZPass;
|
|
|
|
FMarkRelevantStaticMeshesForViewData(FViewInfo& View)
|
|
{
|
|
ViewOrigin = View.ViewMatrices.ViewOrigin;
|
|
|
|
MaxDrawDistanceScaleSquared = GetCachedScalabilityCVars().ViewDistanceScaleSquared;
|
|
|
|
// outside of the loop to be more efficient
|
|
ForcedLODLevel = (View.Family->EngineShowFlags.LOD) ? GetCVarForceLOD() : 0;
|
|
|
|
LODScale = CVarStaticMeshLODDistanceScale.GetValueOnRenderThread();
|
|
InvLODScale = 1.0f / LODScale;
|
|
|
|
MinScreenRadiusForCSMDepthSquared = GMinScreenRadiusForCSMDepth * GMinScreenRadiusForCSMDepth;
|
|
MinScreenRadiusForDepthPrepassSquared = GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass;
|
|
|
|
extern TAutoConsoleVariable<int32> CVarEarlyZPass;
|
|
bForceEarlyZPass = CVarEarlyZPass.GetValueOnRenderThread() == 2;
|
|
}
|
|
};
|
|
|
|
namespace EMarkMaskBits
|
|
{
|
|
enum Type
|
|
{
|
|
StaticMeshShadowDepthMapMask = 0x1,
|
|
StaticMeshVisibilityMapMask = 0x2,
|
|
StaticMeshVelocityMapMask = 0x4,
|
|
StaticMeshOccluderMapMask = 0x8,
|
|
StaticMeshFadeOutDitheredLODMapMask = 0x10,
|
|
StaticMeshFadeInDitheredLODMapMask = 0x20,
|
|
StaticMeshEditorSelectedMask = 0x40,
|
|
};
|
|
}
|
|
|
|
struct FRelevancePacket
|
|
{
|
|
const float CurrentWorldTime;
|
|
const float DeltaWorldTime;
|
|
|
|
FRHICommandListImmediate& RHICmdList;
|
|
const FScene* Scene;
|
|
const FViewInfo& View;
|
|
const uint8 ViewBit;
|
|
const FMarkRelevantStaticMeshesForViewData& ViewData;
|
|
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks;
|
|
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks;
|
|
uint8* RESTRICT MarkMasks;
|
|
|
|
FRelevancePrimSet<int32> Input;
|
|
FRelevancePrimSet<int32> RelevantStaticPrimitives;
|
|
FRelevancePrimSet<int32> NotDrawRelevant;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleDynamicPrimitives;
|
|
FRelevancePrimSet<FTranslucentPrimSet::FTranslucentSortedPrim, ETranslucencyPass::TPT_MAX> TranslucencyPrims;
|
|
// belongs to TranslucencyPrims
|
|
FTranslucenyPrimCount TranslucencyPrimCount;
|
|
FRelevancePrimSet<FPrimitiveSceneProxy*> DistortionPrimSet;
|
|
FRelevancePrimSet<FMeshDecalPrimSet::KeyType> MeshDecalPrimSet;
|
|
FRelevancePrimSet<FPrimitiveSceneProxy*> CustomDepthSet;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> LazyUpdatePrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> DirtyPrecomputedLightingBufferPrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleEditorPrimitives;
|
|
uint16 CombinedShadingModelMask;
|
|
bool bUsesGlobalDistanceField;
|
|
bool bUsesLightingChannels;
|
|
bool bTranslucentSurfaceLighting;
|
|
|
|
FRelevancePacket(
|
|
FRHICommandListImmediate& InRHICmdList,
|
|
const FScene* InScene,
|
|
const FViewInfo& InView,
|
|
uint8 InViewBit,
|
|
const FMarkRelevantStaticMeshesForViewData& InViewData,
|
|
FPrimitiveViewMasks& InOutHasDynamicMeshElementsMasks,
|
|
FPrimitiveViewMasks& InOutHasDynamicEditorMeshElementsMasks,
|
|
uint8* InMarkMasks)
|
|
|
|
: CurrentWorldTime(InView.Family->CurrentWorldTime)
|
|
, DeltaWorldTime(InView.Family->DeltaWorldTime)
|
|
, RHICmdList(InRHICmdList)
|
|
, Scene(InScene)
|
|
, View(InView)
|
|
, ViewBit(InViewBit)
|
|
, ViewData(InViewData)
|
|
, OutHasDynamicMeshElementsMasks(InOutHasDynamicMeshElementsMasks)
|
|
, OutHasDynamicEditorMeshElementsMasks(InOutHasDynamicEditorMeshElementsMasks)
|
|
, MarkMasks(InMarkMasks)
|
|
, CombinedShadingModelMask(0)
|
|
, bUsesGlobalDistanceField(false)
|
|
, bUsesLightingChannels(false)
|
|
, bTranslucentSurfaceLighting(false)
|
|
{
|
|
}
|
|
|
|
void AnyThreadTask()
|
|
{
|
|
ComputeRelevance();
|
|
MarkRelevant();
|
|
}
|
|
|
|
void ComputeRelevance()
|
|
{
|
|
CombinedShadingModelMask = 0;
|
|
bUsesGlobalDistanceField = false;
|
|
bUsesLightingChannels = false;
|
|
bTranslucentSurfaceLighting = false;
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_ComputeViewRelevance);
|
|
for (int32 Index = 0; Index < Input.NumPrims; Index++)
|
|
{
|
|
int32 BitIndex = Input.Prims[Index];
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[BitIndex];
|
|
FPrimitiveViewRelevance& ViewRelevance = const_cast<FPrimitiveViewRelevance&>(View.PrimitiveViewRelevanceMap[BitIndex]);
|
|
ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
|
|
ViewRelevance.bInitializedThisFrame = true;
|
|
|
|
const bool bStaticRelevance = ViewRelevance.bStaticRelevance;
|
|
const bool bDrawRelevance = ViewRelevance.bDrawRelevance;
|
|
const bool bDynamicRelevance = ViewRelevance.bDynamicRelevance;
|
|
const bool bShadowRelevance = ViewRelevance.bShadowRelevance;
|
|
const bool bEditorRelevance = ViewRelevance.bEditorPrimitiveRelevance;
|
|
const bool bEditorSelectionRelevance = ViewRelevance.bEditorStaticSelectionRelevance;
|
|
const bool bTranslucentRelevance = ViewRelevance.HasTranslucency();
|
|
|
|
if (View.bIsReflectionCapture && !PrimitiveSceneInfo->Proxy->IsVisibleInReflectionCaptures())
|
|
{
|
|
NotDrawRelevant.AddPrim(BitIndex);
|
|
continue;
|
|
}
|
|
|
|
if (bStaticRelevance && (bDrawRelevance || bShadowRelevance))
|
|
{
|
|
RelevantStaticPrimitives.AddPrim(BitIndex);
|
|
}
|
|
|
|
if (!bDrawRelevance)
|
|
{
|
|
NotDrawRelevant.AddPrim(BitIndex);
|
|
continue;
|
|
}
|
|
|
|
if (ViewRelevance.bDecal)
|
|
{
|
|
MeshDecalPrimSet.AddPrim(FMeshDecalPrimSet::GenerateKey(PrimitiveSceneInfo));
|
|
}
|
|
|
|
if (bEditorRelevance)
|
|
{
|
|
// Editor primitives are rendered after post processing and composited onto the scene
|
|
VisibleEditorPrimitives.AddPrim(PrimitiveSceneInfo);
|
|
|
|
if (GIsEditor)
|
|
{
|
|
OutHasDynamicEditorMeshElementsMasks[BitIndex] |= ViewBit;
|
|
}
|
|
}
|
|
else if(bDynamicRelevance)
|
|
{
|
|
// Keep track of visible dynamic primitives.
|
|
VisibleDynamicPrimitives.AddPrim(PrimitiveSceneInfo);
|
|
OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;
|
|
}
|
|
|
|
if (bTranslucentRelevance && !bEditorRelevance && ViewRelevance.bRenderInMainPass)
|
|
{
|
|
// Add to set of dynamic translucent primitives
|
|
FTranslucentPrimSet::PlaceScenePrimitive(PrimitiveSceneInfo, View,
|
|
ViewRelevance.bNormalTranslucencyRelevance, ViewRelevance.bSeparateTranslucencyRelevance, ViewRelevance.bMobileSeparateTranslucencyRelevance,
|
|
&TranslucencyPrims.Prims[0], TranslucencyPrims.NumPrims, TranslucencyPrimCount);
|
|
|
|
if (ViewRelevance.bDistortionRelevance)
|
|
{
|
|
// Add to set of dynamic distortion primitives
|
|
DistortionPrimSet.AddPrim(PrimitiveSceneInfo->Proxy);
|
|
}
|
|
}
|
|
|
|
CombinedShadingModelMask |= ViewRelevance.ShadingModelMaskRelevance;
|
|
bUsesGlobalDistanceField |= ViewRelevance.bUsesGlobalDistanceField;
|
|
bUsesLightingChannels |= ViewRelevance.bUsesLightingChannels;
|
|
bTranslucentSurfaceLighting |= ViewRelevance.bTranslucentSurfaceLighting;
|
|
|
|
if (ViewRelevance.bRenderCustomDepth)
|
|
{
|
|
// Add to set of dynamic distortion primitives
|
|
CustomDepthSet.AddPrim(PrimitiveSceneInfo->Proxy);
|
|
}
|
|
|
|
// INITVIEWS_TODO: Do this in a separate pass? There are no dependencies
|
|
// here except maybe ParentPrimitives. This could be done in a
|
|
// low-priority background task and forgotten about.
|
|
|
|
// If the primitive's last render time is older than last frame, consider
|
|
// it newly visible and update its visibility change time
|
|
if (PrimitiveSceneInfo->LastRenderTime < CurrentWorldTime - DeltaWorldTime - DELTA)
|
|
{
|
|
PrimitiveSceneInfo->LastVisibilityChangeTime = CurrentWorldTime;
|
|
}
|
|
PrimitiveSceneInfo->LastRenderTime = CurrentWorldTime;
|
|
|
|
// If the primitive is definitely unoccluded or if in Wireframe mode and the primitive is estimated
|
|
// to be unoccluded, then update the primitive components's LastRenderTime
|
|
// on the game thread. This signals that the primitive is visible.
|
|
if (View.PrimitiveDefinitelyUnoccludedMap[BitIndex] || (View.Family->EngineShowFlags.Wireframe && View.PrimitiveVisibilityMap[BitIndex]))
|
|
{
|
|
// Update the PrimitiveComponent's LastRenderTime.
|
|
*(PrimitiveSceneInfo->ComponentLastRenderTime) = CurrentWorldTime;
|
|
*(PrimitiveSceneInfo->ComponentLastRenderTimeOnScreen) = CurrentWorldTime;
|
|
}
|
|
|
|
// Cache the nearest reflection proxy if needed
|
|
if (PrimitiveSceneInfo->bNeedsCachedReflectionCaptureUpdate
|
|
// For mobile, the per-object reflection is used for everything
|
|
&& (Scene->GetShadingPath() == EShadingPath::Mobile || bTranslucentRelevance || IsForwardShadingEnabled(Scene->GetFeatureLevel())))
|
|
{
|
|
PrimitiveSceneInfo->CachedReflectionCaptureProxy = Scene->FindClosestReflectionCapture(Scene->PrimitiveBounds[BitIndex].Origin);
|
|
PrimitiveSceneInfo->CachedPlanarReflectionProxy = Scene->FindClosestPlanarReflection(Scene->PrimitiveBounds[BitIndex]);
|
|
|
|
if (Scene->GetShadingPath() == EShadingPath::Mobile)
|
|
{
|
|
// mobile HQ reflections
|
|
Scene->FindClosestReflectionCaptures(Scene->PrimitiveBounds[BitIndex].Origin, PrimitiveSceneInfo->CachedReflectionCaptureProxies);
|
|
}
|
|
|
|
PrimitiveSceneInfo->bNeedsCachedReflectionCaptureUpdate = false;
|
|
}
|
|
if (PrimitiveSceneInfo->NeedsLazyUpdateForRendering())
|
|
{
|
|
LazyUpdatePrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
if (PrimitiveSceneInfo->NeedsPrecomputedLightingBufferUpdate())
|
|
{
|
|
DirtyPrecomputedLightingBufferPrimitives.AddPrim(PrimitiveSceneInfo);
|
|
}
|
|
}
|
|
}
|
|
void MarkRelevant()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_StaticRelevance);
|
|
|
|
// using a local counter to reduce memory traffic
|
|
int32 NumVisibleStaticMeshElements = 0;
|
|
FViewInfo& WriteView = const_cast<FViewInfo&>(View);
|
|
FFrozenSceneViewMatricesGuard FrozenMatricesGuard(WriteView);
|
|
|
|
const bool bHLODActive = Scene->SceneLODHierarchy.IsActive();
|
|
|
|
for (int32 StaticPrimIndex = 0, Num = RelevantStaticPrimitives.NumPrims; StaticPrimIndex < Num; ++StaticPrimIndex)
|
|
{
|
|
int32 PrimitiveIndex = RelevantStaticPrimitives.Prims[StaticPrimIndex];
|
|
const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];
|
|
const FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[PrimitiveIndex];
|
|
const FPrimitiveViewRelevance& ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveIndex];
|
|
|
|
FLODMask LODToRender = ComputeLODForMeshes( PrimitiveSceneInfo->StaticMeshes, View, Bounds.Origin, Bounds.SphereRadius, ViewData.ForcedLODLevel, ViewData.LODScale);
|
|
const bool bIsHLODFading = bHLODActive && Scene->SceneLODHierarchy.IsNodeFading(PrimitiveIndex);
|
|
const bool bIsHLODFadingOut = bHLODActive && Scene->SceneLODHierarchy.IsNodeFadingOut(PrimitiveIndex);
|
|
const bool bIsLODDithered = LODToRender.IsDithered();
|
|
|
|
float DistanceSquared = (Bounds.Origin - ViewData.ViewOrigin).SizeSquared();
|
|
const float LODFactorDistanceSquared = DistanceSquared * FMath::Square(View.LODDistanceFactor * ViewData.InvLODScale);
|
|
const bool bDrawShadowDepth = FMath::Square(Bounds.SphereRadius) > ViewData.MinScreenRadiusForCSMDepthSquared * LODFactorDistanceSquared;
|
|
const bool bDrawDepthOnly = ViewData.bForceEarlyZPass || FMath::Square(Bounds.SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared;
|
|
|
|
const int32 NumStaticMeshes = PrimitiveSceneInfo->StaticMeshes.Num();
|
|
for(int32 MeshIndex = 0;MeshIndex < NumStaticMeshes;MeshIndex++)
|
|
{
|
|
const FStaticMesh& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
|
|
if (LODToRender.ContainsLOD(StaticMesh.LODIndex))
|
|
{
|
|
uint8 MarkMask = 0;
|
|
bool bNeedsBatchVisibility = false;
|
|
bool bHiddenByHLODFade = false; // Hide mesh LOD levels that HLOD is substituting
|
|
|
|
if (bIsHLODFading)
|
|
{
|
|
if (bIsHLODFadingOut)
|
|
{
|
|
if (bIsLODDithered && LODToRender.DitheredLODIndices[1] == StaticMesh.LODIndex)
|
|
{
|
|
bHiddenByHLODFade = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bIsLODDithered && LODToRender.DitheredLODIndices[0] == StaticMesh.LODIndex)
|
|
{
|
|
bHiddenByHLODFade = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
|
|
}
|
|
}
|
|
}
|
|
else if (bIsLODDithered)
|
|
{
|
|
if (LODToRender.DitheredLODIndices[0] == StaticMesh.LODIndex)
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
|
|
}
|
|
else
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
|
|
}
|
|
}
|
|
|
|
if (ViewRelevance.bShadowRelevance && bDrawShadowDepth && StaticMesh.CastShadow)
|
|
{
|
|
// Mark static mesh as visible in shadows.
|
|
MarkMask |= EMarkMaskBits::StaticMeshShadowDepthMapMask;
|
|
bNeedsBatchVisibility = true;
|
|
}
|
|
|
|
if(ViewRelevance.bDrawRelevance && (StaticMesh.bUseForMaterial || StaticMesh.bUseAsOccluder) && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth) && !bHiddenByHLODFade)
|
|
{
|
|
// Mark static mesh as visible for rendering
|
|
if (StaticMesh.bUseForMaterial)
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;
|
|
if (PrimitiveSceneInfo->ShouldRenderVelocity(View, false))
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshVelocityMapMask;
|
|
}
|
|
++NumVisibleStaticMeshElements;
|
|
}
|
|
|
|
// If the static mesh is an occluder, check whether it covers enough of the screen to be used as an occluder.
|
|
if( StaticMesh.bUseAsOccluder && bDrawDepthOnly )
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshOccluderMapMask;
|
|
}
|
|
bNeedsBatchVisibility = true;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if(ViewRelevance.bDrawRelevance && ViewRelevance.bEditorStaticSelectionRelevance)
|
|
{
|
|
MarkMask |= EMarkMaskBits::StaticMeshEditorSelectedMask;
|
|
}
|
|
#endif
|
|
if (MarkMask)
|
|
{
|
|
MarkMasks[StaticMesh.Id] = MarkMask;
|
|
}
|
|
|
|
// Static meshes which don't need per-element visibility always draw all elements
|
|
if (bNeedsBatchVisibility && StaticMesh.bRequiresPerElementVisibility)
|
|
{
|
|
WriteView.StaticMeshBatchVisibility[StaticMesh.Id] = StaticMesh.VertexFactory->GetStaticBatchElementVisibility(View, &StaticMesh);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static_assert(sizeof(WriteView.NumVisibleStaticMeshElements) == sizeof(int32), "Atomic is the wrong size");
|
|
FPlatformAtomics::InterlockedAdd((volatile int32*)&WriteView.NumVisibleStaticMeshElements, NumVisibleStaticMeshElements);
|
|
}
|
|
|
|
void RenderThreadFinalize()
|
|
{
|
|
FViewInfo& WriteView = const_cast<FViewInfo&>(View);
|
|
|
|
for (int32 Index = 0; Index < NotDrawRelevant.NumPrims; Index++)
|
|
{
|
|
WriteView.PrimitiveVisibilityMap[NotDrawRelevant.Prims[Index]] = false;
|
|
}
|
|
|
|
WriteView.ShadingModelMaskInView |= CombinedShadingModelMask;
|
|
WriteView.bUsesGlobalDistanceField |= bUsesGlobalDistanceField;
|
|
WriteView.bUsesLightingChannels |= bUsesLightingChannels;
|
|
WriteView.bTranslucentSurfaceLighting |= bTranslucentSurfaceLighting;
|
|
VisibleEditorPrimitives.AppendTo(WriteView.VisibleEditorPrimitives);
|
|
VisibleDynamicPrimitives.AppendTo(WriteView.VisibleDynamicPrimitives);
|
|
WriteView.TranslucentPrimSet.AppendScenePrimitives(TranslucencyPrims.Prims, TranslucencyPrims.NumPrims, TranslucencyPrimCount);
|
|
DistortionPrimSet.AppendTo(WriteView.DistortionPrimSet);
|
|
MeshDecalPrimSet.AppendTo(WriteView.MeshDecalPrimSet.Prims);
|
|
CustomDepthSet.AppendTo(WriteView.CustomDepthSet);
|
|
DirtyPrecomputedLightingBufferPrimitives.AppendTo(WriteView.DirtyPrecomputedLightingBufferPrimitives);
|
|
for (int32 Index = 0; Index < LazyUpdatePrimitives.NumPrims; Index++)
|
|
{
|
|
LazyUpdatePrimitives.Prims[Index]->ConditionalLazyUpdateForRendering(RHICmdList);
|
|
}
|
|
}
|
|
};
|
|
|
|
static void ComputeAndMarkRelevanceForViewParallel(
|
|
FRHICommandListImmediate& RHICmdList,
|
|
const FScene* Scene,
|
|
FViewInfo& View,
|
|
uint8 ViewBit,
|
|
FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks,
|
|
FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks
|
|
)
|
|
{
|
|
check(OutHasDynamicMeshElementsMasks.Num() == Scene->Primitives.Num());
|
|
|
|
const FMarkRelevantStaticMeshesForViewData ViewData(View);
|
|
|
|
int32 NumMesh = View.StaticMeshVisibilityMap.Num();
|
|
check(View.StaticMeshShadowDepthMap.Num() == NumMesh && View.StaticMeshVelocityMap.Num() == NumMesh && View.StaticMeshOccluderMap.Num() == NumMesh);
|
|
uint8* RESTRICT MarkMasks = (uint8*)FMemStack::Get().Alloc(NumMesh + 31 , 8); // some padding to simplify the high speed transpose
|
|
FMemory::Memzero(MarkMasks, NumMesh + 31);
|
|
|
|
int32 EstimateOfNumPackets = NumMesh / (FRelevancePrimSet<int32>::MaxInputPrims * 4);
|
|
|
|
TArray<FRelevancePacket*,SceneRenderingAllocator> Packets;
|
|
|
|
Packets.Reserve(EstimateOfNumPackets);
|
|
|
|
{
|
|
FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap);
|
|
if (BitIt)
|
|
{
|
|
|
|
FRelevancePacket* Packet = new(FMemStack::Get()) FRelevancePacket(
|
|
RHICmdList,
|
|
Scene,
|
|
View,
|
|
ViewBit,
|
|
ViewData,
|
|
OutHasDynamicMeshElementsMasks,
|
|
OutHasDynamicEditorMeshElementsMasks,
|
|
MarkMasks);
|
|
Packets.Add(Packet);
|
|
|
|
while (1)
|
|
{
|
|
Packet->Input.AddPrim(BitIt.GetIndex());
|
|
++BitIt;
|
|
if (Packet->Input.IsFull() || !BitIt)
|
|
{
|
|
if (!BitIt)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Packet = new(FMemStack::Get()) FRelevancePacket(
|
|
RHICmdList,
|
|
Scene,
|
|
View,
|
|
ViewBit,
|
|
ViewData,
|
|
OutHasDynamicMeshElementsMasks,
|
|
OutHasDynamicEditorMeshElementsMasks,
|
|
MarkMasks);
|
|
Packets.Add(Packet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_ParallelFor);
|
|
ParallelFor(Packets.Num(),
|
|
[&Packets](int32 Index)
|
|
{
|
|
Packets[Index]->AnyThreadTask();
|
|
},
|
|
!(FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0)
|
|
);
|
|
}
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_RenderThreadFinalize);
|
|
for (auto Packet : Packets)
|
|
{
|
|
Packet->RenderThreadFinalize();
|
|
}
|
|
}
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits);
|
|
check(View.StaticMeshVelocityMap.Num() == NumMesh &&
|
|
View.StaticMeshShadowDepthMap.Num() == NumMesh &&
|
|
View.StaticMeshVisibilityMap.Num() == NumMesh &&
|
|
View.StaticMeshOccluderMap.Num() == NumMesh &&
|
|
View.StaticMeshFadeOutDitheredLODMap.Num() == NumMesh &&
|
|
View.StaticMeshFadeInDitheredLODMap.Num() == NumMesh
|
|
);
|
|
uint32* RESTRICT StaticMeshVisibilityMap_Words = View.StaticMeshVisibilityMap.GetData();
|
|
uint32* RESTRICT StaticMeshVelocityMap_Words = View.StaticMeshVelocityMap.GetData();
|
|
uint32* RESTRICT StaticMeshShadowDepthMap_Words = View.StaticMeshShadowDepthMap.GetData();
|
|
uint32* RESTRICT StaticMeshOccluderMap_Words = View.StaticMeshOccluderMap.GetData();
|
|
uint32* RESTRICT StaticMeshFadeOutDitheredLODMap_Words = View.StaticMeshFadeOutDitheredLODMap.GetData();
|
|
uint32* RESTRICT StaticMeshFadeInDitheredLODMap_Words = View.StaticMeshFadeInDitheredLODMap.GetData();
|
|
#if WITH_EDITOR
|
|
uint32* RESTRICT StaticMeshEditorSelectionMap_Words = View.StaticMeshEditorSelectionMap.GetData();
|
|
#endif
|
|
const uint64* RESTRICT MarkMasks64 = (const uint64* RESTRICT)MarkMasks;
|
|
const uint8* RESTRICT MarkMasks8 = MarkMasks;
|
|
for (int32 BaseIndex = 0; BaseIndex < NumMesh; BaseIndex += 32)
|
|
{
|
|
uint32 StaticMeshVisibilityMap_Word = 0;
|
|
uint32 StaticMeshVelocityMap_Word = 0;
|
|
uint32 StaticMeshShadowDepthMap_Word = 0;
|
|
uint32 StaticMeshOccluderMap_Word = 0;
|
|
uint32 StaticMeshFadeOutDitheredLODMap_Word = 0;
|
|
uint32 StaticMeshFadeInDitheredLODMap_Word = 0;
|
|
uint32 StaticMeshEditorSelectionMap_Word = 0;
|
|
uint32 Mask = 1;
|
|
bool bAny = false;
|
|
for (int32 QWordIndex = 0; QWordIndex < 4; QWordIndex++)
|
|
{
|
|
if (*MarkMasks64++)
|
|
{
|
|
for (int32 ByteIndex = 0; ByteIndex < 8; ByteIndex++, Mask <<= 1, MarkMasks8++)
|
|
{
|
|
uint8 MaskMask = *MarkMasks8;
|
|
StaticMeshVisibilityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVisibilityMapMask) ? Mask : 0;
|
|
StaticMeshVelocityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVelocityMapMask) ? Mask : 0;
|
|
StaticMeshShadowDepthMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshShadowDepthMapMask) ? Mask : 0;
|
|
StaticMeshOccluderMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshOccluderMapMask) ? Mask : 0;
|
|
StaticMeshFadeOutDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask) ? Mask : 0;
|
|
StaticMeshFadeInDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask) ? Mask : 0;
|
|
#if WITH_EDITOR
|
|
StaticMeshEditorSelectionMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshEditorSelectedMask) ? Mask : 0;
|
|
#endif
|
|
}
|
|
bAny = true;
|
|
}
|
|
else
|
|
{
|
|
MarkMasks8 += 8;
|
|
Mask <<= 8;
|
|
}
|
|
}
|
|
if (bAny)
|
|
{
|
|
checkSlow(!*StaticMeshVisibilityMap_Words && !*StaticMeshVelocityMap_Words && !*StaticMeshShadowDepthMap_Words && !*StaticMeshOccluderMap_Words && !*StaticMeshFadeOutDitheredLODMap_Words && !*StaticMeshFadeInDitheredLODMap_Words);
|
|
*StaticMeshVisibilityMap_Words = StaticMeshVisibilityMap_Word;
|
|
*StaticMeshVelocityMap_Words = StaticMeshVelocityMap_Word;
|
|
*StaticMeshShadowDepthMap_Words = StaticMeshShadowDepthMap_Word;
|
|
*StaticMeshOccluderMap_Words = StaticMeshOccluderMap_Word;
|
|
*StaticMeshFadeOutDitheredLODMap_Words = StaticMeshFadeOutDitheredLODMap_Word;
|
|
*StaticMeshFadeInDitheredLODMap_Words = StaticMeshFadeInDitheredLODMap_Word;
|
|
#if WITH_EDITOR
|
|
*StaticMeshEditorSelectionMap_Words = StaticMeshEditorSelectionMap_Word;
|
|
#endif
|
|
}
|
|
StaticMeshVisibilityMap_Words++;
|
|
StaticMeshVelocityMap_Words++;
|
|
StaticMeshShadowDepthMap_Words++;
|
|
StaticMeshOccluderMap_Words++;
|
|
StaticMeshFadeOutDitheredLODMap_Words++;
|
|
StaticMeshFadeInDitheredLODMap_Words++;
|
|
#if WITH_EDITOR
|
|
StaticMeshEditorSelectionMap_Words++;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void FSceneRenderer::GatherDynamicMeshElements(
|
|
TArray<FViewInfo>& InViews,
|
|
const FScene* InScene,
|
|
const FSceneViewFamily& InViewFamily,
|
|
const FPrimitiveViewMasks& HasDynamicMeshElementsMasks,
|
|
const FPrimitiveViewMasks& HasDynamicEditorMeshElementsMasks,
|
|
FMeshElementCollector& Collector)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements);
|
|
|
|
int32 NumPrimitives = InScene->Primitives.Num();
|
|
check(HasDynamicMeshElementsMasks.Num() == NumPrimitives);
|
|
|
|
int32 ViewCount = InViews.Num();
|
|
{
|
|
Collector.ClearViewMeshArrays();
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
Collector.AddViewMeshArrays(&InViews[ViewIndex], &InViews[ViewIndex].DynamicMeshElements, &InViews[ViewIndex].SimpleElementCollector, InViewFamily.GetFeatureLevel());
|
|
}
|
|
|
|
const bool bIsInstancedStereo = (ViewCount > 0) ? InViews[0].IsInstancedStereoPass() : false;
|
|
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
|
|
{
|
|
const uint8 ViewMask = HasDynamicMeshElementsMasks[PrimitiveIndex];
|
|
|
|
if (ViewMask != 0)
|
|
{
|
|
// Don't cull a single eye when drawing a stereo pair
|
|
const uint8 ViewMaskFinal = (bIsInstancedStereo) ? ViewMask | 0x3 : ViewMask;
|
|
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = InScene->Primitives[PrimitiveIndex];
|
|
Collector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
|
|
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMaskFinal, Collector);
|
|
}
|
|
|
|
// to support GetDynamicMeshElementRange()
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
InViews[ViewIndex].DynamicMeshEndIndices[PrimitiveIndex] = Collector.GetMeshBatchCount(ViewIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
Collector.ClearViewMeshArrays();
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ViewIndex++)
|
|
{
|
|
Collector.AddViewMeshArrays(&InViews[ViewIndex], &InViews[ViewIndex].DynamicEditorMeshElements, &InViews[ViewIndex].EditorSimpleElementCollector, InViewFamily.GetFeatureLevel());
|
|
}
|
|
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < NumPrimitives; ++PrimitiveIndex)
|
|
{
|
|
const uint8 ViewMask = HasDynamicEditorMeshElementsMasks[PrimitiveIndex];
|
|
|
|
if (ViewMask != 0)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = InScene->Primitives[PrimitiveIndex];
|
|
Collector.SetPrimitive(PrimitiveSceneInfo->Proxy, PrimitiveSceneInfo->DefaultDynamicHitProxyId);
|
|
PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMask, Collector);
|
|
}
|
|
}
|
|
}
|
|
MeshCollector.ProcessTasks();
|
|
}
|
|
|
|
static void MarkAllPrimitivesForReflectionProxyUpdate(FScene* Scene)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_MarkAllPrimitivesForReflectionProxyUpdate);
|
|
|
|
if (Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged)
|
|
{
|
|
// Mark all primitives as needing an update
|
|
// Note: Only visible primitives will actually update their reflection proxy
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Scene->Primitives.Num(); PrimitiveIndex++)
|
|
{
|
|
Scene->Primitives[PrimitiveIndex]->bNeedsCachedReflectionCaptureUpdate = true;
|
|
}
|
|
|
|
Scene->ReflectionSceneData.bRegisteredReflectionCapturesHasChanged = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper for InitViews to detect large camera movement, in both angle and position.
|
|
*/
|
|
static bool IsLargeCameraMovement(FSceneView& View, const FMatrix& PrevViewMatrix, const FVector& PrevViewOrigin, float CameraRotationThreshold, float CameraTranslationThreshold)
|
|
{
|
|
float RotationThreshold = FMath::Cos(CameraRotationThreshold * PI / 180.0f);
|
|
float ViewRightAngle = View.ViewMatrices.ViewMatrix.GetColumn(0) | PrevViewMatrix.GetColumn(0);
|
|
float ViewUpAngle = View.ViewMatrices.ViewMatrix.GetColumn(1) | PrevViewMatrix.GetColumn(1);
|
|
float ViewDirectionAngle = View.ViewMatrices.ViewMatrix.GetColumn(2) | PrevViewMatrix.GetColumn(2);
|
|
|
|
FVector Distance = FVector(View.ViewMatrices.ViewOrigin) - PrevViewOrigin;
|
|
return
|
|
ViewRightAngle < RotationThreshold ||
|
|
ViewUpAngle < RotationThreshold ||
|
|
ViewDirectionAngle < RotationThreshold ||
|
|
Distance.SizeSquared() > CameraTranslationThreshold * CameraTranslationThreshold;
|
|
}
|
|
|
|
float Halton( int32 Index, int32 Base )
|
|
{
|
|
float Result = 0.0f;
|
|
float InvBase = 1.0f / Base;
|
|
float Fraction = InvBase;
|
|
while( Index > 0 )
|
|
{
|
|
Result += ( Index % Base ) * Fraction;
|
|
Index /= Base;
|
|
Fraction *= InvBase;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void FSceneRenderer::PreVisibilityFrameSetup(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
// Notify the RHI we are beginning to render a scene.
|
|
RHICmdList.BeginScene();
|
|
|
|
// Notify the FX system that the scene is about to perform visibility checks.
|
|
if (Scene->FXSystem && !Views[0].bIsPlanarReflection)
|
|
{
|
|
Scene->FXSystem->PreInitViews();
|
|
}
|
|
|
|
// Draw lines to lights affecting this mesh if its selected.
|
|
if (ViewFamily.EngineShowFlags.LightInfluences)
|
|
{
|
|
for (TArray<FPrimitiveSceneInfo*>::TConstIterator It(Scene->Primitives); It; ++It)
|
|
{
|
|
const FPrimitiveSceneInfo* PrimitiveSceneInfo = *It;
|
|
if (PrimitiveSceneInfo->Proxy->IsSelected())
|
|
{
|
|
FLightPrimitiveInteraction *LightList = PrimitiveSceneInfo->LightList;
|
|
while (LightList)
|
|
{
|
|
const FLightSceneInfo* LightSceneInfo = LightList->GetLight();
|
|
|
|
bool bDynamic = true;
|
|
bool bRelevant = false;
|
|
bool bLightMapped = true;
|
|
bool bShadowMapped = false;
|
|
PrimitiveSceneInfo->Proxy->GetLightRelevance(LightSceneInfo->Proxy, bDynamic, bRelevant, bLightMapped, bShadowMapped);
|
|
|
|
if (bRelevant)
|
|
{
|
|
// Draw blue for light-mapped lights and orange for dynamic lights
|
|
const FColor LineColor = bLightMapped ? FColor(0,140,255) : FColor(255,140,0);
|
|
for (int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FViewElementPDI LightInfluencesPDI(&View,NULL);
|
|
LightInfluencesPDI.DrawLine(PrimitiveSceneInfo->Proxy->GetBounds().Origin, LightSceneInfo->Proxy->GetLightToWorld().GetOrigin(), LineColor, SDPG_World);
|
|
}
|
|
}
|
|
LightList = LightList->GetNextLight();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Setup motion blur parameters (also check for camera movement thresholds)
|
|
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FSceneViewState* ViewState = View.ViewState;
|
|
|
|
// Once per render increment the occlusion frame counter.
|
|
if (ViewState)
|
|
{
|
|
ViewState->OcclusionFrameCounter++;
|
|
}
|
|
|
|
// HighResScreenshot should get best results so we don't do the occlusion optimization based on the former frame
|
|
extern bool GIsHighResScreenshot;
|
|
const bool bIsHitTesting = ViewFamily.EngineShowFlags.HitProxies;
|
|
if (GIsHighResScreenshot || !DoOcclusionQueries(FeatureLevel) || bIsHitTesting)
|
|
{
|
|
View.bDisableQuerySubmissions = true;
|
|
View.bIgnoreExistingQueries = true;
|
|
}
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
|
|
// set up the screen area for occlusion
|
|
float NumPossiblePixels = SceneContext.UseDownsizedOcclusionQueries() && IsValidRef(SceneContext.GetSmallDepthSurface()) ?
|
|
(float)View.ViewRect.Width() / SceneContext.GetSmallColorDepthDownsampleFactor() * (float)View.ViewRect.Height() / SceneContext.GetSmallColorDepthDownsampleFactor() :
|
|
View.ViewRect.Width() * View.ViewRect.Height();
|
|
View.OneOverNumPossiblePixels = NumPossiblePixels > 0.0 ? 1.0f / NumPossiblePixels : 0.0f;
|
|
|
|
// Still need no jitter to be set for temporal feedback on SSR (it is enabled even when temporal AA is off).
|
|
View.TemporalJitterPixelsX = 0.0f;
|
|
View.TemporalJitterPixelsY = 0.0f;
|
|
|
|
if (ViewState)
|
|
{
|
|
ViewState->SetupDistanceFieldTemporalOffset(ViewFamily);
|
|
}
|
|
|
|
if( View.AntiAliasingMethod == AAM_TemporalAA && ViewState )
|
|
{
|
|
// Subpixel jitter for temporal AA
|
|
int32 TemporalAASamples = CVarTemporalAASamples.GetValueOnRenderThread();
|
|
|
|
if( TemporalAASamples > 1 && View.bAllowTemporalJitter )
|
|
{
|
|
float SampleX, SampleY;
|
|
|
|
if (Scene->GetFeatureLevel() < ERHIFeatureLevel::SM4)
|
|
{
|
|
// Only support 2 samples for mobile temporal AA.
|
|
TemporalAASamples = 2;
|
|
}
|
|
|
|
if( TemporalAASamples == 2 )
|
|
{
|
|
#if 0
|
|
// 2xMSAA
|
|
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
|
|
// N.
|
|
// .S
|
|
float SamplesX[] = { -4.0f/16.0f, 4.0/16.0f };
|
|
float SamplesY[] = { -4.0f/16.0f, 4.0/16.0f };
|
|
#else
|
|
// This pattern is only used for mobile.
|
|
// Shift to reduce blur.
|
|
float SamplesX[] = { -8.0f/16.0f, 0.0/16.0f };
|
|
float SamplesY[] = { /* - */ 0.0f/16.0f, 8.0/16.0f };
|
|
#endif
|
|
ViewState->OnFrameRenderingSetup(ARRAY_COUNT(SamplesX), ViewFamily);
|
|
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
|
|
SampleX = SamplesX[ Index ];
|
|
SampleY = SamplesY[ Index ];
|
|
}
|
|
else if( TemporalAASamples == 3 )
|
|
{
|
|
// 3xMSAA
|
|
// A..
|
|
// ..B
|
|
// .C.
|
|
// Rolling circle pattern (A,B,C).
|
|
float SamplesX[] = { -2.0f/3.0f, 2.0/3.0f, 0.0/3.0f };
|
|
float SamplesY[] = { -2.0f/3.0f, 0.0/3.0f, 2.0/3.0f };
|
|
ViewState->OnFrameRenderingSetup(ARRAY_COUNT(SamplesX), ViewFamily);
|
|
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
|
|
SampleX = SamplesX[ Index ];
|
|
SampleY = SamplesY[ Index ];
|
|
}
|
|
else if( TemporalAASamples == 4 )
|
|
{
|
|
// 4xMSAA
|
|
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
|
|
// .N..
|
|
// ...E
|
|
// W...
|
|
// ..S.
|
|
// Rolling circle pattern (N,E,S,W).
|
|
float SamplesX[] = { -2.0f/16.0f, 6.0/16.0f, 2.0/16.0f, -6.0/16.0f };
|
|
float SamplesY[] = { -6.0f/16.0f, -2.0/16.0f, 6.0/16.0f, 2.0/16.0f };
|
|
ViewState->OnFrameRenderingSetup(ARRAY_COUNT(SamplesX), ViewFamily);
|
|
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
|
|
SampleX = SamplesX[ Index ];
|
|
SampleY = SamplesY[ Index ];
|
|
}
|
|
else if( TemporalAASamples == 5 )
|
|
{
|
|
// Compressed 4 sample pattern on same vertical and horizontal line (less temporal flicker).
|
|
// Compressed 1/2 works better than correct 2/3 (reduced temporal flicker).
|
|
// . N .
|
|
// W . E
|
|
// . S .
|
|
// Rolling circle pattern (N,E,S,W).
|
|
float SamplesX[] = { 0.0f/2.0f, 1.0/2.0f, 0.0/2.0f, -1.0/2.0f };
|
|
float SamplesY[] = { -1.0f/2.0f, 0.0/2.0f, 1.0/2.0f, 0.0/2.0f };
|
|
ViewState->OnFrameRenderingSetup(ARRAY_COUNT(SamplesX), ViewFamily);
|
|
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
|
|
SampleX = SamplesX[ Index ];
|
|
SampleY = SamplesY[ Index ];
|
|
}
|
|
else
|
|
{
|
|
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TemporalAASharpness"));
|
|
float Scale = ( 2.0f - CVar->GetFloat() ) * 0.3f;
|
|
|
|
// More than 8 samples can improve quality.
|
|
ViewState->OnFrameRenderingSetup(TemporalAASamples, ViewFamily);
|
|
uint32 Index = ViewState->GetCurrentTemporalAASampleIndex();
|
|
|
|
float u1 = Halton( Index + 1, 2 );
|
|
float u2 = Halton( Index + 1, 3 );
|
|
|
|
// Gaussian sample
|
|
float phi = 2.0f * PI * u2;
|
|
float r = Scale * FMath::Sqrt( -2.0f * FMath::Loge( FMath::Max( u1, 1e-6f ) ) );
|
|
SampleX = r * FMath::Cos( phi );
|
|
SampleY = r * FMath::Sin( phi );
|
|
}
|
|
|
|
View.TemporalJitterPixelsX = SampleX;
|
|
View.TemporalJitterPixelsY = SampleY;
|
|
|
|
View.ViewMatrices.TemporalAAProjJitter.X = SampleX * 2.0f / View.ViewRect.Width();
|
|
View.ViewMatrices.TemporalAAProjJitter.Y = SampleY * 2.0f / View.ViewRect.Height();
|
|
View.ViewMatrices.ProjMatrix.M[2][0] += View.ViewMatrices.TemporalAAProjJitter.X;
|
|
View.ViewMatrices.ProjMatrix.M[2][1] += View.ViewMatrices.TemporalAAProjJitter.Y;
|
|
|
|
// Compute the view projection matrix and its inverse.
|
|
View.ViewProjectionMatrix = View.ViewMatrices.ViewMatrix * View.ViewMatrices.ProjMatrix;
|
|
View.InvViewProjectionMatrix = View.ViewMatrices.GetInvProjMatrix() * View.InvViewMatrix;
|
|
|
|
// Compute a transform from view origin centered world-space to clip space.
|
|
if( View.ViewMatrices.PreViewTranslation.IsNearlyZero() )
|
|
{
|
|
View.ViewMatrices.TranslatedViewProjectionMatrix = View.ViewProjectionMatrix;
|
|
View.ViewMatrices.InvTranslatedViewProjectionMatrix = View.InvViewProjectionMatrix;
|
|
}
|
|
else
|
|
{
|
|
ensure( View.ViewMatrices.TranslatedViewMatrix.GetOrigin().IsNearlyZero() );
|
|
View.ViewMatrices.TranslatedViewProjectionMatrix = View.ViewMatrices.TranslatedViewMatrix * View.ViewMatrices.ProjMatrix;
|
|
View.ViewMatrices.InvTranslatedViewProjectionMatrix = View.ViewMatrices.GetInvProjMatrix() * View.ViewMatrices.TranslatedViewMatrix.GetTransposed();
|
|
}
|
|
}
|
|
}
|
|
else if(ViewState)
|
|
{
|
|
// no TemporalAA
|
|
ViewState->OnFrameRenderingSetup(1, ViewFamily);
|
|
|
|
ViewState->TemporalAAHistoryRT.SafeRelease();
|
|
ViewState->PendingTemporalAAHistoryRT.SafeRelease();
|
|
}
|
|
|
|
if ( ViewState )
|
|
{
|
|
// In case world origin was rebased, reset previous view transformations
|
|
if (View.bOriginOffsetThisFrame)
|
|
{
|
|
ViewState->PrevViewMatrices = View.ViewMatrices;
|
|
ViewState->PendingPrevViewMatrices = View.ViewMatrices;
|
|
}
|
|
|
|
// determine if we are initializing or we should reset the persistent state
|
|
const float DeltaTime = View.Family->CurrentRealTime - ViewState->LastRenderTime;
|
|
const bool bFirstFrameOrTimeWasReset = DeltaTime < -0.0001f || ViewState->LastRenderTime < 0.0001f;
|
|
|
|
// detect conditions where we should reset occlusion queries
|
|
if (bFirstFrameOrTimeWasReset ||
|
|
ViewState->LastRenderTime + GEngine->PrimitiveProbablyVisibleTime < View.Family->CurrentRealTime ||
|
|
View.bCameraCut ||
|
|
IsLargeCameraMovement(
|
|
View,
|
|
ViewState->PrevViewMatrixForOcclusionQuery,
|
|
ViewState->PrevViewOriginForOcclusionQuery,
|
|
GEngine->CameraRotationThreshold, GEngine->CameraTranslationThreshold))
|
|
{
|
|
View.bIgnoreExistingQueries = true;
|
|
View.bDisableDistanceBasedFadeTransitions = true;
|
|
}
|
|
ViewState->PrevViewMatrixForOcclusionQuery = View.ViewMatrices.ViewMatrix;
|
|
ViewState->PrevViewOriginForOcclusionQuery = View.ViewMatrices.ViewOrigin;
|
|
|
|
// store old view matrix and detect conditions where we should reset motion blur
|
|
{
|
|
bool bResetCamera = bFirstFrameOrTimeWasReset
|
|
|| View.bCameraCut
|
|
|| IsLargeCameraMovement(View, ViewState->PrevViewMatrices.ViewMatrix, ViewState->PrevViewMatrices.ViewOrigin, 45.0f, 10000.0f);
|
|
|
|
if (bResetCamera)
|
|
{
|
|
ViewState->PrevViewMatrices = View.ViewMatrices;
|
|
|
|
ViewState->PendingPrevViewMatrices = ViewState->PrevViewMatrices;
|
|
|
|
// PT: If the motion blur shader is the last shader in the post-processing chain then it is the one that is
|
|
// adjusting for the viewport offset. So it is always required and we can't just disable the work the
|
|
// shader does. The correct fix would be to disable the effect when we don't need it and to properly mark
|
|
// the uber-postprocessing effect as the last effect in the chain.
|
|
|
|
View.bPrevTransformsReset = true;
|
|
}
|
|
else
|
|
{
|
|
// check for pause so we can keep motion blur in paused mode (doesn't work in editor)
|
|
if(!ViewFamily.bWorldIsPaused)
|
|
{
|
|
ViewState->PrevViewMatrices = ViewState->PendingPrevViewMatrices;
|
|
if( ViewState->PendingTemporalAAHistoryRT.GetRefCount() )
|
|
{
|
|
ViewState->TemporalAAHistoryRT = ViewState->PendingTemporalAAHistoryRT;
|
|
ViewState->PendingTemporalAAHistoryRT.SafeRelease();
|
|
}
|
|
|
|
// pending is needed as we are in init view and still need to render.
|
|
ViewState->PendingPrevViewMatrices = View.ViewMatrices;
|
|
}
|
|
}
|
|
// we don't use DeltaTime as it can be 0 (in editor) and is computed by subtracting floats (loses precision over time)
|
|
// Clamp DeltaWorldTime to reasonable values for the purposes of motion blur, things like TimeDilation can make it very small
|
|
if (!ViewFamily.bWorldIsPaused)
|
|
{
|
|
const bool bEnableTimeScale = !ViewState->bSequencerIsPaused;
|
|
const float FixedBlurTimeScale = 2.0f;// 1 / (30 * 1 / 60)
|
|
|
|
ViewState->MotionBlurTimeScale = bEnableTimeScale ? (1.0f / (FMath::Max(View.Family->DeltaWorldTime, .00833f) * 30.0f)) : FixedBlurTimeScale;
|
|
}
|
|
|
|
View.PrevViewMatrices = ViewState->PrevViewMatrices;
|
|
|
|
View.PrevViewProjMatrix = ViewState->PrevViewMatrices.GetViewProjMatrix();
|
|
View.PrevViewRotationProjMatrix = ViewState->PrevViewMatrices.GetViewRotationProjMatrix();
|
|
}
|
|
|
|
ViewState->PrevFrameNumber = ViewState->PendingPrevFrameNumber;
|
|
ViewState->PendingPrevFrameNumber = View.Family->FrameNumber;
|
|
|
|
// This finishes the update of view state
|
|
ViewState->UpdateLastRenderTime(*View.Family);
|
|
|
|
ViewState->UpdateTemporalLODTransition(View);
|
|
}
|
|
}
|
|
}
|
|
|
|
static TAutoConsoleVariable<int32> CVarAlsoUseSphereForFrustumCull(
|
|
TEXT("r.AlsoUseSphereForFrustumCull"),
|
|
0,
|
|
TEXT("Performance tweak. If > 0, then use a sphere cull before and in addition to a box for frustum culling."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
void FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime);
|
|
|
|
STAT(int32 NumProcessedPrimitives = 0);
|
|
STAT(int32 NumCulledPrimitives = 0);
|
|
STAT(int32 NumOccludedPrimitives = 0);
|
|
|
|
// Allocate the visible light info.
|
|
if (Scene->Lights.GetMaxIndex() > 0)
|
|
{
|
|
VisibleLightInfos.AddZeroed(Scene->Lights.GetMaxIndex());
|
|
}
|
|
|
|
int32 NumPrimitives = Scene->Primitives.Num();
|
|
float CurrentRealTime = ViewFamily.CurrentRealTime;
|
|
|
|
FPrimitiveViewMasks HasDynamicMeshElementsMasks;
|
|
HasDynamicMeshElementsMasks.AddZeroed(NumPrimitives);
|
|
|
|
FPrimitiveViewMasks HasDynamicEditorMeshElementsMasks;
|
|
|
|
if (GIsEditor)
|
|
{
|
|
HasDynamicEditorMeshElementsMasks.AddZeroed(NumPrimitives);
|
|
}
|
|
|
|
uint8 ViewBit = 0x1;
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex, ViewBit <<= 1)
|
|
{
|
|
STAT(NumProcessedPrimitives += NumPrimitives);
|
|
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FSceneViewState* ViewState = (FSceneViewState*)View.State;
|
|
|
|
// Allocate the view's visibility maps.
|
|
View.PrimitiveVisibilityMap.Init(false,Scene->Primitives.Num());
|
|
// we don't initialized as we overwrite the whole array (in GatherDynamicMeshElements)
|
|
View.DynamicMeshEndIndices.SetNumUninitialized(Scene->Primitives.Num());
|
|
View.PrimitiveDefinitelyUnoccludedMap.Init(false,Scene->Primitives.Num());
|
|
View.PotentiallyFadingPrimitiveMap.Init(false,Scene->Primitives.Num());
|
|
View.PrimitiveFadeUniformBuffers.AddZeroed(Scene->Primitives.Num());
|
|
View.StaticMeshVisibilityMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshOccluderMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshFadeOutDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshFadeInDitheredLODMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshVelocityMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshShadowDepthMap.Init(false,Scene->StaticMeshes.GetMaxIndex());
|
|
View.StaticMeshBatchVisibility.AddZeroed(Scene->StaticMeshes.GetMaxIndex());
|
|
|
|
View.VisibleLightInfos.Empty(Scene->Lights.GetMaxIndex());
|
|
|
|
#if WITH_EDITOR
|
|
View.StaticMeshEditorSelectionMap.Init(false, Scene->StaticMeshes.GetMaxIndex());
|
|
#endif
|
|
|
|
// The dirty list allocation must take into account the max possible size because when GILCUpdatePrimTaskEnabled is true,
|
|
// the indirect lighting cache will be update on by threaded job, which can not do reallocs on the buffer (since it uses the SceneRenderingAllocator).
|
|
View.DirtyPrecomputedLightingBufferPrimitives.Reserve(Scene->Primitives.Num());
|
|
|
|
for(int32 LightIndex = 0;LightIndex < Scene->Lights.GetMaxIndex();LightIndex++)
|
|
{
|
|
if( LightIndex+2 < Scene->Lights.GetMaxIndex() )
|
|
{
|
|
if (LightIndex > 2)
|
|
{
|
|
FLUSH_CACHE_LINE(&View.VisibleLightInfos(LightIndex-2));
|
|
}
|
|
// @todo optimization These prefetches cause asserts since LightIndex > View.VisibleLightInfos.Num() - 1
|
|
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+2]);
|
|
//FPlatformMisc::Prefetch(&View.VisibleLightInfos[LightIndex+1]);
|
|
}
|
|
new(View.VisibleLightInfos) FVisibleLightViewInfo();
|
|
}
|
|
|
|
View.PrimitiveViewRelevanceMap.Empty(Scene->Primitives.Num());
|
|
View.PrimitiveViewRelevanceMap.AddZeroed(Scene->Primitives.Num());
|
|
|
|
// If this is the visibility-parent of other views, reset its ParentPrimitives list.
|
|
const bool bIsParent = ViewState && ViewState->IsViewParent();
|
|
if ( bIsParent )
|
|
{
|
|
// PVS-Studio does not understand the validation of ViewState above, so we're disabling
|
|
// its warning that ViewState may be null:
|
|
ViewState->ParentPrimitives.Empty(); //-V595
|
|
}
|
|
|
|
if (ViewState)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_DecompressPrecomputedOcclusion);
|
|
View.PrecomputedVisibilityData = ViewState->GetPrecomputedVisibilityData(View, Scene);
|
|
}
|
|
else
|
|
{
|
|
View.PrecomputedVisibilityData = NULL;
|
|
}
|
|
|
|
if (View.PrecomputedVisibilityData)
|
|
{
|
|
bUsedPrecomputedVisibility = true;
|
|
}
|
|
|
|
bool bNeedsFrustumCulling = true;
|
|
|
|
// Development builds sometimes override frustum culling, e.g. dependent views in the editor.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
if( ViewState )
|
|
{
|
|
#if WITH_EDITOR
|
|
// For visibility child views, check if the primitive was visible in the parent view.
|
|
const FSceneViewState* const ViewParent = (FSceneViewState*)ViewState->GetViewParent();
|
|
if(ViewParent)
|
|
{
|
|
bNeedsFrustumCulling = false;
|
|
for (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (ViewParent->ParentPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
|
|
{
|
|
BitIt.GetValue() = true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// For views with frozen visibility, check if the primitive is in the frozen visibility set.
|
|
if(ViewState->bIsFrozen)
|
|
{
|
|
bNeedsFrustumCulling = false;
|
|
for (FSceneBitArray::FIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (ViewState->FrozenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
|
|
{
|
|
BitIt.GetValue() = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Most views use standard frustum culling.
|
|
if (bNeedsFrustumCulling)
|
|
{
|
|
int32 NumCulledPrimitivesForView;
|
|
if (View.CustomVisibilityQuery && View.CustomVisibilityQuery->Prepare())
|
|
{
|
|
if (CVarAlsoUseSphereForFrustumCull.GetValueOnRenderThread())
|
|
{
|
|
NumCulledPrimitivesForView = FrustumCull<true, true>(Scene, View);
|
|
}
|
|
else
|
|
{
|
|
NumCulledPrimitivesForView = FrustumCull<true, false>(Scene, View);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CVarAlsoUseSphereForFrustumCull.GetValueOnRenderThread())
|
|
{
|
|
NumCulledPrimitivesForView = FrustumCull<false, true>(Scene, View);
|
|
}
|
|
else
|
|
{
|
|
NumCulledPrimitivesForView = FrustumCull<false, false>(Scene, View);
|
|
}
|
|
}
|
|
STAT(NumCulledPrimitives += NumCulledPrimitivesForView);
|
|
UpdatePrimitiveFading(Scene, View);
|
|
}
|
|
|
|
// If any primitives are explicitly hidden, remove them now.
|
|
if (View.HiddenPrimitives.Num())
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (View.HiddenPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the view has any show only primitives, hide everything else
|
|
if (View.ShowOnlyPrimitives.Num())
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (!View.ShowOnlyPrimitives.Contains(Scene->PrimitiveComponentIds[BitIt.GetIndex()]))
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (View.bStaticSceneOnly)
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
// Reflection captures should only capture objects that won't move, since reflection captures won't update at runtime
|
|
if (!Scene->Primitives[BitIt.GetIndex()]->Proxy->HasStaticLighting())
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cull small objects in wireframe in ortho views
|
|
// This is important for performance in the editor because wireframe disables any kind of occlusion culling
|
|
if (View.Family->EngineShowFlags.Wireframe)
|
|
{
|
|
float ScreenSizeScale = FMath::Max(View.ViewMatrices.ProjMatrix.M[0][0] * View.ViewRect.Width(), View.ViewMatrices.ProjMatrix.M[1][1] * View.ViewRect.Height());
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
if (ScreenSizeScale * Scene->PrimitiveBounds[BitIt.GetIndex()].SphereRadius <= GWireframeCullThreshold)
|
|
{
|
|
View.PrimitiveVisibilityMap.AccessCorrespondingBit(BitIt) = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Occlusion cull for all primitives in the view frustum, but not in wireframe.
|
|
if (!View.Family->EngineShowFlags.Wireframe)
|
|
{
|
|
int32 NumOccludedPrimitivesInView = OcclusionCull(RHICmdList, Scene, View);
|
|
STAT(NumOccludedPrimitives += NumOccludedPrimitivesInView);
|
|
}
|
|
|
|
// visibility test is done, so now build the hidden flags based on visibility set up
|
|
FLODSceneTree& HLODTree = Scene->SceneLODHierarchy;
|
|
|
|
if (HLODTree.IsActive())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_HLOD);
|
|
HLODTree.UpdateAndApplyVisibilityStates(View);
|
|
}
|
|
|
|
MarkAllPrimitivesForReflectionProxyUpdate(Scene);
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime_ConditionalMarkStaticMeshElementsForUpdate);
|
|
Scene->ConditionalMarkStaticMeshElementsForUpdate();
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ViewRelevance);
|
|
ComputeAndMarkRelevanceForViewParallel(RHICmdList, Scene, View, ViewBit, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks);
|
|
}
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// Store the primitive for parent occlusion rendering.
|
|
if (FPlatformProperties::SupportsWindowedMode() && ViewState && ViewState->IsViewParent())
|
|
{
|
|
for (FSceneDualSetBitIterator BitIt(View.PrimitiveVisibilityMap, View.PrimitiveDefinitelyUnoccludedMap); BitIt; ++BitIt)
|
|
{
|
|
ViewState->ParentPrimitives.Add(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
// if we are freezing the scene, then remember the primitives that are rendered.
|
|
if (ViewState && ViewState->bIsFreezing)
|
|
{
|
|
for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
|
|
{
|
|
ViewState->FrozenPrimitives.Add(Scene->PrimitiveComponentIds[BitIt.GetIndex()]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// TODO: right now decals visibility computed right before rendering them, ideally it should be done in InitViews and this flag should be replaced with list of visible decals
|
|
// Currently used to disable stencil operations in forward base pass when scene has no any decals
|
|
View.bSceneHasDecals = (Scene->Decals.Num() > 0);
|
|
}
|
|
|
|
GatherDynamicMeshElements(Views, Scene, ViewFamily, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks, MeshCollector);
|
|
|
|
INC_DWORD_STAT_BY(STAT_ProcessedPrimitives,NumProcessedPrimitives);
|
|
INC_DWORD_STAT_BY(STAT_CulledPrimitives,NumCulledPrimitives);
|
|
INC_DWORD_STAT_BY(STAT_OccludedPrimitives,NumOccludedPrimitives);
|
|
}
|
|
|
|
void FSceneRenderer::PostVisibilityFrameSetup(FILCUpdatePrimTaskData& OutILCTaskData)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup);
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Sort);
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
View.TranslucentPrimSet.SortPrimitives();
|
|
View.MeshDecalPrimSet.SortPrimitives();
|
|
|
|
if (View.State)
|
|
{
|
|
((FSceneViewState*)View.State)->TrimHistoryRenderTargets(Scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bCheckLightShafts = false;
|
|
if (Scene->GetFeatureLevel() <= ERHIFeatureLevel::ES3_1)
|
|
{
|
|
// Clear the mobile light shaft data.
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
View.bLightShaftUse = false;
|
|
View.LightShaftCenter.X = 0.0f;
|
|
View.LightShaftCenter.Y = 0.0f;
|
|
View.LightShaftColorMask = FLinearColor(0.0f,0.0f,0.0f);
|
|
View.LightShaftColorApply = FLinearColor(0.0f,0.0f,0.0f);
|
|
}
|
|
|
|
extern int32 GLightShafts;
|
|
bCheckLightShafts = ViewFamily.EngineShowFlags.LightShafts && GLightShafts;
|
|
}
|
|
|
|
if (ViewFamily.EngineShowFlags.HitProxies == 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_IndirectLightingCache_Update);
|
|
if (GILCUpdatePrimTaskEnabled)
|
|
{
|
|
Scene->IndirectLightingCache.StartUpdateCachePrimitivesTask(Scene, *this, true, OutILCTaskData);
|
|
}
|
|
else
|
|
{
|
|
Scene->IndirectLightingCache.UpdateCache(Scene, *this, true);
|
|
}
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_Light_Visibility);
|
|
// determine visibility of each light
|
|
for(TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);LightIt;++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
const FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;
|
|
|
|
// view frustum cull lights in each view
|
|
for(int32 ViewIndex = 0;ViewIndex < Views.Num();ViewIndex++)
|
|
{
|
|
const FLightSceneProxy* Proxy = LightSceneInfo->Proxy;
|
|
FViewInfo& View = Views[ViewIndex];
|
|
FVisibleLightViewInfo& VisibleLightViewInfo = View.VisibleLightInfos[LightIt.GetIndex()];
|
|
// dir lights are always visible, and point/spot only if in the frustum
|
|
if (Proxy->GetLightType() == LightType_Point
|
|
|| Proxy->GetLightType() == LightType_Spot)
|
|
{
|
|
const float Radius = Proxy->GetRadius();
|
|
|
|
if (View.ViewFrustum.IntersectSphere(Proxy->GetOrigin(), Radius))
|
|
{
|
|
FSphere Bounds = Proxy->GetBoundingSphere();
|
|
float DistanceSquared = (Bounds.Center - View.ViewMatrices.ViewOrigin).SizeSquared();
|
|
float MaxDistSquared = Proxy->GetMaxDrawDistance() * Proxy->GetMaxDrawDistance();
|
|
const bool bDrawLight = (FMath::Square(FMath::Min(0.0002f, GMinScreenRadiusForLights / Bounds.W) * View.LODDistanceFactor) * DistanceSquared < 1.0f)
|
|
& (MaxDistSquared == 0 || DistanceSquared < MaxDistSquared);
|
|
|
|
VisibleLightViewInfo.bInViewFrustum = bDrawLight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VisibleLightViewInfo.bInViewFrustum = true;
|
|
|
|
static const auto CVarMobileMSAA = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MobileMSAA"));
|
|
bool bNotMobileMSAA = !(CVarMobileMSAA ? CVarMobileMSAA->GetValueOnRenderThread() > 1 : false);
|
|
|
|
// Setup single sun-shaft from direction lights for mobile.
|
|
if(bCheckLightShafts && LightSceneInfo->bEnableLightShaftBloom)
|
|
{
|
|
// Find directional light for sun shafts.
|
|
// Tweaked values from UE3 implementation.
|
|
const float PointLightFadeDistanceIncrease = 200.0f;
|
|
const float PointLightRadiusFadeFactor = 5.0f;
|
|
|
|
const FVector WorldSpaceBlurOrigin = LightSceneInfo->Proxy->GetPosition();
|
|
// Transform into post projection space
|
|
FVector4 ProjectedBlurOrigin = View.WorldToScreen(WorldSpaceBlurOrigin);
|
|
|
|
const float DistanceToBlurOrigin = (View.ViewMatrices.ViewOrigin - WorldSpaceBlurOrigin).Size() + PointLightFadeDistanceIncrease;
|
|
|
|
// Don't render if the light's origin is behind the view
|
|
if(ProjectedBlurOrigin.W >= 0.0f
|
|
// Don't render point lights that have completely faded out
|
|
&& (LightSceneInfo->Proxy->GetLightType() == LightType_Directional
|
|
|| DistanceToBlurOrigin < LightSceneInfo->Proxy->GetRadius() * PointLightRadiusFadeFactor))
|
|
{
|
|
View.bLightShaftUse = bNotMobileMSAA;
|
|
View.LightShaftCenter.X = ProjectedBlurOrigin.X / ProjectedBlurOrigin.W;
|
|
View.LightShaftCenter.Y = ProjectedBlurOrigin.Y / ProjectedBlurOrigin.W;
|
|
// TODO: Might want to hookup different colors for these.
|
|
View.LightShaftColorMask = LightSceneInfo->BloomTint;
|
|
View.LightShaftColorApply = LightSceneInfo->BloomTint;
|
|
|
|
// Apply bloom scale
|
|
View.LightShaftColorMask *= FLinearColor(LightSceneInfo->BloomScale, LightSceneInfo->BloomScale, LightSceneInfo->BloomScale, 1.0f);
|
|
View.LightShaftColorApply *= FLinearColor(LightSceneInfo->BloomScale, LightSceneInfo->BloomScale, LightSceneInfo->BloomScale, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw shapes for reflection captures
|
|
if( View.bIsReflectionCapture
|
|
&& VisibleLightViewInfo.bInViewFrustum
|
|
&& Proxy->HasStaticLighting()
|
|
&& Proxy->GetLightType() != LightType_Directional
|
|
// Min roughness is used to hide the specular response of virtual area lights, so skip drawing the source shape when Min Roughness is 1
|
|
&& Proxy->GetMinRoughness() < 1.0f)
|
|
{
|
|
FVector Origin = Proxy->GetOrigin();
|
|
FVector ToLight = Origin - View.ViewMatrices.ViewOrigin;
|
|
float DistanceSqr = ToLight | ToLight;
|
|
float Radius = Proxy->GetRadius();
|
|
|
|
if( DistanceSqr < Radius * Radius )
|
|
{
|
|
FVector4 PositionAndInvRadius;
|
|
FVector4 ColorAndFalloffExponent;
|
|
FVector Direction;
|
|
FVector2D SpotAngles;
|
|
float SourceRadius;
|
|
float SourceLength;
|
|
float MinRoughness;
|
|
Proxy->GetParameters( PositionAndInvRadius, ColorAndFalloffExponent, Direction, SpotAngles, SourceRadius, SourceLength, MinRoughness );
|
|
|
|
// Force to be at least 0.75 pixels
|
|
float CubemapSize = 128.0f;
|
|
float Distance = FMath::Sqrt( DistanceSqr );
|
|
float MinRadius = Distance * 0.75f / CubemapSize;
|
|
SourceRadius = FMath::Max( MinRadius, SourceRadius );
|
|
|
|
// Snap to cubemap pixel center to reduce aliasing
|
|
FVector Scale = ToLight.GetAbs();
|
|
int32 MaxComponent = Scale.X > Scale.Y ? ( Scale.X > Scale.Z ? 0 : 2 ) : ( Scale.Y > Scale.Z ? 1 : 2 );
|
|
for( int32 k = 1; k < 3; k++ )
|
|
{
|
|
float Projected = ToLight[ (MaxComponent + k) % 3 ] / Scale[ MaxComponent ];
|
|
float Quantized = ( FMath::RoundToFloat( Projected * (0.5f * CubemapSize) - 0.5f ) + 0.5f ) / (0.5f * CubemapSize);
|
|
ToLight[ (MaxComponent + k) % 3 ] = Quantized * Scale[ MaxComponent ];
|
|
}
|
|
Origin = ToLight + View.ViewMatrices.ViewOrigin;
|
|
|
|
FLinearColor Color( ColorAndFalloffExponent );
|
|
|
|
if( Proxy->IsInverseSquared() )
|
|
{
|
|
float LightRadiusMask = FMath::Square( 1.0f - FMath::Square( DistanceSqr * FMath::Square( PositionAndInvRadius.W ) ) );
|
|
Color *= LightRadiusMask;
|
|
|
|
// Correction for lumen units
|
|
Color *= 16.0f;
|
|
}
|
|
else
|
|
{
|
|
// Remove inverse square falloff
|
|
Color *= DistanceSqr + 1.0f;
|
|
|
|
// Apply falloff
|
|
Color *= FMath::Pow( 1.0f - DistanceSqr * FMath::Square( PositionAndInvRadius.W ), ColorAndFalloffExponent.W );
|
|
}
|
|
|
|
// Spot falloff
|
|
FVector L = ToLight.GetSafeNormal();
|
|
Color *= FMath::Square( FMath::Clamp( ( (L | Direction) - SpotAngles.X ) * SpotAngles.Y, 0.0f, 1.0f ) );
|
|
|
|
// Scale by visible area
|
|
Color /= PI * FMath::Square( SourceRadius );
|
|
|
|
// Always opaque
|
|
Color.A = 1.0f;
|
|
|
|
FViewElementPDI LightPDI( &View, NULL );
|
|
FMaterialRenderProxy* const ColoredMeshInstance = new(FMemStack::Get()) FColoredMaterialRenderProxy( GEngine->DebugMeshMaterial->GetRenderProxy(false), Color );
|
|
DrawSphere( &LightPDI, Origin, FVector( SourceRadius, SourceRadius, SourceRadius ), 36, 24, ColoredMeshInstance, SDPG_World );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostVisibilityFrameSetup_InitFogConstants);
|
|
InitFogConstants();
|
|
}
|
|
}
|
|
|
|
uint32 GetShadowQuality();
|
|
|
|
/**
|
|
* Initialize scene's views.
|
|
* Check visibility, sort translucent items, etc.
|
|
*/
|
|
bool FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList, struct FILCUpdatePrimTaskData& ILCTaskData, FGraphEventArray& SortEvents)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_InitViewsTime);
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
const bool bWillApplyTemporalAA = GPostProcessing.AllowFullPostProcessing(View, FeatureLevel) || (View.bIsPlanarReflection && FeatureLevel >= ERHIFeatureLevel::SM4);
|
|
|
|
if (!bWillApplyTemporalAA)
|
|
{
|
|
// Disable anti-aliasing if we are not going to be able to apply final post process effects
|
|
View.AntiAliasingMethod = AAM_None;
|
|
}
|
|
}
|
|
PreVisibilityFrameSetup(RHICmdList);
|
|
ComputeViewVisibility(RHICmdList);
|
|
|
|
// This has to happen before Scene->IndirectLightingCache.UpdateCache, since primitives in View.IndirectShadowPrimitives need ILC updates
|
|
CreateIndirectCapsuleShadows();
|
|
|
|
PostVisibilityFrameSetup(ILCTaskData);
|
|
|
|
FVector AverageViewPosition(0);
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
AverageViewPosition += View.ViewMatrices.ViewOrigin / Views.Num();
|
|
}
|
|
|
|
if (FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0)
|
|
{
|
|
AsyncSortBasePassStaticData(AverageViewPosition, SortEvents);
|
|
}
|
|
else
|
|
{
|
|
SortBasePassStaticData(AverageViewPosition);
|
|
}
|
|
|
|
bool bDoInitViewAftersPrepass = !!GDoInitViewsLightingAfterPrepass;
|
|
|
|
if (!bDoInitViewAftersPrepass)
|
|
{
|
|
InitViewsPossiblyAfterPrepass(RHICmdList, ILCTaskData, SortEvents);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_InitRHIResources);
|
|
// initialize per-view uniform buffer.
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
|
|
View.ForwardLightingResources = View.ViewState ? &View.ViewState->ForwardLightingResources : &View.ForwardLightingResourcesStorage;
|
|
|
|
// Possible stencil dither optimization approach
|
|
View.bAllowStencilDither = bDitheredLODTransitionsUseStencil;
|
|
|
|
// Initialize the view's RHI resources.
|
|
View.InitRHIResources();
|
|
}
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_OnStartFrame);
|
|
OnStartFrame();
|
|
}
|
|
|
|
return bDoInitViewAftersPrepass;
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::InitViewsPossiblyAfterPrepass(FRHICommandListImmediate& RHICmdList, struct FILCUpdatePrimTaskData& ILCTaskData, FGraphEventArray& SortEvents)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_InitViewsPossiblyAfterPrepass);
|
|
|
|
// this cannot be moved later because of static mesh updates for stuff that is only visible in shadows
|
|
if (SortEvents.Num())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_AsyncSortBasePassStaticData_Wait);
|
|
FTaskGraphInterface::Get().WaitUntilTasksComplete(SortEvents, ENamedThreads::RenderThread);
|
|
}
|
|
|
|
if (ViewFamily.EngineShowFlags.DynamicShadows && !IsSimpleForwardShadingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)))
|
|
{
|
|
// Setup dynamic shadows.
|
|
InitDynamicShadows(RHICmdList);
|
|
}
|
|
|
|
// if we kicked off ILC update via task, wait and finalize.
|
|
if (ILCTaskData.TaskRef.IsValid())
|
|
{
|
|
Scene->IndirectLightingCache.FinalizeCacheUpdates(Scene, *this, ILCTaskData);
|
|
}
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_InitViews_UpdatePrimitivePrecomputedLightingBuffers);
|
|
// Now that the indirect lighting cache is updated, we can update the primitive precomputed lighting buffers.
|
|
UpdatePrimitivePrecomputedLightingBuffers();
|
|
}
|
|
|
|
UpdateTranslucencyTimersAndSeparateTranslucencyBufferSize(RHICmdList);
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
FViewInfo& View = Views[ViewIndex];
|
|
SetupReflectionCaptureBuffers(View, RHICmdList);
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
FLODSceneTree Implementation
|
|
------------------------------------------------------------------------------*/
|
|
void FLODSceneTree::AddChildNode(const FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* ChildSceneInfo)
|
|
{
|
|
if (NodeId.IsValid() && ChildSceneInfo)
|
|
{
|
|
FLODSceneNode* Node = SceneNodes.Find(NodeId);
|
|
|
|
if(!Node)
|
|
{
|
|
Node = &SceneNodes.Add(NodeId, FLODSceneNode());
|
|
|
|
// scene info can be added later depending on order of adding to the scene
|
|
// but at least add componentId, that way when parent is added, it will add its info properly
|
|
int32 ParentIndex = Scene->PrimitiveComponentIds.Find(NodeId);
|
|
if(Scene->Primitives.IsValidIndex(ParentIndex))
|
|
{
|
|
Node->SceneInfo = Scene->Primitives[ParentIndex];
|
|
}
|
|
//new nodes that will need distance scale, reset since we don't keep stateful data about this per node.
|
|
ResetHLODDistanceScaleApplication();
|
|
}
|
|
|
|
Node->AddChild(ChildSceneInfo);
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::RemoveChildNode(const FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* ChildSceneInfo)
|
|
{
|
|
if(NodeId.IsValid() && ChildSceneInfo)
|
|
{
|
|
FLODSceneNode* Node = SceneNodes.Find(NodeId);
|
|
if (Node)
|
|
{
|
|
Node->RemoveChild(ChildSceneInfo);
|
|
|
|
// delete from scene if no children remains
|
|
if(Node->ChildrenSceneInfos.Num() == 0)
|
|
{
|
|
SceneNodes.Remove(NodeId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::UpdateNodeSceneInfo(FPrimitiveComponentId NodeId, FPrimitiveSceneInfo* SceneInfo)
|
|
{
|
|
FLODSceneNode* Node = SceneNodes.Find(NodeId);
|
|
if(Node)
|
|
{
|
|
Node->SceneInfo = SceneInfo;
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::UpdateAndApplyVisibilityStates(FViewInfo& View)
|
|
{
|
|
PrimitiveFadingLODMap.Init(false, View.PrimitiveVisibilityMap.Num());
|
|
PrimitiveFadingOutLODMap.Init(false, View.PrimitiveVisibilityMap.Num());
|
|
FSceneBitArray& VisibilityFlags = View.PrimitiveVisibilityMap;
|
|
TArray<FPrimitiveViewRelevance, SceneRenderingAllocator>& RelevanceMap = View.PrimitiveViewRelevanceMap;
|
|
|
|
++UpdateCount;
|
|
|
|
if (const FSceneViewState* ViewState = (FSceneViewState*)View.State)
|
|
{
|
|
const float HLODDistanceScale = FMath::Max(0.0f, CVarHLODDistanceScale.GetValueOnRenderThread());
|
|
|
|
// Update persistent state on temporal dither sync frames
|
|
const FTemporalLODState& LODState = ViewState->GetTemporalLODState();
|
|
bool bSyncFrame = false;
|
|
|
|
if (TemporalLODSyncTime != LODState.TemporalLODTime[0])
|
|
{
|
|
TemporalLODSyncTime = LODState.TemporalLODTime[0];
|
|
bSyncFrame = true;
|
|
}
|
|
|
|
for (auto Iter = SceneNodes.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
FLODSceneNode& Node = Iter.Value();
|
|
const TIndirectArray<FStaticMesh>& NodeMeshes = Node.SceneInfo->StaticMeshes;
|
|
|
|
// Ignore already updated nodes, or those that we can't work with
|
|
if (Node.LatestUpdateCount == UpdateCount || !Node.SceneInfo || NodeMeshes.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 NodeIndex = Node.SceneInfo->GetIndex();
|
|
bool bIsVisible = VisibilityFlags[NodeIndex];
|
|
|
|
FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[NodeIndex];
|
|
{
|
|
if (LastHLODDistanceScale != HLODDistanceScale)
|
|
{
|
|
// Determine desired HLOD state
|
|
const float MinDrawDistance = Scene->Primitives[NodeIndex]->Proxy->GetMinDrawDistance();
|
|
const float AdjustedMinDrawDist = MinDrawDistance * HLODDistanceScale;
|
|
Bounds.MinDrawDistanceSq = AdjustedMinDrawDist * AdjustedMinDrawDist;
|
|
}
|
|
}
|
|
|
|
const float DistanceSquared = (Bounds.Origin - View.ViewMatrices.ViewOrigin).SizeSquared();
|
|
const bool bIsInDrawRange = DistanceSquared >= Bounds.MinDrawDistanceSq;
|
|
|
|
const bool bWasFadingPreUpdate = Node.bIsFading;
|
|
|
|
// Update fading state
|
|
if (NodeMeshes[0].bDitheredLODTransition)
|
|
{
|
|
// Fade when HLODs change threshold on-screen, else snap
|
|
// TODO: This logic can still be improved to clear state and
|
|
// transitions when off-screen, but needs better detection
|
|
const bool bChangedRange = bIsInDrawRange != Node.bWasVisible;
|
|
const bool bIsOnScreen = bIsVisible || Node.bWasVisible;
|
|
|
|
// Update with syncs
|
|
if (bSyncFrame)
|
|
{
|
|
if (Node.bIsFading)
|
|
{
|
|
Node.bIsFading = false;
|
|
}
|
|
else if (bChangedRange && bIsOnScreen)
|
|
{
|
|
Node.bIsFading = true;
|
|
}
|
|
|
|
Node.bWasVisible = Node.bIsVisible;
|
|
Node.bIsVisible = bIsInDrawRange;
|
|
}
|
|
|
|
// Flag as fading or freeze visibility if waiting for a fade
|
|
if (Node.bIsFading)
|
|
{
|
|
PrimitiveFadingLODMap[NodeIndex] = true;
|
|
PrimitiveFadingOutLODMap[NodeIndex] = !Node.bIsVisible;
|
|
}
|
|
else if (bChangedRange && bIsOnScreen)
|
|
{
|
|
VisibilityFlags[NodeIndex] = Node.bWasVisible;
|
|
bIsVisible = Node.bWasVisible;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Instant transitions without dithering
|
|
Node.bWasVisible = Node.bIsVisible;
|
|
Node.bIsVisible = bIsInDrawRange;
|
|
Node.bIsFading = false;
|
|
}
|
|
|
|
if (Node.bIsFading)
|
|
{
|
|
// Fade until state back in sync
|
|
ApplyNodeFadingToChildren(Node, VisibilityFlags, true, Node.bIsVisible);
|
|
}
|
|
else if (bIsVisible)
|
|
{
|
|
// If stable and visible, override hierarchy visibility
|
|
HideNodeChildren(Node, VisibilityFlags);
|
|
}
|
|
|
|
// Flush cached lighting data when changing visible contents
|
|
if (Node.bIsVisible != Node.bWasVisible || bWasFadingPreUpdate || Node.bIsFading)
|
|
{
|
|
FLightPrimitiveInteraction* NodeLightList = Node.SceneInfo->LightList;
|
|
while (NodeLightList)
|
|
{
|
|
NodeLightList->FlushCachedShadowMapData();
|
|
NodeLightList = NodeLightList->GetNextLight();
|
|
}
|
|
}
|
|
|
|
// Force fully disabled view relevance so shadows don't attempt to recompute
|
|
if (!Node.bIsVisible)
|
|
{
|
|
FPrimitiveViewRelevance& ViewRelevance = RelevanceMap[NodeIndex];
|
|
FMemory::Memzero(&ViewRelevance, sizeof(FPrimitiveViewRelevance));
|
|
ViewRelevance.bInitializedThisFrame = true;
|
|
}
|
|
}
|
|
LastHLODDistanceScale = HLODDistanceScale;
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::ApplyNodeFadingToChildren(FLODSceneNode& Node, FSceneBitArray& VisibilityFlags, const bool bIsFading, const bool bIsFadingOut)
|
|
{
|
|
if (Node.SceneInfo)
|
|
{
|
|
Node.LatestUpdateCount = UpdateCount;
|
|
|
|
// Force visibility during fades
|
|
const int32 NodeIndex = Node.SceneInfo->GetIndex();
|
|
VisibilityFlags[NodeIndex] = true;
|
|
|
|
for (const auto& Child : Node.ChildrenSceneInfos)
|
|
{
|
|
const int32 ChildIndex = Child->GetIndex();
|
|
|
|
PrimitiveFadingLODMap[ChildIndex] = bIsFading;
|
|
PrimitiveFadingOutLODMap[ChildIndex] = bIsFadingOut;
|
|
VisibilityFlags[ChildIndex] = true;
|
|
|
|
// Fading only occurs at the adjacent hierarchy level, below should be hidden
|
|
if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId))
|
|
{
|
|
HideNodeChildren(*ChildNode, VisibilityFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLODSceneTree::HideNodeChildren(FLODSceneNode& Node, FSceneBitArray& VisibilityFlags)
|
|
{
|
|
if (Node.LatestUpdateCount != UpdateCount)
|
|
{
|
|
Node.LatestUpdateCount = UpdateCount;
|
|
|
|
for (const auto& Child : Node.ChildrenSceneInfos)
|
|
{
|
|
const int32 ChildIndex = Child->GetIndex();
|
|
VisibilityFlags[ChildIndex] = false;
|
|
|
|
if (FLODSceneNode* ChildNode = SceneNodes.Find(Child->PrimitiveComponentId))
|
|
{
|
|
HideNodeChildren(*ChildNode, VisibilityFlags);
|
|
}
|
|
}
|
|
}
|
|
} |