Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/SceneVisibility.cpp
Andrew Grant a534ff9466 Copying //UE4/Orion-Staging to //UE4/Main (Source: //Orion/Dev-General @ 3106465)
#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)

Change 3106213 on 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.

Change 3076010 on 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

Change 3069381 on 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)

Change 3067025 on 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.

	(Edigrating 3057152 from //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]
2016-09-01 21:20:38 -04:00

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);
}
}
}
}