void FIndirectLightingCache::FinalizeUpdateInternal_RenderThread(FScene* Scene, FSceneRenderer& Renderer, TMap<FIntVector, FBlockUpdateInfo>& BlocksToUpdate, const TArray<FIndirectLightingCacheAllocation*>& TransitionsOverTimeToUpdate)
{
	check(IsInRenderingThread());

	if (IndirectLightingAllowed(Scene, Renderer))
	{
		{
			SCOPE_CYCLE_COUNTER(STAT_UpdateIndirectLightingCacheBlocks);
			UpdateBlocks(Scene, Renderer.Views.GetData(), BlocksToUpdate);
		}

		{
			SCOPE_CYCLE_COUNTER(STAT_UpdateIndirectLightingCacheTransitions);
			UpdateTransitionsOverTime(TransitionsOverTimeToUpdate, Renderer.ViewFamily.DeltaWorldTime);
		}
	}	

	if (GCacheDrawLightingSamples || Renderer.ViewFamily.EngineShowFlags.VolumeLightingSamples || GCacheDrawDirectionalShadowing)
	{
		FViewElementPDI DebugPDI(Renderer.Views.GetData(), NULL);

		for (int32 VolumeIndex = 0; VolumeIndex < Scene->PrecomputedLightVolumes.Num(); VolumeIndex++)
		{
			const FPrecomputedLightVolume* PrecomputedLightVolume = Scene->PrecomputedLightVolumes[VolumeIndex];

			PrecomputedLightVolume->DebugDrawSamples(&DebugPDI, GCacheDrawDirectionalShadowing != 0);
		}
	}
}
void FIndirectLightingCache::EncodeBlock(
	FViewInfo* DebugDrawingView,
	const FIndirectLightingCacheBlock& Block, 
	const TArray<float>& AccumulatedWeight, 
	const TArray<FSHVectorRGB2>& AccumulatedIncidentRadiance,
	const TArray<FVector>& AccumulatedSkyBentNormal,
	TArray<FFloat16Color>& Texture0Data,
	TArray<FFloat16Color>& Texture1Data,
	TArray<FFloat16Color>& Texture2Data,
	FSHVectorRGB2& SingleSample,
	FVector& SkyBentNormal)
{
	FViewElementPDI DebugPDI(DebugDrawingView, NULL);

	for (int32 Z = 0; Z < Block.TexelSize; Z++)
	{
		for (int32 Y = 0; Y < Block.TexelSize; Y++)
		{
			for (int32 X = 0; X < Block.TexelSize; X++)
			{
				const int32 LinearIndex = Z * Block.TexelSize * Block.TexelSize + Y * Block.TexelSize + X;

				FSHVectorRGB2 IncidentRadiance = AccumulatedIncidentRadiance[LinearIndex];
				float Weight = AccumulatedWeight[LinearIndex];

				if (Weight > 0)
				{
					IncidentRadiance = IncidentRadiance / Weight;

					if (GCacheReduceSHRinging != 0)
					{
						ReduceSHRinging(IncidentRadiance);
					}
				}

				// Populate single sample from center
				if (X == Block.TexelSize / 2 && Y == Block.TexelSize / 2 && Z == Block.TexelSize / 2)
				{
					SingleSample = IncidentRadiance;
					SkyBentNormal = AccumulatedSkyBentNormal[LinearIndex] / (Weight > 0 ? Weight : 1);
				}

				if (GCacheDrawInterpolationPoints != 0 && DebugDrawingView)
				{
					const FVector WorldPosition = Block.Min + (FVector(X, Y, Z) + .5f) / Block.TexelSize * Block.Size;
					DebugPDI.DrawPoint(WorldPosition, FLinearColor(0, 0, 1), 10, SDPG_World);
				}

				Texture0Data[LinearIndex] = FLinearColor(IncidentRadiance.R.V[0], IncidentRadiance.G.V[0], IncidentRadiance.B.V[0], IncidentRadiance.R.V[3]);
				Texture1Data[LinearIndex] = FLinearColor(IncidentRadiance.R.V[1], IncidentRadiance.G.V[1], IncidentRadiance.B.V[1], IncidentRadiance.G.V[3]);
				Texture2Data[LinearIndex] = FLinearColor(IncidentRadiance.R.V[2], IncidentRadiance.G.V[2], IncidentRadiance.B.V[2], IncidentRadiance.B.V[3]);
			}
		}
	}
}
void FIndirectLightingCache::UpdateBlock(FScene* Scene, FViewInfo* DebugDrawingView, FBlockUpdateInfo& BlockInfo)
{
	const int32 NumSamplesPerBlock = BlockInfo.Block.TexelSize * BlockInfo.Block.TexelSize * BlockInfo.Block.TexelSize;
	FSHVectorRGB3 SingleSample;

	float DirectionalShadowing = 1;
	FVector SkyBentNormal(0, 0, 1);

	//always do point interpolation to get valid 3band single sample and directional data.
	InterpolatePoint(Scene, BlockInfo.Block, DirectionalShadowing, SingleSample, SkyBentNormal);

	if (CanIndirectLightingCacheUseVolumeTexture(GetFeatureLevel()) && !BlockInfo.Allocation->bPointSample)
	{
		static TArray<float> AccumulatedWeight;
		AccumulatedWeight.Reset(NumSamplesPerBlock);
		AccumulatedWeight.AddZeroed(NumSamplesPerBlock);

		//volume textures are encoded as two band, so no reason to waste perf interpolating 3 bands.
		static TArray<FSHVectorRGB2> AccumulatedIncidentRadiance;

		AccumulatedIncidentRadiance.Reset(NumSamplesPerBlock);
		AccumulatedIncidentRadiance.AddZeroed(NumSamplesPerBlock);

		// Interpolate SH samples from precomputed lighting samples and accumulate lighting data for an entire block
		InterpolateBlock(Scene, BlockInfo.Block, AccumulatedWeight, AccumulatedIncidentRadiance);

		static TArray<FFloat16Color> Texture0Data;
		static TArray<FFloat16Color> Texture1Data;
		static TArray<FFloat16Color> Texture2Data;
		Texture0Data.Reset(NumSamplesPerBlock);
		Texture1Data.Reset(NumSamplesPerBlock);
		Texture2Data.Reset(NumSamplesPerBlock);
		Texture0Data.AddUninitialized(NumSamplesPerBlock);
		Texture1Data.AddUninitialized(NumSamplesPerBlock);
		Texture2Data.AddUninitialized(NumSamplesPerBlock);

		const int32 FormatSize = GPixelFormats[PF_FloatRGBA].BlockBytes;
		check(FormatSize == sizeof(FFloat16Color));

		// Encode the SH samples into a texture format
		// Note the single sample is updated even if this is a volume allocation, because translucent materials only use the single sample
		EncodeBlock(DebugDrawingView, BlockInfo.Block, AccumulatedWeight, AccumulatedIncidentRadiance, Texture0Data, Texture1Data, Texture2Data);

		// Setup an update region
		const FUpdateTextureRegion3D UpdateRegion(
			BlockInfo.Block.MinTexel.X,
			BlockInfo.Block.MinTexel.Y,
			BlockInfo.Block.MinTexel.Z,
			0,
			0,
			0,
			BlockInfo.Block.TexelSize,
			BlockInfo.Block.TexelSize,
			BlockInfo.Block.TexelSize);

		// Update the volume texture atlas
		RHIUpdateTexture3D((const FTexture3DRHIRef&)GetTexture0().ShaderResourceTexture, 0, UpdateRegion, BlockInfo.Block.TexelSize * FormatSize, BlockInfo.Block.TexelSize * BlockInfo.Block.TexelSize * FormatSize, (const uint8*)Texture0Data.GetData());
		RHIUpdateTexture3D((const FTexture3DRHIRef&)GetTexture1().ShaderResourceTexture, 0, UpdateRegion, BlockInfo.Block.TexelSize * FormatSize, BlockInfo.Block.TexelSize * BlockInfo.Block.TexelSize * FormatSize, (const uint8*)Texture1Data.GetData());
		RHIUpdateTexture3D((const FTexture3DRHIRef&)GetTexture2().ShaderResourceTexture, 0, UpdateRegion, BlockInfo.Block.TexelSize * FormatSize, BlockInfo.Block.TexelSize * BlockInfo.Block.TexelSize * FormatSize, (const uint8*)Texture2Data.GetData());
	}
	else
	{
		if (GCacheDrawInterpolationPoints != 0 && DebugDrawingView)
		{
			FViewElementPDI DebugPDI(DebugDrawingView, NULL);
			const FVector WorldPosition = BlockInfo.Block.Min;
			DebugPDI.DrawPoint(WorldPosition, FLinearColor(0, 0, 1), 10, SDPG_World);
		}
	}

	// Record the position that the sample was taken at
	BlockInfo.Allocation->TargetPosition = BlockInfo.Block.Min + BlockInfo.Block.Size / 2;

	BlockInfo.Allocation->TargetSamplePacked0[0] = FVector4(SingleSample.R.V[0], SingleSample.R.V[1], SingleSample.R.V[2], SingleSample.R.V[3]) / PI;
	BlockInfo.Allocation->TargetSamplePacked0[1] = FVector4(SingleSample.G.V[0], SingleSample.G.V[1], SingleSample.G.V[2], SingleSample.G.V[3]) / PI;
	BlockInfo.Allocation->TargetSamplePacked0[2] = FVector4(SingleSample.B.V[0], SingleSample.B.V[1], SingleSample.B.V[2], SingleSample.B.V[3]) / PI;
	BlockInfo.Allocation->TargetSamplePacked1[0] = FVector4(SingleSample.R.V[4], SingleSample.R.V[5], SingleSample.R.V[6], SingleSample.R.V[7]) / PI;
	BlockInfo.Allocation->TargetSamplePacked1[1] = FVector4(SingleSample.G.V[4], SingleSample.G.V[5], SingleSample.G.V[6], SingleSample.G.V[7]) / PI;
	BlockInfo.Allocation->TargetSamplePacked1[2] = FVector4(SingleSample.B.V[4], SingleSample.B.V[5], SingleSample.B.V[6], SingleSample.B.V[7]) / PI;
	BlockInfo.Allocation->TargetSamplePacked2 = FVector4(SingleSample.R.V[8], SingleSample.G.V[8], SingleSample.B.V[8], 0) / PI;

	BlockInfo.Allocation->TargetDirectionalShadowing = DirectionalShadowing;

	const float BentNormalLength = SkyBentNormal.Size();
	BlockInfo.Allocation->TargetSkyBentNormal = FVector4(SkyBentNormal / FMath::Max(BentNormalLength, .0001f), BentNormalLength);

	if (!BlockInfo.Allocation->bHasEverUpdatedSingleSample)
	{
		// If this is the first update, also set the interpolated state to match the new target
		//@todo - detect and handle teleports in the same way
		BlockInfo.Allocation->SingleSamplePosition = BlockInfo.Allocation->TargetPosition;

		for (int32 VectorIndex = 0; VectorIndex < 3; VectorIndex++) // RGB
		{
			BlockInfo.Allocation->SingleSamplePacked0[VectorIndex] = BlockInfo.Allocation->TargetSamplePacked0[VectorIndex];
			BlockInfo.Allocation->SingleSamplePacked1[VectorIndex] = BlockInfo.Allocation->TargetSamplePacked1[VectorIndex];
		}
		BlockInfo.Allocation->SingleSamplePacked2 = BlockInfo.Allocation->TargetSamplePacked2;
		BlockInfo.Allocation->CurrentDirectionalShadowing = BlockInfo.Allocation->TargetDirectionalShadowing;
		BlockInfo.Allocation->CurrentSkyBentNormal = BlockInfo.Allocation->TargetSkyBentNormal;

		BlockInfo.Allocation->bHasEverUpdatedSingleSample = true;
	}

	BlockInfo.Block.bHasEverBeenUpdated = true;
}
void FIndirectLightingCache::UpdateCache(FScene* Scene, FSceneRenderer& Renderer, bool bAllowUnbuiltPreview)
{
	if (IsIndirectLightingCacheAllowed())
	{
		bool bAnyViewAllowsIndirectLightingCache = false;

		for (int32 ViewIndex = 0; ViewIndex < Renderer.Views.Num(); ViewIndex++)
		{
			bAnyViewAllowsIndirectLightingCache |= Renderer.Views[ViewIndex].Family->EngineShowFlags.IndirectLightingCache;
		}

		if (bAnyViewAllowsIndirectLightingCache)
		{
			SCOPE_CYCLE_COUNTER(STAT_UpdateIndirectLightingCache);

			TMap<FIntVector, FBlockUpdateInfo> BlocksToUpdate;
			TArray<FIndirectLightingCacheAllocation*> TransitionsOverTimeToUpdate;

			if (bUpdateAllCacheEntries)
			{
				const uint32 PrimitiveCount = Scene->Primitives.Num();

				for (uint32 PrimitiveIndex = 0; PrimitiveIndex < PrimitiveCount; ++PrimitiveIndex)
				{
					FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];

					UpdateCachePrimitive(Scene, PrimitiveSceneInfo, false, true, BlocksToUpdate, TransitionsOverTimeToUpdate);
				}
			}

			// Go over the views and operate on any relevant visible primitives
			for (int32 ViewIndex = 0; ViewIndex < Renderer.Views.Num(); ViewIndex++)
			{
				FViewInfo& View = Renderer.Views[ViewIndex];

				if (!bUpdateAllCacheEntries)
				{
					for (FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap); BitIt; ++BitIt)
					{
						uint32 PrimitiveIndex = BitIt.GetIndex();
						FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];
						const FPrimitiveViewRelevance& PrimitiveRelevance = View.PrimitiveViewRelevanceMap[PrimitiveIndex];

						UpdateCachePrimitive(Scene, PrimitiveSceneInfo, bAllowUnbuiltPreview, PrimitiveRelevance.bOpaqueRelevance, BlocksToUpdate, TransitionsOverTimeToUpdate);
					}
				}

				UpdateTranslucentVolumeCache(View, BlocksToUpdate, TransitionsOverTimeToUpdate);
			}

			UpdateBlocks(Scene, Renderer.Views.GetTypedData(), BlocksToUpdate);

			UpdateTransitionsOverTime(TransitionsOverTimeToUpdate, Renderer.ViewFamily.DeltaWorldTime);

			if (GCacheDrawLightingSamples || Renderer.ViewFamily.EngineShowFlags.VolumeLightingSamples || GCacheDrawDirectionalShadowing)
			{
				FViewElementPDI DebugPDI(Renderer.Views.GetTypedData(), NULL);

				for (int32 VolumeIndex = 0; VolumeIndex < Scene->PrecomputedLightVolumes.Num(); VolumeIndex++)
				{
					const FPrecomputedLightVolume* PrecomputedLightVolume = Scene->PrecomputedLightVolumes[VolumeIndex];

					PrecomputedLightVolume->DebugDrawSamples(&DebugPDI, GCacheDrawDirectionalShadowing != 0);
				}
			}
		}

		bUpdateAllCacheEntries = false;
	}
}