You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden
==========================
MAJOR FEATURES + CHANGES
==========================
Change 3301794 on 2017/02/14 by Josh.Adams
Fixed a crash with clothing on platforms that don't support NV_CLOTH
Change 3302696 on 2017/02/14 by Chad.Garyet
adding dev-console json
Change 3306418 on 2017/02/16 by Ben.Woodhouse
Fix prepass/basepass zfighting, caused by bad vertex welding in depth-only indexbuffer. Requires bumping the staticmesh DDC key
Duplicated from Fortnite/Main CL 3302965
#jira UE-34332
Change 3308922 on 2017/02/17 by Josh.Adams
- Disabled the game analytics anon usage data sent to Epic on the console platforms
Change 3311506 on 2017/02/20 by Keith.Judge
Replicate fix for FD3D12UniqueDescriptorTable leak in async compute contexts from another branch.
Change 3313445 on 2017/02/20 by Josh.Adams
- Various Vulkan fixes:
- Compiles in Linux
- Many cubemap bugs squashed
- Changed the scratch reflection cubemap clear to SetRenderTargestsAndClear, instead of SetRenderTarget() / Clear()
- Added compute fences
Change 3314916 on 2017/02/21 by Josh.Adams
- Fixed an issue with 4 and 8 vertex instanced particles using the wrong VertexFactory objects (D3D didn't even need separate VFs due to the VertexDecl updating the stride at draw call time)
Change 3315398 on 2017/02/21 by Ben.Woodhouse
Fix GPUTestbed packaging
Change 3316340 on 2017/02/22 by Ben.Woodhouse
Duplicate hotfix from Release-4.15:
CL 3316322
Fix for GPU Cubemap copy crash - Guard for invalid indices before marking cubemap indices as removed
#jira UE-42165
Change 3317345 on 2017/02/22 by Ben.Woodhouse
Integrate from //UE4/Main/...@3316239
Change 3319186 on 2017/02/23 by Josh.Adams
Added /VIRTUALIZEDIRECTX option to XgConsole for XGE shader compiling to work on remote machines without DX installed
Change 3323514 on 2017/02/27 by Chad.Garyet
adding populate ddc for dev-console, removing RDU agent type
Change 3335889 on 2017/03/07 by Luke.Thatcher
[CONSOLE] [STREAMS] [^] Merge //UE4/Main (CL 3335229) to //UE4/Dev-Console
#tests Build Win64 Editor, run QAGame editor, Launch on PS4.
Change 3336550 on 2017/03/07 by Ben.Woodhouse
Duplicate CL 3336456
#jira UE-42468
Fix a bug in the rendertargetpool handling of fastVRAM targets, reported on UDN
Change 3340385 on 2017/03/09 by Ben.Woodhouse
Optimized fastVRAM layout and configurability. CVars can be configured based title rendering requirements and resolution
With these changes, we try to store the GBuffer in Fast VRAM if possible. Transient/non perf critical surfaces are now disabled by default
In content w/ dynamic lighting @ 900p we see a 1.8ms gain. In RenderTestMap QAGame @ 1080p we see 0.4ms gains (further improvements may be possible with additional tweaking).
Change 3355982 on 2017/03/21 by Ben.Woodhouse
Duplicate from CL 3354688:
Fix async SSAO not actually running asynchronously. This was because bHZBBeforeBasePass was set to false even though we had a full prepass (EDepthDrawingMode::DDM_AllOpaque), so we didn't process it until after the basepass.
This saved 0.6ms in GPUTestbed
Change 3356166 on 2017/03/21 by Ben.Woodhouse
Duplicate from 3347033
Subsurface postprocess optimization, courtesy of Mike O'Connor at Iron Galaxy Studios.
Add a branch to reduce bandwidth. Halved the cost of the setup pass according to PIX (0.3ms to 0.15ms)
Change 3360243 on 2017/03/23 by Luke.Thatcher
[CONSOLE] [STREAMS] [^] Merge //UE4/Main (CL 3358685) to //UE4/Dev-Console
#tests Build Win64 Editor, run FortGPUTestbed editor, Launch on PS4.
Change 3365746 on 2017/03/27 by Joe.Barnes
- Handle NULL source data.
- Log failed surround conversion.
Change 3368022 on 2017/03/28 by Ben.Woodhouse
Cherry pick reflection capture hotfix from release-4.15 CL 3365830:
Fixed reflection capture crash when repeatedly adding/removing captures
Previously we used an array of indices (CubemapIndicesRemovedSinceLastRealloc) to keep track of indices which had been removed, however this caused issues when those indices were reused by subsequent allocations before the array was reallocated
The new method uses a simple bitfield to track usage (one bit per cubemap slot index).
Also fixed order(N^2) index search in the index allocator - now just a fast bit scan
#jira UE-42165
#jira UE-42911
Change 3371568 on 2017/03/30 by Luke.Thatcher
[CONSOLE] [STREAMS] [^] Merging //UE4/Dev-Main (CL 3371054) to Dev-Console (//UE4/Dev-Console)
Change 3372780 on 2017/03/30 by Joe.Barnes
Add support for multi-channel ADPCM encoding. Format based on game side ADPCM decompressor.
Change 3374847 on 2017/03/31 by Ben.Woodhouse
Fix shipping warning
#jira UE-43522
Change 3376442 on 2017/04/03 by Ben.Woodhouse
Fix FortGPUTestbed animnotify cook errors (delete the offending animnotifies)
[CL 3378288 by Luke Thatcher in Main branch]
1620 lines
62 KiB
C++
1620 lines
62 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
Functionality for capturing the scene into reflection capture cubemaps, and prefiltering
|
|
=============================================================================*/
|
|
|
|
#include "ReflectionEnvironmentCapture.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "RenderingThread.h"
|
|
#include "RenderResource.h"
|
|
#include "ShowFlags.h"
|
|
#include "UnrealClient.h"
|
|
#include "ShaderParameters.h"
|
|
#include "RendererInterface.h"
|
|
#include "RHIStaticStates.h"
|
|
#include "SceneView.h"
|
|
#include "Shader.h"
|
|
#include "TextureResource.h"
|
|
#include "StaticBoundShaderState.h"
|
|
#include "SceneUtils.h"
|
|
#include "SceneManagement.h"
|
|
#include "Components/SkyLightComponent.h"
|
|
#include "Components/ReflectionCaptureComponent.h"
|
|
#include "Engine/TextureCube.h"
|
|
#include "PostProcess/SceneRenderTargets.h"
|
|
#include "GlobalShader.h"
|
|
#include "SceneRenderTargetParameters.h"
|
|
#include "SceneRendering.h"
|
|
#include "ScenePrivate.h"
|
|
#include "PostProcess/SceneFilterRendering.h"
|
|
#include "PostProcess/PostProcessing.h"
|
|
#include "ScreenRendering.h"
|
|
#include "ReflectionEnvironment.h"
|
|
#include "OneColorShader.h"
|
|
#include "PipelineStateCache.h"
|
|
|
|
/** Near plane to use when capturing the scene. */
|
|
float GReflectionCaptureNearPlane = 5;
|
|
|
|
int32 GSupersampleCaptureFactor = 1;
|
|
|
|
/**
|
|
* Mip map used by a Roughness of 0, counting down from the lowest resolution mip (MipCount - 1).
|
|
* This has been tweaked along with ReflectionCaptureRoughnessMipScale to make good use of the resolution in each mip, especially the highest resolution mips.
|
|
* This value is duplicated in ReflectionEnvironmentShared.usf!
|
|
*/
|
|
float ReflectionCaptureRoughestMip = 1;
|
|
|
|
/**
|
|
* Scales the log2 of Roughness when computing which mip to use for a given roughness.
|
|
* Larger values make the higher resolution mips sharper.
|
|
* This has been tweaked along with ReflectionCaptureRoughnessMipScale to make good use of the resolution in each mip, especially the highest resolution mips.
|
|
* This value is duplicated in ReflectionEnvironmentShared.usf!
|
|
*/
|
|
float ReflectionCaptureRoughnessMipScale = 1.2f;
|
|
|
|
int32 GDiffuseIrradianceCubemapSize = 32;
|
|
|
|
void OnUpdateReflectionCaptures( UWorld* InWorld )
|
|
{
|
|
InWorld->UpdateAllReflectionCaptures();
|
|
}
|
|
|
|
FAutoConsoleCommandWithWorld CaptureConsoleCommand(
|
|
TEXT("r.ReflectionCapture"),
|
|
TEXT("Updates all reflection captures"),
|
|
FConsoleCommandWithWorldDelegate::CreateStatic(OnUpdateReflectionCaptures)
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarReflectionCaptureGPUArrayCopy(
|
|
TEXT("r.ReflectionCaptureGPUArrayCopy"),
|
|
1,
|
|
TEXT("Do a fast copy of the reflection capture array when resizing if possible. This avoids hitches on the rendering thread when the cubemap array needs to grow.\n")
|
|
TEXT(" 0 is off, 1 is on (default)"),
|
|
ECVF_ReadOnly);
|
|
|
|
|
|
bool DoGPUArrayCopy()
|
|
{
|
|
return GRHISupportsResolveCubemapFaces && CVarReflectionCaptureGPUArrayCopy.GetValueOnAnyThread();
|
|
}
|
|
|
|
void FullyResolveReflectionScratchCubes(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, FullyResolveReflectionScratchCubes);
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
FTextureRHIRef& Scratch0 = SceneContext.ReflectionColorScratchCubemap[0]->GetRenderTargetItem().TargetableTexture;
|
|
FTextureRHIRef& Scratch1 = SceneContext.ReflectionColorScratchCubemap[1]->GetRenderTargetItem().TargetableTexture;
|
|
FResolveParams ResolveParams(FResolveRect(), CubeFace_PosX, -1, -1, -1);
|
|
RHICmdList.CopyToResolveTarget(Scratch0, Scratch0, true, ResolveParams);
|
|
RHICmdList.CopyToResolveTarget(Scratch1, Scratch1, true, ResolveParams);
|
|
}
|
|
|
|
/** Pixel shader used for filtering a mip. */
|
|
class FCubeFilterPS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FCubeFilterPS,Global);
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FCubeFilterPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
|
|
FGlobalShader(Initializer)
|
|
{
|
|
CubeFace.Bind(Initializer.ParameterMap,TEXT("CubeFace"));
|
|
MipIndex.Bind(Initializer.ParameterMap,TEXT("MipIndex"));
|
|
NumMips.Bind(Initializer.ParameterMap,TEXT("NumMips"));
|
|
SourceTexture.Bind(Initializer.ParameterMap,TEXT("SourceTexture"));
|
|
SourceTextureSampler.Bind(Initializer.ParameterMap,TEXT("SourceTextureSampler"));
|
|
}
|
|
FCubeFilterPS() {}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
|
|
Ar << CubeFace;
|
|
Ar << MipIndex;
|
|
Ar << NumMips;
|
|
Ar << SourceTexture;
|
|
Ar << SourceTextureSampler;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
|
|
FShaderParameter CubeFace;
|
|
FShaderParameter MipIndex;
|
|
FShaderParameter NumMips;
|
|
FShaderResourceParameter SourceTexture;
|
|
FShaderResourceParameter SourceTextureSampler;
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(,FCubeFilterPS,TEXT("ReflectionEnvironmentShaders"),TEXT("DownsamplePS"),SF_Pixel);
|
|
|
|
template< uint32 bNormalize >
|
|
class TCubeFilterPS : public FCubeFilterPS
|
|
{
|
|
DECLARE_SHADER_TYPE(TCubeFilterPS,Global);
|
|
|
|
public:
|
|
static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FCubeFilterPS::ModifyCompilationEnvironment(Platform, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("NORMALIZE"), bNormalize);
|
|
}
|
|
|
|
TCubeFilterPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
|
|
: FCubeFilterPS(Initializer)
|
|
{}
|
|
|
|
TCubeFilterPS() {}
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(template<>,TCubeFilterPS<0>,TEXT("ReflectionEnvironmentShaders"),TEXT("FilterPS"),SF_Pixel);
|
|
IMPLEMENT_SHADER_TYPE(template<>,TCubeFilterPS<1>,TEXT("ReflectionEnvironmentShaders"),TEXT("FilterPS"),SF_Pixel);
|
|
|
|
/** Computes the average brightness of a 1x1 mip of a cubemap. */
|
|
class FComputeBrightnessPS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FComputeBrightnessPS,Global)
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("COMPUTEBRIGHTNESS_PIXELSHADER"), 1);
|
|
}
|
|
|
|
FComputeBrightnessPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
|
|
: FGlobalShader(Initializer)
|
|
{
|
|
ReflectionEnvironmentColorTexture.Bind(Initializer.ParameterMap,TEXT("ReflectionEnvironmentColorTexture"));
|
|
ReflectionEnvironmentColorSampler.Bind(Initializer.ParameterMap,TEXT("ReflectionEnvironmentColorSampler"));
|
|
NumCaptureArrayMips.Bind(Initializer.ParameterMap, TEXT("NumCaptureArrayMips"));
|
|
}
|
|
|
|
FComputeBrightnessPS()
|
|
{
|
|
}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, int32 TargetSize)
|
|
{
|
|
const int32 EffectiveTopMipSize = TargetSize;
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
// Read from the smallest mip that was downsampled to
|
|
FSceneRenderTargetItem& Cubemap = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
|
|
if (Cubemap.IsValid())
|
|
{
|
|
SetTextureParameter(
|
|
RHICmdList,
|
|
GetPixelShader(),
|
|
ReflectionEnvironmentColorTexture,
|
|
ReflectionEnvironmentColorSampler,
|
|
TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
|
|
Cubemap.ShaderResourceTexture);
|
|
}
|
|
|
|
SetShaderValue(RHICmdList, GetPixelShader(), NumCaptureArrayMips, FMath::CeilLogTwo(TargetSize) + 1);
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
|
|
Ar << ReflectionEnvironmentColorTexture;
|
|
Ar << ReflectionEnvironmentColorSampler;
|
|
Ar << NumCaptureArrayMips;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
|
|
private:
|
|
|
|
FShaderResourceParameter ReflectionEnvironmentColorTexture;
|
|
FShaderResourceParameter ReflectionEnvironmentColorSampler;
|
|
FShaderParameter NumCaptureArrayMips;
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(,FComputeBrightnessPS,TEXT("ReflectionEnvironmentShaders"),TEXT("ComputeBrightnessMain"),SF_Pixel);
|
|
|
|
void CreateCubeMips( FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 NumMips, FSceneRenderTargetItem& Cubemap )
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CreateCubeMips);
|
|
|
|
FTextureRHIParamRef CubeRef = Cubemap.TargetableTexture.GetReference();
|
|
|
|
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
|
|
|
// Downsample all the mips, each one reads from the mip above it
|
|
for (int32 MipIndex = 1; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
|
|
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
SetRenderTarget(RHICmdList, Cubemap.TargetableTexture, MipIndex, CubeFace, NULL, true);
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
const FIntRect ViewRect(0, 0, MipSize, MipSize);
|
|
RHICmdList.SetViewport(0, 0, 0.0f, MipSize, MipSize, 1.0f);
|
|
|
|
|
|
TShaderMapRef<FScreenVS> VertexShader( ShaderMap );
|
|
TShaderMapRef<FCubeFilterPS> PixelShader( ShaderMap );
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
|
|
{
|
|
const FPixelShaderRHIParamRef ShaderRHI = PixelShader->GetPixelShader();
|
|
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->CubeFace, CubeFace );
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->MipIndex, MipIndex );
|
|
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->NumMips, NumMips );
|
|
|
|
SetSRVParameter( RHICmdList, ShaderRHI, PixelShader->SourceTexture, Cubemap.MipSRVs[ MipIndex - 1 ] );
|
|
SetSamplerParameter( RHICmdList, ShaderRHI, PixelShader->SourceTextureSampler, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI() );
|
|
}
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
FIntPoint(ViewRect.Width(), ViewRect.Height()),
|
|
FIntPoint(MipSize, MipSize),
|
|
*VertexShader);
|
|
|
|
//Use ERWSubResBarrier since we don't transition individual subresources. Basically treat the whole texture as R/W as we walk down the mip chain.
|
|
RHICmdList.TransitionResources(EResourceTransitionAccess::ERWSubResBarrier, &CubeRef, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Computes the average brightness of the given reflection capture and stores it in the scene. */
|
|
float ComputeSingleAverageBrightnessFromCubemap(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 TargetSize)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ComputeSingleAverageBrightnessFromCubemap);
|
|
|
|
TRefCountPtr<IPooledRenderTarget> ReflectionBrightnessTarget;
|
|
FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(FIntPoint(1, 1), PF_FloatRGBA, FClearValueBinding::None, TexCreate_None, TexCreate_RenderTargetable, false));
|
|
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, ReflectionBrightnessTarget, TEXT("ReflectionBrightness"));
|
|
|
|
FTextureRHIRef& BrightnessTarget = ReflectionBrightnessTarget->GetRenderTargetItem().TargetableTexture;
|
|
SetRenderTarget(RHICmdList, BrightnessTarget, NULL, true);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
|
|
|
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
TShaderMapRef<FPostProcessVS> VertexShader(ShaderMap);
|
|
TShaderMapRef<FComputeBrightnessPS> PixelShader(ShaderMap);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
|
|
PixelShader->SetParameters(RHICmdList, TargetSize);
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
0, 0,
|
|
1, 1,
|
|
0, 0,
|
|
1, 1,
|
|
FIntPoint(1, 1),
|
|
FIntPoint(1, 1),
|
|
*VertexShader);
|
|
|
|
RHICmdList.CopyToResolveTarget(BrightnessTarget, BrightnessTarget, true, FResolveParams());
|
|
|
|
FSceneRenderTargetItem& EffectiveRT = ReflectionBrightnessTarget->GetRenderTargetItem();
|
|
check(EffectiveRT.ShaderResourceTexture->GetFormat() == PF_FloatRGBA);
|
|
|
|
TArray<FFloat16Color> SurfaceData;
|
|
RHICmdList.ReadSurfaceFloatData(EffectiveRT.ShaderResourceTexture, FIntRect(0, 0, 1, 1), SurfaceData, CubeFace_PosX, 0, 0);
|
|
|
|
float AverageBrightness = SurfaceData[0].R.GetFloat();
|
|
return AverageBrightness;
|
|
}
|
|
|
|
void ComputeAverageBrightness(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 CubmapSize, float& OutAverageBrightness)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ComputeAverageBrightness);
|
|
|
|
const int32 EffectiveTopMipSize = CubmapSize;
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
|
|
// necessary to resolve the clears which touched all the mips. scene rendering only resolves mip 0.
|
|
FullyResolveReflectionScratchCubes(RHICmdList);
|
|
|
|
FSceneRenderTargetItem& DownSampledCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
CreateCubeMips( RHICmdList, FeatureLevel, NumMips, DownSampledCube );
|
|
|
|
OutAverageBrightness = ComputeSingleAverageBrightnessFromCubemap(RHICmdList, FeatureLevel, CubmapSize);
|
|
}
|
|
|
|
/** Generates mips for glossiness and filters the cubemap for a given reflection. */
|
|
void FilterReflectionEnvironment(FRHICommandListImmediate& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, int32 CubmapSize, FSHVectorRGB3* OutIrradianceEnvironmentMap)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, FilterReflectionEnvironment);
|
|
|
|
const int32 EffectiveTopMipSize = CubmapSize;
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
|
|
FSceneRenderTargetItem& EffectiveColorRT = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_Zero, BF_DestAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
|
|
|
|
// Premultiply alpha in-place using alpha blending
|
|
for (uint32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
SetRenderTarget(RHICmdList, EffectiveColorRT.TargetableTexture, 0, CubeFace, NULL, true);
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
const FIntPoint SourceDimensions(CubmapSize, CubmapSize);
|
|
const FIntRect ViewRect(0, 0, EffectiveTopMipSize, EffectiveTopMipSize);
|
|
RHICmdList.SetViewport(0, 0, 0.0f, EffectiveTopMipSize, EffectiveTopMipSize, 1.0f);
|
|
|
|
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
|
|
TShaderMapRef<FOneColorPS> PixelShader(GetGlobalShaderMap(FeatureLevel));
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
|
|
FLinearColor UnusedColors[1] = { FLinearColor::Black };
|
|
PixelShader->SetColors(RHICmdList, UnusedColors, ARRAY_COUNT(UnusedColors));
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
0, 0,
|
|
SourceDimensions.X, SourceDimensions.Y,
|
|
FIntPoint(ViewRect.Width(), ViewRect.Height()),
|
|
SourceDimensions,
|
|
*VertexShader);
|
|
|
|
RHICmdList.CopyToResolveTarget(EffectiveColorRT.TargetableTexture, EffectiveColorRT.ShaderResourceTexture, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace));
|
|
}
|
|
|
|
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
|
|
FSceneRenderTargetItem& DownSampledCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
FSceneRenderTargetItem& FilteredCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
|
|
|
|
CreateCubeMips( RHICmdList, FeatureLevel, NumMips, DownSampledCube );
|
|
|
|
if (OutIrradianceEnvironmentMap)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ComputeDiffuseIrradiance);
|
|
|
|
const int32 NumDiffuseMips = FMath::CeilLogTwo( GDiffuseIrradianceCubemapSize ) + 1;
|
|
const int32 DiffuseConvolutionSourceMip = NumMips - NumDiffuseMips;
|
|
|
|
ComputeDiffuseIrradiance(RHICmdList, FeatureLevel, DownSampledCube.ShaderResourceTexture, DiffuseConvolutionSourceMip, OutIrradianceEnvironmentMap);
|
|
}
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, FilterCubeMap);
|
|
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
|
|
|
// Filter all the mips
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
|
|
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
SetRenderTarget(RHICmdList, FilteredCube.TargetableTexture, MipIndex, CubeFace, NULL, true);
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
|
|
const FIntRect ViewRect(0, 0, MipSize, MipSize);
|
|
RHICmdList.SetViewport(0, 0, 0.0f, MipSize, MipSize, 1.0f);
|
|
|
|
|
|
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
|
|
TShaderMapRef< TCubeFilterPS<1> > CaptureCubemapArrayPixelShader(GetGlobalShaderMap(FeatureLevel));
|
|
|
|
FCubeFilterPS* PixelShader;
|
|
|
|
PixelShader = *TShaderMapRef< TCubeFilterPS<0> >(ShaderMap);
|
|
check(PixelShader);
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
|
|
{
|
|
const FPixelShaderRHIParamRef ShaderRHI = PixelShader->GetPixelShader();
|
|
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->CubeFace, CubeFace );
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->MipIndex, MipIndex );
|
|
|
|
SetShaderValue( RHICmdList, ShaderRHI, PixelShader->NumMips, NumMips );
|
|
|
|
SetTextureParameter(
|
|
RHICmdList,
|
|
ShaderRHI,
|
|
PixelShader->SourceTexture,
|
|
PixelShader->SourceTextureSampler,
|
|
TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
|
|
DownSampledCube.ShaderResourceTexture);
|
|
}
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
FIntPoint(ViewRect.Width(), ViewRect.Height()),
|
|
FIntPoint(MipSize, MipSize),
|
|
*VertexShader);
|
|
|
|
RHICmdList.CopyToResolveTarget(FilteredCube.TargetableTexture, FilteredCube.ShaderResourceTexture, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace, MipIndex));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Vertex shader used when writing to a cubemap. */
|
|
class FCopyToCubeFaceVS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FCopyToCubeFaceVS,Global);
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FCopyToCubeFaceVS() {}
|
|
FCopyToCubeFaceVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
|
|
FGlobalShader(Initializer)
|
|
{
|
|
}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View)
|
|
{
|
|
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, GetVertexShader(),View.ViewUniformBuffer);
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(,FCopyToCubeFaceVS,TEXT("ReflectionEnvironmentShaders"),TEXT("CopyToCubeFaceVS"),SF_Vertex);
|
|
|
|
/** Pixel shader used when copying scene color from a scene render into a face of a reflection capture cubemap. */
|
|
class FCopySceneColorToCubeFacePS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FCopySceneColorToCubeFacePS,Global);
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Platform,OutEnvironment);
|
|
}
|
|
|
|
FCopySceneColorToCubeFacePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
|
|
FGlobalShader(Initializer)
|
|
{
|
|
DeferredParameters.Bind(Initializer.ParameterMap);
|
|
InTexture.Bind(Initializer.ParameterMap,TEXT("InTexture"));
|
|
InTextureSampler.Bind(Initializer.ParameterMap,TEXT("InTextureSampler"));
|
|
SkyLightCaptureParameters.Bind(Initializer.ParameterMap,TEXT("SkyLightCaptureParameters"));
|
|
LowerHemisphereColor.Bind(Initializer.ParameterMap,TEXT("LowerHemisphereColor"));
|
|
}
|
|
FCopySceneColorToCubeFacePS() {}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View, bool bCapturingForSkyLight, bool bLowerHemisphereIsBlack, const FLinearColor& LowerHemisphereColorValue)
|
|
{
|
|
const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
|
|
|
|
FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, ShaderRHI, View.ViewUniformBuffer);
|
|
DeferredParameters.Set(RHICmdList, ShaderRHI, View);
|
|
|
|
SetTextureParameter(
|
|
RHICmdList,
|
|
ShaderRHI,
|
|
InTexture,
|
|
InTextureSampler,
|
|
TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(),
|
|
FSceneRenderTargets::Get(RHICmdList).GetSceneColor()->GetRenderTargetItem().ShaderResourceTexture);
|
|
|
|
FVector SkyLightParametersValue = FVector::ZeroVector;
|
|
FScene* Scene = (FScene*)View.Family->Scene;
|
|
|
|
if (bCapturingForSkyLight)
|
|
{
|
|
// When capturing reflection captures, support forcing all low hemisphere lighting to be black
|
|
SkyLightParametersValue = FVector(0, 0, bLowerHemisphereIsBlack ? 1.0f : 0.0f);
|
|
}
|
|
else if (Scene->SkyLight && !Scene->SkyLight->bHasStaticLighting)
|
|
{
|
|
// When capturing reflection captures and there's a stationary sky light, mask out any pixels whose depth classify it as part of the sky
|
|
// This will allow changing the stationary sky light at runtime
|
|
SkyLightParametersValue = FVector(1, Scene->SkyLight->SkyDistanceThreshold, 0);
|
|
}
|
|
else
|
|
{
|
|
// When capturing reflection captures and there's no sky light, or only a static sky light, capture all depth ranges
|
|
SkyLightParametersValue = FVector(2, 0, 0);
|
|
}
|
|
|
|
SetShaderValue(RHICmdList, ShaderRHI, SkyLightCaptureParameters, SkyLightParametersValue);
|
|
SetShaderValue(RHICmdList, ShaderRHI, LowerHemisphereColor, LowerHemisphereColorValue);
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
|
|
Ar << DeferredParameters;
|
|
Ar << InTexture;
|
|
Ar << InTextureSampler;
|
|
Ar << SkyLightCaptureParameters;
|
|
Ar << LowerHemisphereColor;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
|
|
private:
|
|
FDeferredPixelShaderParameters DeferredParameters;
|
|
FShaderResourceParameter InTexture;
|
|
FShaderResourceParameter InTextureSampler;
|
|
FShaderParameter SkyLightCaptureParameters;
|
|
FShaderParameter LowerHemisphereColor;
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(,FCopySceneColorToCubeFacePS,TEXT("ReflectionEnvironmentShaders"),TEXT("CopySceneColorToCubeFaceColorPS"),SF_Pixel);
|
|
|
|
/** Pixel shader used when copying a cubemap into a face of a reflection capture cubemap. */
|
|
class FCopyCubemapToCubeFacePS : public FGlobalShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FCopyCubemapToCubeFacePS,Global);
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FCopyCubemapToCubeFacePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
|
|
FGlobalShader(Initializer)
|
|
{
|
|
CubeFace.Bind(Initializer.ParameterMap,TEXT("CubeFace"));
|
|
SourceTexture.Bind(Initializer.ParameterMap,TEXT("SourceTexture"));
|
|
SourceTextureSampler.Bind(Initializer.ParameterMap,TEXT("SourceTextureSampler"));
|
|
SkyLightCaptureParameters.Bind(Initializer.ParameterMap,TEXT("SkyLightCaptureParameters"));
|
|
LowerHemisphereColor.Bind(Initializer.ParameterMap,TEXT("LowerHemisphereColor"));
|
|
SinCosSourceCubemapRotation.Bind(Initializer.ParameterMap,TEXT("SinCosSourceCubemapRotation"));
|
|
}
|
|
FCopyCubemapToCubeFacePS() {}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, const FTexture* SourceCubemap, uint32 CubeFaceValue, bool bIsSkyLight, bool bLowerHemisphereIsBlack, float SourceCubemapRotation, const FLinearColor& LowerHemisphereColorValue)
|
|
{
|
|
const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
|
|
|
|
SetShaderValue(RHICmdList, ShaderRHI, CubeFace, CubeFaceValue);
|
|
|
|
SetTextureParameter(
|
|
RHICmdList,
|
|
ShaderRHI,
|
|
SourceTexture,
|
|
SourceTextureSampler,
|
|
SourceCubemap);
|
|
|
|
SetShaderValue(RHICmdList, ShaderRHI, SkyLightCaptureParameters, FVector(bIsSkyLight ? 1.0f : 0.0f, 0.0f, bLowerHemisphereIsBlack ? 1.0f : 0.0f));
|
|
SetShaderValue(RHICmdList, ShaderRHI, LowerHemisphereColor, LowerHemisphereColorValue);
|
|
|
|
SetShaderValue(RHICmdList, ShaderRHI, SinCosSourceCubemapRotation, FVector2D(FMath::Sin(SourceCubemapRotation), FMath::Cos(SourceCubemapRotation)));
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
|
|
Ar << CubeFace;
|
|
Ar << SourceTexture;
|
|
Ar << SourceTextureSampler;
|
|
Ar << SkyLightCaptureParameters;
|
|
Ar << LowerHemisphereColor;
|
|
Ar << SinCosSourceCubemapRotation;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
|
|
private:
|
|
FShaderParameter CubeFace;
|
|
FShaderResourceParameter SourceTexture;
|
|
FShaderResourceParameter SourceTextureSampler;
|
|
FShaderParameter SkyLightCaptureParameters;
|
|
FShaderParameter LowerHemisphereColor;
|
|
FShaderParameter SinCosSourceCubemapRotation;
|
|
};
|
|
|
|
IMPLEMENT_SHADER_TYPE(,FCopyCubemapToCubeFacePS,TEXT("ReflectionEnvironmentShaders"),TEXT("CopyCubemapToCubeFaceColorPS"),SF_Pixel);
|
|
|
|
int32 FindOrAllocateCubemapIndex(FScene* Scene, const UReflectionCaptureComponent* Component)
|
|
{
|
|
int32 CaptureIndex = -1;
|
|
|
|
// Try to find an existing capture index for this component
|
|
const FCaptureComponentSceneState* CaptureSceneStatePtr = Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Find(Component);
|
|
|
|
if (CaptureSceneStatePtr)
|
|
{
|
|
CaptureIndex = CaptureSceneStatePtr->CaptureIndex;
|
|
}
|
|
else
|
|
{
|
|
// Reuse a freed index if possible
|
|
CaptureIndex = Scene->ReflectionSceneData.CubemapArraySlotsUsed.FindAndSetFirstZeroBit();
|
|
if (CaptureIndex == INDEX_NONE)
|
|
{
|
|
// If we didn't find a free index, allocate a new one from the CubemapArraySlotsUsed bitfield
|
|
CaptureIndex = Scene->ReflectionSceneData.CubemapArraySlotsUsed.Num();
|
|
Scene->ReflectionSceneData.CubemapArraySlotsUsed.Add(true);
|
|
}
|
|
|
|
Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Add(Component, FCaptureComponentSceneState(CaptureIndex));
|
|
|
|
check(CaptureIndex < GMaxNumReflectionCaptures);
|
|
}
|
|
|
|
check(CaptureIndex >= 0);
|
|
return CaptureIndex;
|
|
}
|
|
|
|
void ClearScratchCubemaps(FRHICommandList& RHICmdList, int32 TargetSize)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ClearScratchCubemaps);
|
|
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
SceneContext.AllocateReflectionTargets(RHICmdList, TargetSize);
|
|
// Clear scratch render targets to a consistent but noticeable value
|
|
// This makes debugging capture issues much easier, otherwise the random contents from previous captures is shown
|
|
|
|
FSceneRenderTargetItem& RT0 = SceneContext.ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
int32 NumMips = (int32)RT0.TargetableTexture->GetNumMips();
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ClearScratchCubemapsRT0);
|
|
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
TransitionSetRenderTargetsHelper(RHICmdList, RT0.TargetableTexture, FTextureRHIParamRef(), FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
|
|
|
FRHIRenderTargetView RtView = FRHIRenderTargetView(RT0.TargetableTexture, ERenderTargetLoadAction::EClear, MipIndex, CubeFace);
|
|
FRHISetRenderTargetsInfo Info(1, &RtView, FRHIDepthRenderTargetView());
|
|
RHICmdList.SetRenderTargetsAndClear(Info);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, ClearScratchCubemapsRT1);
|
|
|
|
FSceneRenderTargetItem& RT1 = SceneContext.ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
|
|
NumMips = (int32)RT1.TargetableTexture->GetNumMips();
|
|
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
TransitionSetRenderTargetsHelper(RHICmdList, RT1.TargetableTexture, FTextureRHIParamRef(), FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
|
|
|
FRHIRenderTargetView RtView = FRHIRenderTargetView(RT1.TargetableTexture, ERenderTargetLoadAction::EClear, MipIndex, CubeFace);
|
|
FRHISetRenderTargetsInfo Info(1, &RtView, FRHIDepthRenderTargetView());
|
|
RHICmdList.SetRenderTargetsAndClear(Info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Captures the scene for a reflection capture by rendering the scene multiple times and copying into a cubemap texture. */
|
|
void CaptureSceneToScratchCubemap(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer, ECubeFace CubeFace, int32 CubemapSize, bool bCapturingForSkyLight, bool bLowerHemisphereIsBlack, const FLinearColor& LowerHemisphereColor)
|
|
{
|
|
FMemMark MemStackMark(FMemStack::Get());
|
|
|
|
// update any resources that needed a deferred update
|
|
FDeferredUpdateResource::UpdateResources(RHICmdList);
|
|
|
|
const auto FeatureLevel = SceneRenderer->FeatureLevel;
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CubeMapCapture);
|
|
|
|
// Render the scene normally for one face of the cubemap
|
|
SceneRenderer->Render(RHICmdList);
|
|
check(&RHICmdList == &FRHICommandListExecutor::GetImmediateCommandList());
|
|
check(IsInRenderingThread());
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_CaptureSceneToScratchCubemap_Flush);
|
|
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThread);
|
|
}
|
|
|
|
// some platforms may not be able to keep enqueueing commands like crazy, this will
|
|
// allow them to restart their command buffers
|
|
RHICmdList.SubmitCommandsAndFlushGPU();
|
|
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
SceneContext.AllocateReflectionTargets(RHICmdList, CubemapSize);
|
|
|
|
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
|
|
const int32 EffectiveSize = CubemapSize;
|
|
FSceneRenderTargetItem& EffectiveColorRT = SceneContext.ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CubeMapCopyScene);
|
|
|
|
// Copy the captured scene into the cubemap face
|
|
SetRenderTarget(RHICmdList, EffectiveColorRT.TargetableTexture, 0, CubeFace, NULL);
|
|
|
|
const FIntRect ViewRect(0, 0, EffectiveSize, EffectiveSize);
|
|
RHICmdList.SetViewport(0, 0, 0.0f, EffectiveSize, EffectiveSize, 1.0f);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
|
|
|
TShaderMapRef<FCopyToCubeFaceVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
|
|
TShaderMapRef<FCopySceneColorToCubeFacePS> PixelShader(GetGlobalShaderMap(FeatureLevel));
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
|
|
PixelShader->SetParameters(RHICmdList, SceneRenderer->Views[0], bCapturingForSkyLight, bLowerHemisphereIsBlack, LowerHemisphereColor);
|
|
VertexShader->SetParameters(RHICmdList, SceneRenderer->Views[0]);
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width() * GSupersampleCaptureFactor, ViewRect.Height() * GSupersampleCaptureFactor,
|
|
FIntPoint(ViewRect.Width(), ViewRect.Height()),
|
|
SceneContext.GetBufferSizeXY(),
|
|
*VertexShader);
|
|
|
|
RHICmdList.CopyToResolveTarget(EffectiveColorRT.TargetableTexture, EffectiveColorRT.ShaderResourceTexture, true, FResolveParams(FResolveRect(), CubeFace));
|
|
}
|
|
}
|
|
|
|
FSceneRenderer::WaitForTasksClearSnapshotsAndDeleteSceneRenderer(RHICmdList, SceneRenderer);
|
|
}
|
|
|
|
void CopyCubemapToScratchCubemap(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, UTextureCube* SourceCubemap, int32 CubemapSize, bool bIsSkyLight, bool bLowerHemisphereIsBlack, float SourceCubemapRotation, const FLinearColor& LowerHemisphereColorValue)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CopyCubemapToScratchCubemap);
|
|
check(SourceCubemap);
|
|
|
|
const int32 EffectiveSize = CubemapSize;
|
|
FSceneRenderTargetItem& EffectiveColorRT = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[0]->GetRenderTargetItem();
|
|
|
|
for (uint32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
// Copy the captured scene into the cubemap face
|
|
SetRenderTarget(RHICmdList, EffectiveColorRT.TargetableTexture, 0, CubeFace, NULL, true);
|
|
|
|
const FTexture* SourceCubemapResource = SourceCubemap->Resource;
|
|
const FIntPoint SourceDimensions(SourceCubemapResource->GetSizeX(), SourceCubemapResource->GetSizeY());
|
|
const FIntRect ViewRect(0, 0, EffectiveSize, EffectiveSize);
|
|
RHICmdList.SetViewport(0, 0, 0.0f, EffectiveSize, EffectiveSize, 1.0f);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
|
|
|
|
TShaderMapRef<FScreenVS> VertexShader(GetGlobalShaderMap(FeatureLevel));
|
|
TShaderMapRef<FCopyCubemapToCubeFacePS> PixelShader(GetGlobalShaderMap(FeatureLevel));
|
|
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
|
|
PixelShader->SetParameters(RHICmdList, SourceCubemapResource, CubeFace, bIsSkyLight, bLowerHemisphereIsBlack, SourceCubemapRotation, LowerHemisphereColorValue);
|
|
|
|
DrawRectangle(
|
|
RHICmdList,
|
|
ViewRect.Min.X, ViewRect.Min.Y,
|
|
ViewRect.Width(), ViewRect.Height(),
|
|
0, 0,
|
|
SourceDimensions.X, SourceDimensions.Y,
|
|
FIntPoint(ViewRect.Width(), ViewRect.Height()),
|
|
SourceDimensions,
|
|
*VertexShader);
|
|
|
|
RHICmdList.CopyToResolveTarget(EffectiveColorRT.TargetableTexture, EffectiveColorRT.ShaderResourceTexture, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocates reflection captures in the scene's reflection cubemap array and updates them by recapturing the scene.
|
|
* Existing captures will only be updated. Must be called from the game thread.
|
|
*/
|
|
void FScene::AllocateReflectionCaptures(const TArray<UReflectionCaptureComponent*>& NewCaptures)
|
|
{
|
|
if (NewCaptures.Num() > 0)
|
|
{
|
|
if (GetFeatureLevel() >= ERHIFeatureLevel::SM5)
|
|
{
|
|
for (int32 CaptureIndex = 0; CaptureIndex < NewCaptures.Num(); CaptureIndex++)
|
|
{
|
|
bool bAlreadyExists = false;
|
|
|
|
// Try to find an existing allocation
|
|
for (TSparseArray<UReflectionCaptureComponent*>::TIterator It(ReflectionSceneData.AllocatedReflectionCapturesGameThread); It; ++It)
|
|
{
|
|
UReflectionCaptureComponent* OtherComponent = *It;
|
|
|
|
if (OtherComponent == NewCaptures[CaptureIndex])
|
|
{
|
|
bAlreadyExists = true;
|
|
}
|
|
}
|
|
|
|
// Add the capture to the allocated list
|
|
if (!bAlreadyExists && ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num() < GMaxNumReflectionCaptures)
|
|
{
|
|
ReflectionSceneData.AllocatedReflectionCapturesGameThread.Add(NewCaptures[CaptureIndex]);
|
|
}
|
|
}
|
|
|
|
// Request the exact amount needed by default
|
|
int32 DesiredMaxCubemaps = ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num();
|
|
const float MaxCubemapsRoundUpBase = 1.5f;
|
|
|
|
// If this is not the first time the scene has allocated the cubemap array, include slack to reduce reallocations
|
|
if (ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread > 0)
|
|
{
|
|
float Exponent = FMath::LogX(MaxCubemapsRoundUpBase, ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num());
|
|
|
|
// Round up to the next integer exponent to provide stability and reduce reallocations
|
|
DesiredMaxCubemaps = FMath::Pow(MaxCubemapsRoundUpBase, FMath::TruncToInt(Exponent) + 1);
|
|
}
|
|
|
|
DesiredMaxCubemaps = FMath::Min(DesiredMaxCubemaps, GMaxNumReflectionCaptures);
|
|
|
|
const int32 ReflectionCaptureSize = UReflectionCaptureComponent::GetReflectionCaptureSize_GameThread();
|
|
bool bNeedsUpdateAllCaptures = DesiredMaxCubemaps != ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread || ReflectionCaptureSize != ReflectionSceneData.CubemapArray.GetCubemapSize();
|
|
|
|
if (DoGPUArrayCopy() && bNeedsUpdateAllCaptures)
|
|
{
|
|
// If we're not in the editor, we discard the CPU-side reflection capture data after loading to save memory, so we can't resize if the resolution changes. If this happens, we assert
|
|
check(GIsEditor || ReflectionCaptureSize == ReflectionSceneData.CubemapArray.GetCubemapSize() || ReflectionSceneData.CubemapArray.GetCubemapSize() == 0);
|
|
|
|
if (ReflectionCaptureSize == ReflectionSceneData.CubemapArray.GetCubemapSize())
|
|
{
|
|
// We can do a fast GPU copy to realloc the array, so we don't need to update all captures
|
|
ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread = DesiredMaxCubemaps;
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
GPUResizeArrayCommand,
|
|
FScene*, Scene, this,
|
|
uint32, MaxSize, ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread,
|
|
int32, ReflectionCaptureSize, ReflectionCaptureSize,
|
|
{
|
|
// Update the scene's cubemap array, preserving the original contents with a GPU-GPU copy
|
|
Scene->ReflectionSceneData.ResizeCubemapArrayGPU(MaxSize, ReflectionCaptureSize);
|
|
});
|
|
|
|
bNeedsUpdateAllCaptures = false;
|
|
}
|
|
}
|
|
|
|
if (bNeedsUpdateAllCaptures)
|
|
{
|
|
ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread = DesiredMaxCubemaps;
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
ResizeArrayCommand,
|
|
FScene*, Scene, this,
|
|
uint32, MaxSize, ReflectionSceneData.MaxAllocatedReflectionCubemapsGameThread,
|
|
int32, ReflectionCaptureSize, ReflectionCaptureSize,
|
|
{
|
|
// Update the scene's cubemap array, which will reallocate it, so we no longer have the contents of existing entries
|
|
Scene->ReflectionSceneData.CubemapArray.UpdateMaxCubemaps(MaxSize, ReflectionCaptureSize);
|
|
});
|
|
|
|
// Recapture all reflection captures now that we have reallocated the cubemap array
|
|
UpdateAllReflectionCaptures();
|
|
}
|
|
else
|
|
{
|
|
// No teardown of the cubemap array was needed, just update the captures that were requested
|
|
for (TSparseArray<UReflectionCaptureComponent*>::TIterator It(ReflectionSceneData.AllocatedReflectionCapturesGameThread); It; ++It)
|
|
{
|
|
UReflectionCaptureComponent* CurrentComponent = *It;
|
|
|
|
if (NewCaptures.Contains(CurrentComponent))
|
|
{
|
|
UpdateReflectionCaptureContents(CurrentComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (GetFeatureLevel() == ERHIFeatureLevel::SM4)
|
|
{
|
|
for (int32 ComponentIndex = 0; ComponentIndex < NewCaptures.Num(); ComponentIndex++)
|
|
{
|
|
UReflectionCaptureComponent* CurrentComponent = NewCaptures[ComponentIndex];
|
|
UpdateReflectionCaptureContents(CurrentComponent);
|
|
}
|
|
}
|
|
|
|
for (int32 CaptureIndex = 0; CaptureIndex < NewCaptures.Num(); CaptureIndex++)
|
|
{
|
|
UReflectionCaptureComponent* Component = NewCaptures[CaptureIndex];
|
|
|
|
Component->SetCaptureCompleted();
|
|
|
|
if (Component->SceneProxy)
|
|
{
|
|
// Update the transform of the reflection capture
|
|
// This is not done earlier by the reflection capture when it detects that it is dirty,
|
|
// To ensure that the RT sees both the new transform and the new contents on the same frame.
|
|
Component->SendRenderTransform_Concurrent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Updates the contents of all reflection captures in the scene. Must be called from the game thread. */
|
|
void FScene::UpdateAllReflectionCaptures()
|
|
{
|
|
if (IsReflectionEnvironmentAvailable(GetFeatureLevel()))
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
|
|
CaptureCommand,
|
|
FScene*, Scene, this,
|
|
{
|
|
Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Empty();
|
|
Scene->ReflectionSceneData.CubemapArraySlotsUsed.Reset();
|
|
});
|
|
|
|
const int32 UpdateDivisor = FMath::Max(ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num() / 20, 1);
|
|
const bool bDisplayStatus = ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num() > 50;
|
|
|
|
if (bDisplayStatus)
|
|
{
|
|
const FText Status = NSLOCTEXT("Engine", "BeginReflectionCapturesTask", "Updating Reflection Captures...");
|
|
GWarn->BeginSlowTask( Status, true );
|
|
GWarn->StatusUpdate(0, ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num(), Status);
|
|
}
|
|
|
|
int32 CaptureIndex = 0;
|
|
|
|
for (TSparseArray<UReflectionCaptureComponent*>::TIterator It(ReflectionSceneData.AllocatedReflectionCapturesGameThread); It; ++It)
|
|
{
|
|
// Update progress occasionally
|
|
if (bDisplayStatus && CaptureIndex % UpdateDivisor == 0)
|
|
{
|
|
GWarn->UpdateProgress(CaptureIndex, ReflectionSceneData.AllocatedReflectionCapturesGameThread.Num());
|
|
}
|
|
|
|
CaptureIndex++;
|
|
UReflectionCaptureComponent* CurrentComponent = *It;
|
|
UpdateReflectionCaptureContents(CurrentComponent);
|
|
}
|
|
|
|
if (bDisplayStatus)
|
|
{
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetReflectionCaptureData_RenderingThread(FRHICommandListImmediate& RHICmdList, FScene* Scene, const UReflectionCaptureComponent* Component, FReflectionCaptureFullHDR* OutDerivedData)
|
|
{
|
|
const FCaptureComponentSceneState* ComponentStatePtr = Scene->ReflectionSceneData.AllocatedReflectionCaptureState.Find(Component);
|
|
|
|
if (ComponentStatePtr)
|
|
{
|
|
FSceneRenderTargetItem& EffectiveDest = Scene->ReflectionSceneData.CubemapArray.GetRenderTarget();
|
|
|
|
const int32 CaptureIndex = ComponentStatePtr->CaptureIndex;
|
|
const int32 NumMips = EffectiveDest.ShaderResourceTexture->GetNumMips();
|
|
const int32 EffectiveTopMipSize = FMath::Pow(2, NumMips - 1);
|
|
|
|
TArray<uint8> CaptureData;
|
|
int32 CaptureDataSize = 0;
|
|
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
|
|
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
CaptureDataSize += MipSize * MipSize * sizeof(FFloat16Color);
|
|
}
|
|
}
|
|
|
|
CaptureData.Empty(CaptureDataSize);
|
|
CaptureData.AddZeroed(CaptureDataSize);
|
|
int32 MipBaseIndex = 0;
|
|
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
check(EffectiveDest.ShaderResourceTexture->GetFormat() == PF_FloatRGBA);
|
|
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
|
|
const int32 CubeFaceBytes = MipSize * MipSize * sizeof(FFloat16Color);
|
|
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
TArray<FFloat16Color> SurfaceData;
|
|
// Read each mip face
|
|
//@todo - do this without blocking the GPU so many times
|
|
//@todo - pool the temporary textures in RHIReadSurfaceFloatData instead of always creating new ones
|
|
RHICmdList.ReadSurfaceFloatData(EffectiveDest.ShaderResourceTexture, FIntRect(0, 0, MipSize, MipSize), SurfaceData, (ECubeFace)CubeFace, CaptureIndex, MipIndex);
|
|
const int32 DestIndex = MipBaseIndex + CubeFace * CubeFaceBytes;
|
|
uint8* FaceData = &CaptureData[DestIndex];
|
|
check(SurfaceData.Num() * SurfaceData.GetTypeSize() == CubeFaceBytes);
|
|
FMemory::Memcpy(FaceData, SurfaceData.GetData(), CubeFaceBytes);
|
|
}
|
|
|
|
MipBaseIndex += CubeFaceBytes * CubeFace_MAX;
|
|
}
|
|
|
|
OutDerivedData->InitializeFromUncompressedData(CaptureData, EffectiveTopMipSize);
|
|
}
|
|
}
|
|
|
|
void FScene::GetReflectionCaptureData(UReflectionCaptureComponent* Component, FReflectionCaptureFullHDR& OutDerivedData)
|
|
{
|
|
check(GetFeatureLevel() >= ERHIFeatureLevel::SM5);
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
GetReflectionDataCommand,
|
|
FScene*,Scene,this,
|
|
const UReflectionCaptureComponent*,Component,Component,
|
|
FReflectionCaptureFullHDR*,OutDerivedData,&OutDerivedData,
|
|
{
|
|
GetReflectionCaptureData_RenderingThread(RHICmdList, Scene, Component, OutDerivedData);
|
|
});
|
|
|
|
// Necessary since the RT is writing to OutDerivedData directly
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
void UploadReflectionCapture_RenderingThread(FScene* Scene, const FReflectionCaptureFullHDR* FullHDRData, const UReflectionCaptureComponent* CaptureComponent)
|
|
{
|
|
const int32 EffectiveTopMipSize = FullHDRData->CubemapSize;
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
|
|
const int32 CaptureIndex = FindOrAllocateCubemapIndex(Scene, CaptureComponent);
|
|
FTextureCubeRHIRef& CubeMapArray = (FTextureCubeRHIRef&)Scene->ReflectionSceneData.CubemapArray.GetRenderTarget().ShaderResourceTexture;
|
|
check(CubeMapArray->GetFormat() == PF_FloatRGBA);
|
|
|
|
TRefCountPtr<FReflectionCaptureUncompressedData> SourceCubemapData = FullHDRData->GetUncompressedData();
|
|
int32 MipBaseIndex = 0;
|
|
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
const int32 MipSize = 1 << (NumMips - MipIndex - 1);
|
|
const int32 CubeFaceBytes = MipSize * MipSize * sizeof(FFloat16Color);
|
|
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
uint32 DestStride = 0;
|
|
uint8* DestBuffer = (uint8*)RHILockTextureCubeFace(CubeMapArray, CubeFace, CaptureIndex, MipIndex, RLM_WriteOnly, DestStride, false);
|
|
|
|
// Handle DestStride by copying each row
|
|
for (int32 Y = 0; Y < MipSize; Y++)
|
|
{
|
|
FFloat16Color* DestPtr = (FFloat16Color*)((uint8*)DestBuffer + Y * DestStride);
|
|
const int32 SourceIndex = MipBaseIndex + CubeFace * CubeFaceBytes + Y * MipSize * sizeof(FFloat16Color);
|
|
const uint8* SourcePtr = SourceCubemapData->GetData(SourceIndex);
|
|
FMemory::Memcpy(DestPtr, SourcePtr, MipSize * sizeof(FFloat16Color));
|
|
}
|
|
|
|
RHIUnlockTextureCubeFace(CubeMapArray, CubeFace, CaptureIndex, MipIndex, false);
|
|
}
|
|
|
|
MipBaseIndex += CubeFaceBytes * CubeFace_MAX;
|
|
}
|
|
}
|
|
|
|
/** Creates a transformation for a cubemap face, following the D3D cubemap layout. */
|
|
FMatrix CalcCubeFaceViewRotationMatrix(ECubeFace Face)
|
|
{
|
|
FMatrix Result(FMatrix::Identity);
|
|
|
|
static const FVector XAxis(1.f,0.f,0.f);
|
|
static const FVector YAxis(0.f,1.f,0.f);
|
|
static const FVector ZAxis(0.f,0.f,1.f);
|
|
|
|
// vectors we will need for our basis
|
|
FVector vUp(YAxis);
|
|
FVector vDir;
|
|
|
|
switch( Face )
|
|
{
|
|
case CubeFace_PosX:
|
|
vDir = XAxis;
|
|
break;
|
|
case CubeFace_NegX:
|
|
vDir = -XAxis;
|
|
break;
|
|
case CubeFace_PosY:
|
|
vUp = -ZAxis;
|
|
vDir = YAxis;
|
|
break;
|
|
case CubeFace_NegY:
|
|
vUp = ZAxis;
|
|
vDir = -YAxis;
|
|
break;
|
|
case CubeFace_PosZ:
|
|
vDir = ZAxis;
|
|
break;
|
|
case CubeFace_NegZ:
|
|
vDir = -ZAxis;
|
|
break;
|
|
}
|
|
|
|
// derive right vector
|
|
FVector vRight( vUp ^ vDir );
|
|
// create matrix from the 3 axes
|
|
Result = FBasisVectorMatrix( vRight, vUp, vDir, FVector::ZeroVector );
|
|
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Render target class required for rendering the scene.
|
|
* This doesn't actually allocate a render target as we read from scene color to get HDR results directly.
|
|
*/
|
|
class FCaptureRenderTarget : public FRenderResource, public FRenderTarget
|
|
{
|
|
public:
|
|
|
|
FCaptureRenderTarget() :
|
|
Size(0)
|
|
{}
|
|
|
|
virtual const FTexture2DRHIRef& GetRenderTargetTexture() const
|
|
{
|
|
static FTexture2DRHIRef DummyTexture;
|
|
return DummyTexture;
|
|
}
|
|
|
|
void SetSize(int32 TargetSize) { Size = TargetSize; }
|
|
virtual FIntPoint GetSizeXY() const { return FIntPoint(Size, Size); }
|
|
virtual float GetDisplayGamma() const { return 1.0f; }
|
|
|
|
private:
|
|
|
|
int32 Size;
|
|
};
|
|
|
|
TGlobalResource<FCaptureRenderTarget> GReflectionCaptureRenderTarget;
|
|
|
|
void CaptureSceneIntoScratchCubemap(
|
|
FScene* Scene,
|
|
FVector CapturePosition,
|
|
int32 CubemapSize,
|
|
bool bCapturingForSkyLight,
|
|
bool bStaticSceneOnly,
|
|
float SkyLightNearPlane,
|
|
bool bLowerHemisphereIsBlack,
|
|
bool bCaptureEmissiveOnly,
|
|
const FLinearColor& LowerHemisphereColor
|
|
)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
if( !bCapturingForSkyLight )
|
|
{
|
|
// Alert the RHI that we're rendering a new frame
|
|
// Not really a new frame, but it will allow pooling mechanisms to update, like the uniform buffer pool
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
BeginFrame,
|
|
{
|
|
GFrameNumberRenderThread++;
|
|
RHICmdList.BeginFrame();
|
|
})
|
|
}
|
|
|
|
GReflectionCaptureRenderTarget.SetSize(CubemapSize);
|
|
|
|
auto ViewFamilyInit = FSceneViewFamily::ConstructionValues(
|
|
&GReflectionCaptureRenderTarget,
|
|
Scene,
|
|
FEngineShowFlags(ESFIM_Game)
|
|
)
|
|
.SetResolveScene(false);
|
|
|
|
if( bStaticSceneOnly )
|
|
{
|
|
ViewFamilyInit.SetWorldTimes( 0.0f, 0.0f, 0.0f );
|
|
}
|
|
|
|
FSceneViewFamilyContext ViewFamily( ViewFamilyInit );
|
|
|
|
// Disable features that are not desired when capturing the scene
|
|
ViewFamily.EngineShowFlags.PostProcessing = 0;
|
|
ViewFamily.EngineShowFlags.MotionBlur = 0;
|
|
ViewFamily.EngineShowFlags.SetOnScreenDebug(false);
|
|
ViewFamily.EngineShowFlags.HMDDistortion = 0;
|
|
// Exclude particles and light functions as they are usually dynamic, and can't be captured well
|
|
ViewFamily.EngineShowFlags.Particles = 0;
|
|
ViewFamily.EngineShowFlags.LightFunctions = 0;
|
|
ViewFamily.EngineShowFlags.SetCompositeEditorPrimitives(false);
|
|
// These are highly dynamic and can't be captured effectively
|
|
ViewFamily.EngineShowFlags.LightShafts = 0;
|
|
// Don't apply sky lighting diffuse when capturing the sky light source, or we would have feedback
|
|
ViewFamily.EngineShowFlags.SkyLighting = !bCapturingForSkyLight;
|
|
// Skip lighting for emissive only
|
|
ViewFamily.EngineShowFlags.Lighting = !bCaptureEmissiveOnly;
|
|
|
|
FSceneViewInitOptions ViewInitOptions;
|
|
ViewInitOptions.ViewFamily = &ViewFamily;
|
|
ViewInitOptions.BackgroundColor = FLinearColor::Black;
|
|
ViewInitOptions.OverlayColor = FLinearColor::Black;
|
|
ViewInitOptions.SetViewRectangle(FIntRect(0, 0, CubemapSize * GSupersampleCaptureFactor, CubemapSize * GSupersampleCaptureFactor));
|
|
|
|
const float NearPlane = bCapturingForSkyLight ? SkyLightNearPlane : GReflectionCaptureNearPlane;
|
|
|
|
// Projection matrix based on the fov, near / far clip settings
|
|
// Each face always uses a 90 degree field of view
|
|
if ((bool)ERHIZBuffer::IsInverted)
|
|
{
|
|
ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix(
|
|
90.0f * (float)PI / 360.0f,
|
|
(float)CubemapSize * GSupersampleCaptureFactor,
|
|
(float)CubemapSize * GSupersampleCaptureFactor,
|
|
NearPlane
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix(
|
|
90.0f * (float)PI / 360.0f,
|
|
(float)CubemapSize * GSupersampleCaptureFactor,
|
|
(float)CubemapSize * GSupersampleCaptureFactor,
|
|
NearPlane
|
|
);
|
|
}
|
|
|
|
ViewInitOptions.ViewOrigin = CapturePosition;
|
|
ViewInitOptions.ViewRotationMatrix = CalcCubeFaceViewRotationMatrix((ECubeFace)CubeFace);
|
|
|
|
FSceneView* View = new FSceneView(ViewInitOptions);
|
|
|
|
// Force all surfaces diffuse
|
|
View->RoughnessOverrideParameter = FVector2D( 1.0f, 0.0f );
|
|
|
|
if (bCaptureEmissiveOnly)
|
|
{
|
|
View->DiffuseOverrideParameter = FVector4(0, 0, 0, 0);
|
|
View->SpecularOverrideParameter = FVector4(0, 0, 0, 0);
|
|
}
|
|
|
|
View->bIsReflectionCapture = true;
|
|
View->bStaticSceneOnly = bStaticSceneOnly;
|
|
View->StartFinalPostprocessSettings(CapturePosition);
|
|
View->EndFinalPostprocessSettings(ViewInitOptions);
|
|
|
|
ViewFamily.Views.Add(View);
|
|
|
|
FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(&ViewFamily, NULL);
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_SIXPARAMETER(
|
|
CaptureCommand,
|
|
FSceneRenderer*, SceneRenderer, SceneRenderer,
|
|
ECubeFace, CubeFace, (ECubeFace)CubeFace,
|
|
int32, CubemapSize, CubemapSize,
|
|
bool, bCapturingForSkyLight, bCapturingForSkyLight,
|
|
bool, bLowerHemisphereIsBlack, bLowerHemisphereIsBlack,
|
|
FLinearColor, LowerHemisphereColor, LowerHemisphereColor,
|
|
{
|
|
CaptureSceneToScratchCubemap(RHICmdList, SceneRenderer, CubeFace, CubemapSize, bCapturingForSkyLight, bLowerHemisphereIsBlack, LowerHemisphereColor);
|
|
|
|
if( !bCapturingForSkyLight )
|
|
{
|
|
RHICmdList.EndFrame();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void CopyToSceneArray(FRHICommandListImmediate& RHICmdList, FScene* Scene, FReflectionCaptureProxy* ReflectionProxy)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CopyToSceneArray);
|
|
const int32 EffectiveTopMipSize = UReflectionCaptureComponent::GetReflectionCaptureSize_RenderThread();
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
|
|
const int32 CaptureIndex = FindOrAllocateCubemapIndex(Scene, ReflectionProxy->Component);
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
|
|
FSceneRenderTargetItem& FilteredCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
|
|
FSceneRenderTargetItem& DestCube = Scene->ReflectionSceneData.CubemapArray.GetRenderTarget();
|
|
|
|
// GPU copy back to the scene's texture array, which is not a render target
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
RHICmdList.CopyToResolveTarget(FilteredCube.ShaderResourceTexture, DestCube.ShaderResourceTexture, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace, MipIndex, 0, CaptureIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CopyToComponentTexture(FRHICommandList& RHICmdList, FScene* Scene, FReflectionCaptureProxy* ReflectionProxy)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CopyToComponentTexture);
|
|
check(ReflectionProxy->SM4FullHDRCubemap);
|
|
|
|
const int32 EffectiveTopMipSize = UReflectionCaptureComponent::GetReflectionCaptureSize_RenderThread();
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
|
|
FSceneRenderTargetItem& FilteredCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
|
|
|
|
// GPU copy back to the component's cubemap texture, which is not a render target
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
RHICmdList.CopyToResolveTarget(FilteredCube.ShaderResourceTexture, ReflectionProxy->SM4FullHDRCubemap->TextureRHI, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace, MipIndex, 0, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the contents of the given reflection capture by rendering the scene.
|
|
* This must be called on the game thread.
|
|
*/
|
|
void FScene::UpdateReflectionCaptureContents(UReflectionCaptureComponent* CaptureComponent)
|
|
{
|
|
const bool bCubemapSpecified = CaptureComponent->ReflectionSourceType == EReflectionSourceType::SpecifiedCubemap && CaptureComponent->Cubemap;
|
|
const int32 ReflectionCaptureSize = UReflectionCaptureComponent::GetReflectionCaptureSize_GameThread();
|
|
|
|
if (IsReflectionEnvironmentAvailable(GetFeatureLevel()) || bCubemapSpecified)
|
|
{
|
|
const FReflectionCaptureFullHDR* DerivedData = CaptureComponent->GetFullHDRData();
|
|
|
|
// Upload existing derived data if it exists, instead of capturing
|
|
if (DerivedData && DerivedData->HasValidData() )
|
|
{
|
|
// For other feature levels the reflection textures are stored on the component instead of in a scene-wide texture array
|
|
if (GetFeatureLevel() >= ERHIFeatureLevel::SM5)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
UploadCaptureCommand,
|
|
FScene*, Scene, this,
|
|
const FReflectionCaptureFullHDR*, DerivedData, DerivedData,
|
|
const UReflectionCaptureComponent*, CaptureComponent, CaptureComponent,
|
|
{
|
|
UploadReflectionCapture_RenderingThread(Scene, DerivedData, CaptureComponent);
|
|
});
|
|
|
|
if ( DoGPUArrayCopy() && !GIsEditor )
|
|
{
|
|
// We no longer need the HDR data, since we have a copy on the GPU
|
|
// In the editor we need this data for serialization, however
|
|
DerivedData = nullptr;
|
|
CaptureComponent->ReleaseHDRData();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CaptureComponent->ReflectionSourceType == EReflectionSourceType::SpecifiedCubemap && !CaptureComponent->Cubemap)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
|
|
ClearCommand,
|
|
int32, ReflectionCaptureSize, ReflectionCaptureSize,
|
|
{
|
|
ClearScratchCubemaps(RHICmdList, ReflectionCaptureSize);
|
|
});
|
|
|
|
if (CaptureComponent->ReflectionSourceType == EReflectionSourceType::CapturedScene)
|
|
{
|
|
CaptureSceneIntoScratchCubemap(this, CaptureComponent->GetComponentLocation() + CaptureComponent->CaptureOffset, ReflectionCaptureSize, false, true, 0, false, false, FLinearColor());
|
|
}
|
|
else if (CaptureComponent->ReflectionSourceType == EReflectionSourceType::SpecifiedCubemap)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_FOURPARAMETER(
|
|
CopyCubemapCommand,
|
|
UTextureCube*, SourceTexture, CaptureComponent->Cubemap,
|
|
int32, ReflectionCaptureSize, ReflectionCaptureSize,
|
|
float, SourceCubemapRotation, CaptureComponent->SourceCubemapAngle * (PI / 180.f),
|
|
ERHIFeatureLevel::Type, FeatureLevel, GetFeatureLevel(),
|
|
{
|
|
CopyCubemapToScratchCubemap(RHICmdList, FeatureLevel, SourceTexture, ReflectionCaptureSize, false, false, SourceCubemapRotation, FLinearColor());
|
|
});
|
|
}
|
|
else
|
|
{
|
|
check(!TEXT("Unknown reflection source type"));
|
|
}
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
FilterCommand,
|
|
ERHIFeatureLevel::Type, FeatureLevel, GetFeatureLevel(),
|
|
int32, ReflectionCaptureSize, ReflectionCaptureSize,
|
|
float&, AverageBrightness, *CaptureComponent->GetAverageBrightnessPtr(),
|
|
{
|
|
ComputeAverageBrightness(RHICmdList, FeatureLevel, ReflectionCaptureSize, AverageBrightness);
|
|
FilterReflectionEnvironment(RHICmdList, FeatureLevel, ReflectionCaptureSize, NULL);
|
|
}
|
|
);
|
|
|
|
// Create a proxy to represent the reflection capture to the rendering thread
|
|
// The rendering thread will be responsible for deleting this when done with the filtering operation
|
|
// We can't use the component's SceneProxy here because the component may not be registered with the scene
|
|
FReflectionCaptureProxy* ReflectionProxy = new FReflectionCaptureProxy(CaptureComponent);
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(
|
|
CopyCommand,
|
|
FScene*, Scene, this,
|
|
FReflectionCaptureProxy*, ReflectionProxy, ReflectionProxy,
|
|
ERHIFeatureLevel::Type, FeatureLevel, GetFeatureLevel(),
|
|
{
|
|
if (FeatureLevel == ERHIFeatureLevel::SM5)
|
|
{
|
|
CopyToSceneArray(RHICmdList, Scene, ReflectionProxy);
|
|
}
|
|
else if (FeatureLevel == ERHIFeatureLevel::SM4)
|
|
{
|
|
CopyToComponentTexture(RHICmdList, Scene, ReflectionProxy);
|
|
}
|
|
|
|
// Clean up the proxy now that the rendering thread is done with it
|
|
delete ReflectionProxy;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void CopyToSkyTexture(FRHICommandList& RHICmdList, FScene* Scene, FTexture* ProcessedTexture)
|
|
{
|
|
SCOPED_DRAW_EVENT(RHICmdList, CopyToSkyTexture);
|
|
if (ProcessedTexture->TextureRHI)
|
|
{
|
|
const int32 EffectiveTopMipSize = ProcessedTexture->GetSizeX();
|
|
const int32 NumMips = FMath::CeilLogTwo(EffectiveTopMipSize) + 1;
|
|
FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
|
|
|
|
FSceneRenderTargetItem& FilteredCube = FSceneRenderTargets::Get(RHICmdList).ReflectionColorScratchCubemap[1]->GetRenderTargetItem();
|
|
|
|
// GPU copy back to the skylight's texture, which is not a render target
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; MipIndex++)
|
|
{
|
|
for (int32 CubeFace = 0; CubeFace < CubeFace_MAX; CubeFace++)
|
|
{
|
|
RHICmdList.CopyToResolveTarget(FilteredCube.ShaderResourceTexture, ProcessedTexture->TextureRHI, true, FResolveParams(FResolveRect(), (ECubeFace)CubeFace, MipIndex, 0, 0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Warning: returns before writes to OutIrradianceEnvironmentMap have completed, as they are queued on the rendering thread
|
|
void FScene::UpdateSkyCaptureContents(const USkyLightComponent* CaptureComponent, bool bCaptureEmissiveOnly, UTextureCube* SourceCubemap, FTexture* OutProcessedTexture, float& OutAverageBrightness, FSHVectorRGB3& OutIrradianceEnvironmentMap)
|
|
{
|
|
if (GetFeatureLevel() >= ERHIFeatureLevel::SM4)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateSkyCaptureContents);
|
|
{
|
|
World = GetWorld();
|
|
if (World)
|
|
{
|
|
//guarantee that all render proxies are up to date before kicking off this render
|
|
World->SendAllEndOfFrameUpdates();
|
|
}
|
|
}
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
|
|
ClearCommand,
|
|
int32, CubemapSize, CaptureComponent->CubemapResolution,
|
|
{
|
|
ClearScratchCubemaps(RHICmdList, CubemapSize);
|
|
});
|
|
|
|
if (CaptureComponent->SourceType == SLS_CapturedScene)
|
|
{
|
|
bool bStaticSceneOnly = CaptureComponent->Mobility == EComponentMobility::Static;
|
|
CaptureSceneIntoScratchCubemap(this, CaptureComponent->GetComponentLocation(), CaptureComponent->CubemapResolution, true, bStaticSceneOnly, CaptureComponent->SkyDistanceThreshold, CaptureComponent->bLowerHemisphereIsBlack, bCaptureEmissiveOnly, CaptureComponent->LowerHemisphereColor);
|
|
}
|
|
else if (CaptureComponent->SourceType == SLS_SpecifiedCubemap)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_SIXPARAMETER(
|
|
CopyCubemapCommand,
|
|
UTextureCube*, SourceTexture, SourceCubemap,
|
|
int32, CubemapSize, CaptureComponent->CubemapResolution,
|
|
bool, bLowerHemisphereIsBlack, CaptureComponent->bLowerHemisphereIsBlack,
|
|
float, SourceCubemapRotation, CaptureComponent->SourceCubemapAngle * (PI / 180.f),
|
|
ERHIFeatureLevel::Type, FeatureLevel, GetFeatureLevel(),
|
|
FLinearColor, LowerHemisphereColor, CaptureComponent->LowerHemisphereColor,
|
|
{
|
|
CopyCubemapToScratchCubemap(RHICmdList, FeatureLevel, SourceTexture, CubemapSize, true, bLowerHemisphereIsBlack, SourceCubemapRotation, LowerHemisphereColor);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
check(0);
|
|
}
|
|
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_FOURPARAMETER(
|
|
FilterCommand,
|
|
int32, CubemapSize, CaptureComponent->CubemapResolution,
|
|
float&, AverageBrightness, OutAverageBrightness,
|
|
FSHVectorRGB3*, IrradianceEnvironmentMap, &OutIrradianceEnvironmentMap,
|
|
ERHIFeatureLevel::Type, FeatureLevel, GetFeatureLevel(),
|
|
{
|
|
ComputeAverageBrightness(RHICmdList, FeatureLevel, CubemapSize, AverageBrightness);
|
|
FilterReflectionEnvironment(RHICmdList, FeatureLevel, CubemapSize, IrradianceEnvironmentMap);
|
|
});
|
|
|
|
// Optionally copy the filtered mip chain to the output texture
|
|
if (OutProcessedTexture)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
CopyCommand,
|
|
FScene*, Scene, this,
|
|
FTexture*, ProcessedTexture, OutProcessedTexture,
|
|
{
|
|
CopyToSkyTexture(RHICmdList, Scene, ProcessedTexture);
|
|
});
|
|
}
|
|
}
|
|
}
|