summaryrefslogtreecommitdiff
path: root/Runtime/Camera/RenderLoops
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Camera/RenderLoops')
-rw-r--r--Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp19
-rw-r--r--Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h11
-rw-r--r--Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp1403
-rw-r--r--Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp637
-rw-r--r--Runtime/Camera/RenderLoops/GlobalLayeringData.h26
-rw-r--r--Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp1958
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoop.h25
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopEnums.h29
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp469
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopPrivate.h86
-rw-r--r--Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp245
-rw-r--r--Runtime/Camera/RenderLoops/ReplacementRenderLoop.h11
12 files changed, 4919 insertions, 0 deletions
diff --git a/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp
new file mode 100644
index 0000000..99e5d22
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp
@@ -0,0 +1,19 @@
+#include "UnityPrefix.h"
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+
+static ShaderKeyword gSupportedLODFadeKeyword = keywords::Create("ENABLE_LOD_FADE");
+
+void SetObjectScale (GfxDevice& device, float lodFade, float invScale)
+{
+ device.SetInverseScale(invScale);
+
+ /////@TODO: Figure out why inverse scale is implemented in gfxdevice, and decide if we should do the same for lodFade?
+ device.GetBuiltinParamValues().SetInstanceVectorParam(kShaderInstanceVecScale, Vector4f(0,0,lodFade, invScale));
+
+ if (lodFade == LOD_FADE_DISABLED)
+ g_ShaderKeywords.Disable(gSupportedLODFadeKeyword);
+ else
+ g_ShaderKeywords.Enable(gSupportedLODFadeKeyword);
+}
diff --git a/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h
new file mode 100644
index 0000000..7b480ed
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h
@@ -0,0 +1,11 @@
+#pragma once
+
+
+///@TODO: This should probably be 0. But for now we don't have proper ifdef support for switching to a different subshader.
+#define LOD_FADE_DISABLED 0.999F
+
+#define LOD_FADE_BATCH_EPSILON 0.0625 // 1/16
+
+class GfxDevice;
+
+void SetObjectScale (GfxDevice& device, float lodFade, float invScale);
diff --git a/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp b/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp
new file mode 100644
index 0000000..44e91b8
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp
@@ -0,0 +1,1403 @@
+#include "UnityPrefix.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+
+#include "RenderLoopPrivate.h"
+#include "RenderLoop.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/Renderable.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/Shadows.h"
+#include "Runtime/Camera/LODGroupManager.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Camera/LightManager.h"
+#include "External/MurmurHash/MurmurHash2.h"
+
+
+// Enable/disable hash based forward shader render loop sorting functionality.
+#define ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING 0
+
+PROFILER_INFORMATION(gFwdOpaquePrepare, "RenderForwardOpaque.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueSort, "RenderForwardOpaque.Sort", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueCollectShadows, "RenderForwardOpaque.CollectShadows", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueRender, "RenderForwardOpaque.Render", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaPrepare, "RenderForwardAlpha.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaSort, "RenderForwardAlpha.Sort", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaRender, "RenderForwardAlpha.Render", kProfilerRender)
+
+static SHADERPROP (ShadowMapTexture);
+
+
+static inline bool CompareLights (ForwardLightsBlock const* a, ForwardLightsBlock const* b)
+{
+ if (!a || !b)
+ return false;
+
+ if (a->mainLight != b->mainLight)
+ return false;
+ if (a->vertexLightCount != b->vertexLightCount)
+ return false;
+ if (a->addLightCount != b->addLightCount)
+ return false;
+
+ int totalLightCount = a->vertexLightCount + a->addLightCount;
+ const ActiveLight* const* lightsA = a->GetLights();
+ const ActiveLight* const* lightsB = b->GetLights();
+ for (int i = 0; i < totalLightCount; ++i)
+ if (lightsA[i] != lightsB[i])
+ return false;
+
+ if (memcmp(a->sh, b->sh, sizeof(a->sh)) != 0)
+ return false;
+
+ if (!CompareApproximately(a->lastAddLightBlend, b->lastAddLightBlend))
+ return false;
+ if (!CompareApproximately(a->lastVertexLightBlend, b->lastVertexLightBlend))
+ return false;
+
+ return true;
+}
+
+struct RenderObjectDataCold {
+ float invScale; // 4
+ float lodFade; // 4
+ size_t lightsDataOffset; // 4 into memory block with all light data chunks
+ int subshaderIndex; // 4
+ // 16 bytes
+};
+
+
+namespace ForwardShaderRenderLoop_Enum
+{
+// Render pass data here is 8 bytes each; an index of the render object and "the rest" packed
+// into 4 bytes.
+enum {
+ kPackPassShift = 0,
+ kPackPassMask = 0xFF,
+ kPackTypeShift = 8,
+ kPackTypeMask = 0xFF,
+ kPackFirstPassFlag = (1<<24),
+ kPackMultiPassFlag = (1<<25),
+};
+
+} // namespace ForwardShaderRenderLoop_Enum
+
+struct RenderPassData {
+ int roIndex;
+ // Packed into UInt32: pass number, pass type, first pass flag, multipass flag
+ UInt32 data;
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+ // state hash for optimizing render object sorter
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<RenderPassData> RenderPasses;
+
+
+struct ForwardShaderRenderState
+{
+ int rendererType;
+ int transformType;
+
+ float invScale;
+ float lodFade;
+
+ Material* material;
+ Shader* shader;
+ int subshaderIndex;
+ ShaderPassType passType;
+ int passIndex;
+
+ const ForwardLightsBlock* lights;
+ int receiveShadows;
+
+ int lightmapIndex;
+ Vector4f lightmapST;
+
+ UInt32 customPropsHash;
+
+
+ void Invalidate()
+ {
+ rendererType = -1;
+ transformType = -1;
+ invScale = 0.0f;
+ lodFade = 0.0F;
+ material = 0; shader = 0; subshaderIndex = -1; passType = kShaderPassTypeCount; passIndex = -1;
+ lights = 0;
+ lightmapIndex = -1; lightmapST = Vector4f(0,0,0,0);
+ receiveShadows = -1;
+ customPropsHash = 0;
+ }
+
+ bool operator == (const ForwardShaderRenderState& rhs) const
+ {
+ if (this == &rhs)
+ return true;
+
+ return (
+ rendererType == rhs.rendererType &&
+ transformType == rhs.transformType &&
+ material == rhs.material &&
+ shader == rhs.shader &&
+ CompareLights(lights, rhs.lights) &&
+ subshaderIndex == rhs.subshaderIndex &&
+ passType == rhs.passType &&
+ passIndex == rhs.passIndex &&
+ CompareApproximately(invScale,rhs.invScale) &&
+ CompareApproximately(lodFade,rhs.lodFade, LOD_FADE_BATCH_EPSILON) &&
+ #if ENABLE_SHADOWS
+ receiveShadows == rhs.receiveShadows &&
+ #endif
+ lightmapIndex == rhs.lightmapIndex &&
+ lightmapST == rhs.lightmapST &&
+ customPropsHash == rhs.customPropsHash
+ );
+ }
+
+ bool operator != (const ForwardShaderRenderState& rhs) const
+ {
+ return !(rhs == *this);
+ }
+};
+
+
+struct ForwardShadowMap
+{
+ ForwardShadowMap() : light(NULL), texture(NULL) {}
+ const ActiveLight* light;
+ RenderTexture* texture;
+ Matrix4x4f shadowMatrix;
+ MinMaxAABB receiverBounds;
+};
+typedef dynamic_array<ForwardShadowMap> ForwardShadowMaps;
+
+struct CompactShadowCollectorSortData;
+
+struct ForwardShaderRenderLoop
+{
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+
+ dynamic_array<RenderObjectDataCold> m_RenderObjectsCold;
+ dynamic_array<UInt8> m_RenderObjectsLightData;
+
+ RenderPasses m_PlainRenderPasses;
+ #if ENABLE_SHADOWS
+ ForwardShadowMap m_MainShadowMap;
+ ForwardShadowMaps m_ShadowMaps;
+ // Render object indices of shadow receivers.
+ // This includes both shadow receivers and objects that have shadows off, but
+ // are within shadow distance. They should still participate in screenspace shadow
+ // gathering, otherwise shadows will be visible through them.
+ dynamic_array<int> m_ReceiverObjects;
+ #endif
+
+ BatchRenderer m_BatchRenderer;
+
+ ForwardShaderRenderLoop()
+ : m_RenderObjectsCold (kMemTempAlloc)
+ , m_RenderObjectsLightData (kMemTempAlloc)
+ , m_PlainRenderPasses (kMemTempAlloc)
+ #if ENABLE_SHADOWS
+ , m_ShadowMaps (kMemTempAlloc)
+ , m_ReceiverObjects (kMemTempAlloc)
+ #endif
+ { }
+
+ void PerformRendering (const ActiveLight* mainDirShadowLight, RenderTexture* existingShadowMap, const ShadowCullData& shadowCullData, bool disableDynamicBatching, bool sRGBrenderTarget, bool clearFrameBuffer);
+ #if ENABLE_SHADOWS
+ RenderTexture* CollectShadows (RenderTexture* inputShadowMap, const Light* light, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer);
+ void RenderLightShadowMaps (ForwardShadowMap& shadowMap, ShadowCameraData& camData, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer);
+ int SortShadowCollectorsCompact(CompactShadowCollectorSortData* _resultOrder);
+ #endif
+
+ template <bool opaque>
+ struct RenderObjectSorter
+ {
+ bool operator()( const RenderPassData& ra, const RenderPassData& rb ) const;
+ const ForwardShaderRenderLoop* queue;
+ };
+
+ template <bool opaque>
+ void SortRenderPassData( RenderPasses& passes )
+ {
+ RenderObjectSorter<opaque> sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+template <bool opaque>
+bool ForwardShaderRenderLoop::RenderObjectSorter<opaque>::operator() (const RenderPassData& ra, const RenderPassData& rb) const
+{
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+
+ if (!opaque)
+ {
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+#if DEBUGMODE
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+#endif
+
+ // Sort strictly by distance unless they are equal
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt64 keya = (0x0000ffff-((dataa.queueIndex)&0x0000ffff))<<16;
+ UInt64 keyb = (0x0000ffff-((datab.queueIndex)&0x0000ffff))<<16;
+
+ keya |= (ra.data & kPackFirstPassFlag)>>(24-8);
+ keyb |= (rb.data & kPackFirstPassFlag)>>(24-8);
+ keya |= (0x000000ff-((dataa.lightmapIndex)&0x000000ff));
+ keyb |= (0x000000ff-((datab.lightmapIndex)&0x000000ff));
+ keya = keya << 32;
+ keyb = keyb << 32;
+ keya |= ra.hash;
+ keyb |= rb.hash;
+
+ //Sort keys, TODO try to move the key generation outside the sorting loop
+ if( keya != keyb )
+ return (keya > keyb);
+
+#if DEBUGMODE
+ if (opaque)
+ {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ }
+#endif
+
+ //fall though distance, TODO insert distance into the key
+ return dataa.distance > datab.distance;
+
+#else
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+#if DEBUGMODE
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+#endif
+
+ if (!opaque)
+ {
+ // Sort strictly by distance unless they are equal
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ // sort by lightmap index (fine to do it before source material index
+ // since every part of same mesh will have the same lightmap index)
+ if( dataa.lightmapIndex != datab.lightmapIndex )
+ return dataa.lightmapIndex < datab.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ // prefer static batched first as they usually cover quite a lot
+ if( dataa.staticBatchIndex != datab.staticBatchIndex )
+ return dataa.staticBatchIndex > datab.staticBatchIndex;
+
+ // otherwise sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.staticBatchIndex == 0 && dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#else
+ // Sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#endif
+
+ // sort by shader
+ if( dataa.shader != datab.shader )
+ return dataa.shader->GetInstanceID() < datab.shader->GetInstanceID(); // just compare instance IDs
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ if (opaque)
+ {
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#endif // ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+}
+
+#if ENABLE_SHADOWS
+static void SetLightShadowProps (const Camera& camera, const Light& light, Texture* shadowMap, const Matrix4x4f& shadowMatrix, bool useDualInForward)
+{
+ const float shadowStrength = light.GetShadowStrength();
+ DebugAssert (shadowMap);
+
+ ShaderLab::PropertySheet *props = ShaderLab::g_GlobalProperties;
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+
+ // shadow matrix
+ CopyMatrix (shadowMatrix.GetPtr(), params.GetWritableMatrixParam(kShaderMatWorldToShadow).GetPtr());
+
+ props->SetTexture( kSLPropShadowMapTexture, shadowMap );
+
+ if (light.GetType() == kLightPoint)
+ {
+ const Vector3f lightPos = light.GetWorldPosition();
+ params.SetVectorParam(kShaderVecLightPositionRange, Vector4f(lightPos.x, lightPos.y, lightPos.z, 1.0f/light.GetRange()));
+ }
+
+ // ambient & shadow fade out
+ Vector4f lightFade;
+ Vector4f fadeCenterAndType;
+ CalculateLightShadowFade (camera, shadowStrength, lightFade, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecLightmapFade, lightFade);
+ if (useDualInForward)
+ lightFade.z = lightFade.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightShadowData, lightFade);
+ params.SetVectorParam(kShaderVecShadowFadeCenterAndType, fadeCenterAndType);
+ // texel offsets for PCF
+ Vector4f offsets;
+ float offX = 0.5f / shadowMap->GetGLWidth();
+ float offY = 0.5f / shadowMap->GetGLHeight();
+ offsets.z = 0.0f; offsets.w = 0.0f;
+ offsets.x = -offX; offsets.y = -offY; params.SetVectorParam(kShaderVecShadowOffset0, offsets);
+ offsets.x = offX; offsets.y = -offY; params.SetVectorParam(kShaderVecShadowOffset1, offsets);
+ offsets.x = -offX; offsets.y = offY; params.SetVectorParam(kShaderVecShadowOffset2, offsets);
+ offsets.x = offX; offsets.y = offY; params.SetVectorParam(kShaderVecShadowOffset3, offsets);
+}
+static void SetLightShadowCollectProps (const Camera& camera, const Light& light, Texture* shadowMap, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool useDualInForward)
+{
+ DebugAssert (shadowMatrices && shadowMap);
+ SetLightShadowProps (camera, light, shadowMap, shadowMatrices[0], useDualInForward);
+ SetCascadedShadowShaderParams (shadowMatrices, splitDistances, splitSphereCentersAndSquaredRadii);
+}
+#endif // ENABLE_SHADOWS
+
+
+
+void ForwardShaderRenderLoop::PerformRendering (const ActiveLight* mainDirShadowLight, RenderTexture* existingShadowMap, const ShadowCullData& shadowCullData, bool disableDynamicBatching, bool sRGBrenderTarget, bool clearFrameBuffer)
+{
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const RenderManager::Renderables& renderables = GetRenderManager ().GetRenderables ();
+ RenderManager::Renderables::const_iterator renderablesBegin = renderables.begin(), renderablesEnd = renderables.end();
+
+ SetNoShadowsKeywords();
+
+ GfxDevice& device = GetGfxDevice();
+ // save current scissor params
+ int oldScissorRect[4];
+ device.GetScissorRect(oldScissorRect);
+ const bool oldScissor = device.IsScissorEnabled();
+
+ #if ENABLE_SHADOWS
+ const bool enableSoftShadows = GetSoftShadowsEnabled();
+ ShadowCameraData camData(shadowCullData);
+ ForwardShadowMap mainLightShadowMap;
+ const bool hasAnyShadows = (mainDirShadowLight != 0 || !m_ShadowMaps.empty());
+ const bool useDualInForward = GetLightmapSettings().GetUseDualLightmapsInForward();
+
+ // shadow map of main directional light
+ if (mainDirShadowLight != 0)
+ {
+ // Render shadow map
+ if (!existingShadowMap)
+ {
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ mainLightShadowMap.receiverBounds = m_MainShadowMap.receiverBounds;
+ mainLightShadowMap.receiverBounds.Expand (0.01f);
+ mainLightShadowMap.light = mainDirShadowLight;
+
+ // One directional light can have shadows in free version, so temporarily
+ // enable render textures just for that.
+ RenderTexture::SetTemporarilyAllowIndieRenderTexture (true);
+ RenderLightShadowMaps (mainLightShadowMap, camData, enableSoftShadows, useDualInForward, clearFrameBuffer);
+ RenderTexture::SetTemporarilyAllowIndieRenderTexture (false);
+
+ // There were no shadow casters - no shadowmap is produced
+ if (!mainLightShadowMap.texture)
+ mainDirShadowLight = 0;
+ }
+ else
+ {
+ mainLightShadowMap.texture = existingShadowMap;
+ }
+ }
+
+ // shadow maps of other lights
+ for (ForwardShadowMaps::iterator it = m_ShadowMaps.begin(), itEnd = m_ShadowMaps.end(); it != itEnd; ++it)
+ {
+ ForwardShadowMap& shadowMap = *it;
+
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ shadowMap.receiverBounds.Expand (0.01f);
+
+ RenderLightShadowMaps (shadowMap, camData, enableSoftShadows, false, clearFrameBuffer);
+ }
+
+ if (hasAnyShadows)
+ {
+ m_Context->m_Camera->SetupRender (Camera::kRenderFlagSetRenderTarget);
+ SetNoShadowsKeywords ();
+ }
+ #endif
+
+ const RenderSettings& renderSettings = GetRenderSettings();
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+ size_t npasses = m_PlainRenderPasses.size();
+
+ int currentQueueIndex = m_Context->m_RenderQueueStart;
+
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ ForwardShaderRenderState prevRenderState;
+ prevRenderState.Invalidate();
+
+ //If we are in linear lighting enable sRGB writes here...
+ device.SetSRGBWrite(sRGBrenderTarget);
+ if (clearFrameBuffer)
+ m_Context->m_Camera->ClearNoSkybox(false);
+ else
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ const ChannelAssigns* channels = NULL;
+
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ const RenderPassData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roDataH = (*m_Objects)[rpData.roIndex];
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[rpData.roIndex];
+ const ForwardLightsBlock& roDataL = *reinterpret_cast<ForwardLightsBlock*>(&m_RenderObjectsLightData[roDataC.lightsDataOffset]);
+
+ // We're going over all things that need to be rendered in increasing
+ // render queue order. Whenever we switch to the new queue, we must
+ // invoke all "camera renderables" (halos, flares and so on).
+ const int roQueueIndex = roDataH.queueIndex;
+ DebugAssert (roQueueIndex >= currentQueueIndex);
+ if( roQueueIndex > currentQueueIndex )
+ {
+ m_BatchRenderer.Flush();
+
+ // Draw required renderables
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first <= roQueueIndex )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+ ++renderablesBegin;
+ }
+ }
+
+ currentQueueIndex = roQueueIndex;
+ }
+
+ const VisibleNode *node = roDataH.visibleNode;
+ const UInt16 subsetIndex = roDataH.subsetIndex;
+
+ ForwardShaderRenderState rs;
+ {
+ rs.rendererType = node->renderer->GetRendererType();
+ rs.transformType = node->transformType;
+ rs.invScale = roDataC.invScale;
+ rs.lodFade = roDataC.lodFade;
+
+ rs.material = roDataH.material;
+ rs.shader = roDataH.shader;
+ rs.subshaderIndex = roDataC.subshaderIndex;
+ rs.passType = (ShaderPassType)((rpData.data >> kPackTypeShift) & kPackTypeMask);
+ rs.passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+
+ rs.lights = &roDataL;
+ #if ENABLE_SHADOWS
+ rs.receiveShadows = hasAnyShadows && node->renderer->GetReceiveShadows() && IsObjectWithinShadowRange (*m_Context->m_ShadowCullData, node->worldAABB);
+ #endif
+
+ rs.lightmapIndex = roDataH.lightmapIndex;
+ DebugAssert(rs.lightmapIndex == node->renderer->GetLightmapIndex());
+ rs.lightmapST = node->renderer->GetLightmapSTForRendering();
+ rs.customPropsHash = node->renderer->GetCustomPropertiesHash();
+ }
+
+
+ // multi-pass requires vertex position values to be EXACTLY the same for all passes
+ // therefore do NOT batch dynamic multi-pass nodes
+ // same for shadow casters
+ const bool multiPass = (rpData.data & kPackMultiPassFlag) == kPackMultiPassFlag;
+ const bool dynamicShouldNotBatch = (node->renderer->GetStaticBatchIndex() == 0) && (multiPass || disableDynamicBatching);
+
+ #if ENABLE_SHADOWS
+ const bool dynamicAndShadowCaster = (node->renderer->GetStaticBatchIndex() == 0) && (mainDirShadowLight != 0) && node->renderer->GetCastShadows();
+ #else
+ const bool dynamicAndShadowCaster = false;
+ #endif
+
+ bool shouldResetPass;
+ if (rs.passType == kPassForwardAdd || // rendering multiple different lights in a row - impossible to batch
+ prevRenderState != rs)
+ {
+ // break the batch
+ m_BatchRenderer.Flush();
+ prevRenderState = rs;
+ shouldResetPass = true;
+ }
+ // We can not use dynamic batching for shadow casting renderers or multipass renderers,
+ // because that will lead to zfighting due to slightly different vertex positions
+ else if (dynamicAndShadowCaster || dynamicShouldNotBatch)
+ {
+ m_BatchRenderer.Flush();
+ shouldResetPass = false;
+ }
+ else
+ shouldResetPass = false;
+
+ renderSettings.SetupAmbient();
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ node->renderer->ApplyCustomProperties(*roDataH.material, rs.shader, rs.subshaderIndex);
+
+ // non batchable and generally inefficient multi-pass path
+ if (rs.passType == kPassForwardAdd)
+ {
+ const int lightCount = rs.lights->addLightCount;
+ const ActiveLight* const* addLights = rs.lights->GetLights();
+ for( int lightNo = 0; lightNo < lightCount; ++lightNo )
+ {
+ const ActiveLight& activeLight = *addLights[lightNo];
+ Light* light = activeLight.light;
+ LightManager::SetupForwardAddLight (light, lightNo==lightCount-1 ? rs.lights->lastAddLightBlend : 1.0f);
+
+ if (light->GetType() != kLightDirectional)
+ SetLightScissorRect (activeLight.screenRect, m_Context->m_CameraViewport, false, device);
+
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && light->GetShadows() != kShadowNone)
+ {
+ // find light among additional shadow lights
+ ForwardShadowMaps::iterator sl, slEnd = m_ShadowMaps.end();
+ for (sl = m_ShadowMaps.begin(); sl != slEnd; ++sl)
+ {
+ if (sl->light == &activeLight && sl->texture)
+ {
+ const Light& light = *activeLight.light;
+ SetLightShadowProps (*m_Context->m_Camera, light, sl->texture, sl->shadowMatrix, false);
+ SetShadowsKeywords (light.GetType(), light.GetShadows(), light.GetType()==kLightDirectional, enableSoftShadows);
+ break;
+ }
+ }
+ }
+ #endif
+
+ channels = rs.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, rs.transformType);
+ node->renderer->Render( subsetIndex, *channels );
+ }
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && light->GetShadows() != kShadowNone)
+ {
+ SetNoShadowsKeywords ();
+ }
+ #endif
+
+ if (light->GetType() != kLightDirectional)
+ ClearScissorRect (oldScissor, oldScissorRect, device);
+ }
+ }
+ else
+ {
+ // only setup lights & pass state when they're differ from previous
+ if (shouldResetPass)
+ {
+ // only setup lights & pass state when they're differ from previous
+ switch( rs.passType )
+ {
+ case kPassAlways:
+ {
+ // Disable all fixed function lights for consistency (so if user
+ // has accidentally Lighting On in an Always pass, it will not produce
+ // random results)
+ device.DisableLights (0);
+
+ // Reset SH lighting
+ float blackSH [9][3];
+ memset (blackSH, 0, (9 * 3)* sizeof(float));
+ SetSHConstants (blackSH, GetGfxDevice().GetBuiltinParamValues());
+
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, false);
+ }
+ break;
+
+ case kPassForwardBase:
+ {
+ // NOTE: identity matrix has to be set for GLSL & OpenGLES before vertex lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ LightManager::SetupForwardBaseLights (*rs.lights);
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, false);
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && mainDirShadowLight && rs.lights->mainLight == mainDirShadowLight)
+ {
+ const Light& light = *mainDirShadowLight->light;
+ SetLightShadowProps (*m_Context->m_Camera, light, mainLightShadowMap.texture, mainLightShadowMap.shadowMatrix, false);
+ SetShadowsKeywords (light.GetType(), light.GetShadows(), true, enableSoftShadows);
+ }
+ #endif
+ }
+ break;
+
+ case kPassVertex:
+ case kPassVertexLM:
+ case kPassVertexLMRGBM:
+ {
+ // NOTE: identity matrix has to be set for GLSL & OpenGLES before vertex lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, true);
+ LightManager::SetupVertexLights( rs.lights->vertexLightCount, rs.lights->GetLights() );
+ }
+ break;
+
+ default:
+ {
+ AssertString ("This pass type should not happen");
+ break;
+ }
+ }
+
+ channels = roDataH.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ }
+
+ if (channels)
+ m_BatchRenderer.Add(node->renderer, subsetIndex, channels, node->worldMatrix, rs.transformType);
+
+ if (ENABLE_SHADOWS && rs.passType == kPassForwardBase)
+ SetNoShadowsKeywords ();
+ }
+ }
+
+ m_BatchRenderer.Flush();
+
+ SetNoShadowsKeywords ();
+
+ // restore scissor
+ ClearScissorRect (oldScissor, oldScissorRect, device);
+
+ #if ENABLE_SHADOWS
+ if (mainLightShadowMap.texture && mainLightShadowMap.texture != existingShadowMap)
+ GetRenderBufferManager().ReleaseTempBuffer( mainLightShadowMap.texture );
+ for (ForwardShadowMaps::iterator it = m_ShadowMaps.begin(), itEnd = m_ShadowMaps.end(); it != itEnd; ++it)
+ {
+ ForwardShadowMap& sl = *it;
+ if (sl.texture)
+ GetRenderBufferManager().ReleaseTempBuffer (sl.texture);
+ }
+ #endif
+
+ // After everything we might still have renderables that should be drawn and the
+ // very end. Do it.
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while (renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueStart)
+ ++renderablesBegin;
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueEnd )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+ ++renderablesBegin;
+ }
+ }
+ GetGfxDevice().SetSRGBWrite(false);
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+}
+
+
+// ------------------------------------------------------------------------
+// collect cascaded shadows into screen-space texture; apply blur
+
+#if ENABLE_SHADOWS
+
+struct ShadowCollectorSorter
+{
+ bool operator() (int raIndex, int rbIndex) const;
+ const ForwardShaderRenderLoop* queue;
+};
+
+bool ShadowCollectorSorter::operator()(int raIndex, int rbIndex) const
+{
+ const RenderObjectData& ra = (*queue->m_Objects)[raIndex];
+ const RenderObjectData& rb = (*queue->m_Objects)[rbIndex];
+
+ // Sort by layering depth. //@TODO:should this be here?
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+struct CompactShadowCollectorSortData
+{
+ UInt64 key; // 64b key, stores full 32b material instance ID, 16b internal static batch ID, 2b for transform type, and 14b depth
+ int collectorIndex;
+
+ CompactShadowCollectorSortData(UInt32 _smallMeshIndex, UInt32 _instanceID, TransformType _transformType, float _depth, int _collectorIndex )
+ {
+ key=0;
+ UInt32 transformType = static_cast<UInt32>(_transformType);
+ UInt32 z = (UInt32)(16383.0f*_depth);
+
+ key |= (_instanceID);
+ key = key << 32;
+ key |= ((_smallMeshIndex&0x0000ffff)<<16)|((transformType&0x00000003)<<14)|(z&0x00003fff);
+
+ collectorIndex = _collectorIndex;
+ }
+};
+
+struct CompactShadowCollectorKeySorter
+{
+ inline bool operator()(const CompactShadowCollectorSortData& a, const CompactShadowCollectorSortData& b)
+ {
+ return a.key < b.key;
+ }
+};
+
+// Shadow collector sorting
+// Sorted shadow collector order is stored into m_ReceiverObjects
+// Output:
+// _resultOrder - Sorted shadow caster sort data
+// Returns:
+// Number of active collectors
+int ForwardShaderRenderLoop::SortShadowCollectorsCompact(CompactShadowCollectorSortData* _resultOrder)
+{
+ int activeShadowCollectors = 0;
+
+ // Generate key array for sorting
+ for( int i = 0; i < m_ReceiverObjects.size(); ++i )
+ {
+ int roIndex = m_ReceiverObjects[i];
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ Shader* shader = roDataH.shader;
+
+ if( shader->HasShadowCollectorPass() )
+ {
+ const TransformInfo& xformInfo = roDataH.visibleNode->renderer->GetTransformInfo();
+
+ Matrix4x4f worldToClipMatrix = m_Context->m_Camera->GetWorldToClipMatrix();
+ const Vector3f& worldPos = roDataH.visibleNode->worldAABB.GetCenter();
+ float z = worldToClipMatrix.Get (2, 0) * worldPos.x + worldToClipMatrix.Get (2, 1) * worldPos.y + worldToClipMatrix.Get (2, 2) * worldPos.z + worldToClipMatrix.Get (2, 3);
+ float w = worldToClipMatrix.Get (3, 0) * worldPos.x + worldToClipMatrix.Get (3, 1) * worldPos.y + worldToClipMatrix.Get (3, 2) * worldPos.z + worldToClipMatrix.Get (3, 3);
+ float z_proj = z/w;
+ z_proj = max(z_proj,0.0f);
+ z_proj = min(z_proj,1.0f);
+
+ _resultOrder[activeShadowCollectors++] = CompactShadowCollectorSortData( roDataH.visibleNode->renderer->GetMeshIDSmall(), roDataH.material->GetShadowCollectorHash(),
+ xformInfo.transformType, z_proj, roIndex );
+
+ }
+ }
+
+ std::sort( _resultOrder, _resultOrder + activeShadowCollectors, CompactShadowCollectorKeySorter() );
+
+ return activeShadowCollectors;
+}
+
+RenderTexture* ForwardShaderRenderLoop::CollectShadows (RenderTexture* inputShadowMap, const Light* light, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer)
+{
+ PROFILER_AUTO_GFX(gFwdOpaqueCollectShadows, m_Context->m_Camera)
+ GPU_AUTO_SECTION(kGPUSectionShadowPass);
+
+ DebugAssert (shadowMatrices && inputShadowMap && light && splitDistances);
+
+ //Sort shadow collectors
+#if GFX_ENABLE_SHADOW_BATCHING
+ CompactShadowCollectorSortData* sortOrder;
+ ALLOC_TEMP(sortOrder, CompactShadowCollectorSortData, m_ReceiverObjects.size());
+ int shadowColectors = SortShadowCollectorsCompact(sortOrder);
+#else
+ ShadowCollectorSorter sorter;
+ sorter.queue = this;
+ std::sort (m_ReceiverObjects.begin(), m_ReceiverObjects.end(), sorter);
+#endif
+
+ // If camera is rendering into a texture, we can share its depth buffer while collecting shadows.
+ // This doesn't apply if the target texture is antialiased (case 559079).
+ bool shareDepthBuffer = false;
+ RenderTexture* cameraRT = m_Context->m_Camera->GetCurrentTargetTexture();
+ if (cameraRT && cameraRT->GetDepthFormat() != kDepthFormatNone && !cameraRT->IsAntiAliased())
+ {
+ shareDepthBuffer = true;
+ if (!cameraRT->IsCreated())
+ cameraRT->Create();
+ }
+
+ // create screen-space render texture and collect shadows into it
+ RenderTexture* screenShadowMap = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, shareDepthBuffer ? kDepthFormatNone : kDepthFormat24, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ if (shareDepthBuffer)
+ {
+ if (!screenShadowMap->IsCreated())
+ screenShadowMap->Create();
+ RenderSurfaceHandle rtSurfaceColor = screenShadowMap->GetColorSurfaceHandle();
+ RenderSurfaceHandle rtSurfaceDepth = cameraRT->GetDepthSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, screenShadowMap);
+ }
+ else
+ {
+ RenderTexture::SetActive (screenShadowMap);
+ }
+
+ GfxDevice& device = GetGfxDevice();
+ // (case 555375)
+ // Clear all, expect in cases where depth buffer is shared and forward path is used to render deferred path shadow receiving objects
+ // (clearFrameBuffer variable is false in those cases)
+ bool clearColorOnly = shareDepthBuffer && !clearFrameBuffer;
+ device.Clear (clearColorOnly ? kGfxClearColor : kGfxClearAll, ColorRGBAf(1,1,1,0).GetPtr(), 1.0f, 0);
+ if (clearColorOnly)
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+ GPU_TIMESTAMP();
+ m_Context->m_Camera->SetupRender ();
+
+ SetLightShadowCollectProps (*m_Context->m_Camera, *light, inputShadowMap, shadowMatrices, splitDistances, splitSphereCentersAndSquaredRadii, useDualInForward);
+ light->SetPropsToShaderLab (1.0f);
+
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+#if GFX_ENABLE_SHADOW_BATCHING
+
+ device.SetInverseScale(1.0f);
+ m_BatchRenderer.Flush();
+
+ if (shadowColectors > 0)
+ {
+ UInt64 previousKey = ((sortOrder[0].key)&0xFFFFFFFFFFFFC000ULL); // depth component does not affect state change boundaries
+ UInt32 previousHash = 0;
+ int roIndex = sortOrder[0].collectorIndex;
+ const ChannelAssigns* channels = (*m_Objects)[roIndex].material->SetShadowCollectorPassWithShader ((*m_Objects)[roIndex].shader, m_RenderObjectsCold[roIndex].subshaderIndex);
+
+ for(int i=0; i<shadowColectors;i++)
+ {
+ UInt64 currentKey = ((sortOrder[i].key)&0xFFFFFFFFFFFFC000ULL);
+
+ roIndex = sortOrder[i].collectorIndex;
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ Shader* shader = roDataH.shader;
+ const TransformInfo& xformInfo = roDataH.visibleNode->renderer->GetTransformInfo ();
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[roIndex];
+
+ roDataH.visibleNode->renderer->ApplyCustomProperties(*roDataH.material, shader, roDataC.subshaderIndex);
+
+ UInt32 currentHash = roDataH.visibleNode->renderer->GetCustomPropertiesHash();
+
+ // different property hasah or shared depth buffer cause Flush(), state setup, and one non-batched draw call
+ if (currentHash != previousHash || shareDepthBuffer)
+ {
+ m_BatchRenderer.Flush(); // empty BatchRenderer
+ channels = roDataH.material->SetShadowCollectorPassWithShader(shader, roDataC.subshaderIndex);
+ SetupObjectMatrix(xformInfo.worldMatrix, xformInfo.transformType);
+ roDataH.visibleNode->renderer->Render( roDataH.subsetIndex, *channels );
+ }
+ else
+ {
+ if (previousKey != currentKey) // Flush() and update state when key changes
+ {
+ m_BatchRenderer.Flush();
+ channels = roDataH.material->SetShadowCollectorPassWithShader (shader, roDataC.subshaderIndex);
+ }
+
+ // if this pass needs to be rendered
+ if (channels)
+ m_BatchRenderer.Add(roDataH.visibleNode->renderer, roDataH.subsetIndex, channels, xformInfo.worldMatrix, xformInfo.transformType);
+ }
+ previousKey = currentKey;
+ previousHash = currentHash;
+ }
+ m_BatchRenderer.Flush();
+ }
+
+#else // GFX_ENABLE_SHADOW_BATCHING
+
+ size_t npasses = m_ReceiverObjects.size();
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ int roIndex = m_ReceiverObjects[i];
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[roIndex];
+
+ Shader* shader = roDataH.shader;
+ if( !shader->HasShadowCollectorPass() )
+ continue;
+
+ const VisibleNode* node = roDataH.visibleNode;
+ BaseRenderer* renderer = node->renderer;
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ renderer->ApplyCustomProperties(*roDataH.material, shader, roDataC.subshaderIndex);
+
+ const ChannelAssigns* channels = roDataH.material->SetShadowCollectorPassWithShader(shader, roDataC.subshaderIndex);
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ renderer->Render( roDataH.subsetIndex, *channels );
+ }
+
+#endif // GFX_ENABLE_SHADOW_BATCHING
+
+ GetRenderBufferManager().ReleaseTempBuffer( inputShadowMap );
+
+ //
+ // possibly blur into another screen-space render texture
+
+ if( IsSoftShadow(light->GetShadows()) && enableSoftShadows )
+ {
+ return BlurScreenShadowMap (screenShadowMap, light->GetShadows(), m_Context->m_Camera->GetFar(), light->GetShadowSoftness(), light->GetShadowSoftnessFade());
+ }
+
+ return screenShadowMap;
+}
+#endif // ENABLE_SHADOWS
+
+// ------------------------------------------------------------------------
+// render shadow maps for a single light
+
+
+#if ENABLE_SHADOWS
+
+void ForwardShaderRenderLoop::RenderLightShadowMaps (ForwardShadowMap& shadowMap, ShadowCameraData& camData, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer)
+{
+ // Set correct keywords before rendering casters (caster passes use keywords for shader selection)
+ const Light* light = shadowMap.light->light;
+ SetShadowsKeywords( light->GetType(), light->GetShadows(), false, enableSoftShadows );
+ GetGfxDevice().SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ Matrix4x4f shadowMatrices[kMaxShadowCascades];
+
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization ().GetUseLightmapsForRendering ();
+#else
+ bool useLightmaps = true;
+#endif
+
+ bool excludeLightmapped = !useDualInForward && useLightmaps;
+ shadowMap.texture = RenderShadowMaps (camData, *shadowMap.light, shadowMap.receiverBounds, excludeLightmapped, shadowMatrices);
+ CopyMatrix (shadowMatrices[0].GetPtr(), shadowMap.shadowMatrix.GetPtr());
+
+ // Shadow map can be null if out of memory; or no shadow casters present
+ if (gGraphicsCaps.hasShadowCollectorPass && shadowMap.texture && light->GetType() == kLightDirectional)
+ {
+ SetShadowsKeywords( light->GetType(), light->GetShadows(), false, enableSoftShadows );
+ shadowMap.texture = CollectShadows (shadowMap.texture, light, shadowMatrices, camData.splitDistances, camData.splitSphereCentersAndSquaredRadii, enableSoftShadows, useDualInForward, clearFrameBuffer);
+ }
+ else
+ {
+ // If shadow map could not actually be created (out of VRAM, whatever), set the no shadows
+ // keywords and proceed. So there will be no shadows, but otherwise it will be ok.
+ SetNoShadowsKeywords();
+ }
+}
+
+#endif // ENABLE_SHADOWS
+
+
+// ------------------------------------------------------------------------
+// rendering entry points
+
+ForwardShaderRenderLoop* CreateForwardShaderRenderLoop()
+{
+ return new ForwardShaderRenderLoop();
+}
+
+void DeleteForwardShaderRenderLoop (ForwardShaderRenderLoop* queue)
+{
+ delete queue;
+}
+
+static bool IsPassSuitable (UInt32 currentRenderOptions, UInt32 passRenderOptions, ShaderPassType passType,
+ bool isLightmapped, bool useRGBM, bool useVertexLights, bool hasAddLights)
+{
+ // All options that a pass requires must be on
+ if( (currentRenderOptions & passRenderOptions) != passRenderOptions )
+ return false; // some options are off, skip this pass
+
+ if (useVertexLights)
+ {
+ if (passType != kPassAlways && passType != kPassVertex &&
+ passType != kPassVertexLM && passType != kPassVertexLMRGBM)
+ return false;
+
+ // Use either lightmapped or non-lightmapped pass
+ if ((passType == kPassVertex && isLightmapped) ||
+ ((passType == kPassVertexLM || passType == kPassVertexLMRGBM) && !isLightmapped))
+ return false;
+
+ // Use pass that can properly decode the lightmap
+ if ((passType == kPassVertexLM && useRGBM) ||
+ (passType == kPassVertexLMRGBM && !useRGBM))
+ return false;
+ }
+ else
+ {
+ if (passType != kPassAlways && passType != kPassForwardBase && passType != kPassForwardAdd)
+ return false; // pass does not belong to forward loop
+
+ if (!hasAddLights && passType == kPassForwardAdd)
+ return false; // additive pass but have no additive lights
+ }
+ return true;
+}
+
+// A point or spot light might be completely behind shadow distance,
+// so there's no point in doing shadows on them.
+static bool IsLightBeyondShadowDistance (const Light& light, const Matrix4x4f& cameraMatrix, float shadowDistance)
+{
+ if (light.GetType() == kLightDirectional)
+ return false;
+ const Vector3f lightPos = light.GetComponent(Transform).GetPosition();
+ float distanceToLight = -cameraMatrix.MultiplyPoint3 (lightPos).z;
+ if (distanceToLight - light.GetRange() > shadowDistance)
+ return true;
+ return false;
+}
+
+
+static void PutAdditionalShadowLights (const AABB& bounds, ForwardLightsBlock& lights, const Matrix4x4f& cameraMatrix, float shadowDistance, ForwardShadowMaps& outShadowMaps)
+{
+ const int lightCount = lights.addLightCount;
+ const ActiveLight* const* addLights = lights.GetLights();
+ for (int lightNo = 0; lightNo < lightCount; ++lightNo)
+ {
+ const ActiveLight* light = addLights[lightNo];
+ if (light->light->GetShadows() == kShadowNone)
+ continue;
+
+ // Find this light's shadow data
+ ForwardShadowMaps::iterator sl, slEnd = outShadowMaps.end();
+ ForwardShadowMap* found = NULL;
+ for (sl = outShadowMaps.begin(); sl != slEnd; ++sl)
+ {
+ if (sl->light == light)
+ {
+ found = &(*sl);
+ break;
+ }
+ }
+ if (sl == slEnd)
+ {
+ // Point/Spot light beyond shadow distance: no need to add
+ if (IsLightBeyondShadowDistance (*light->light, cameraMatrix, shadowDistance))
+ continue;
+
+ ForwardShadowMap& shadowMap = outShadowMaps.push_back ();
+ shadowMap.light = light;
+ shadowMap.receiverBounds = bounds;
+ shadowMap.texture = NULL;
+ }
+ else
+ {
+ found->receiverBounds.Encapsulate (bounds);
+ }
+ }
+}
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InsertIntoHashBuffer(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+}
+#endif
+
+void DoForwardShaderRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ bool opaque,
+ bool disableDynamicBatching,
+ RenderTexture* mainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool clearFrameBuffer)
+{
+ GPU_AUTO_SECTION(opaque ? kGPUSectionOpaquePass : kGPUSectionTransparentPass);
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+
+ // figure out hardware supports shadows
+ #if ENABLE_SHADOWS
+ float shadowDistance = QualitySettings::GetShadowDistanceForRendering();
+ const bool receiveShadows =
+ opaque &&
+ GetBuildSettings().hasShadows &&
+ CheckPlatformSupportsShadows() &&
+ (quality.shadows != QualitySettings::kShadowsDisable) &&
+ (shadowDistance > 0.0f);
+ const bool localLightShadows = receiveShadows && GetBuildSettings().hasLocalLightShadows;
+ #endif
+
+ bool useRGBM = gGraphicsCaps.SupportsRGBM();
+
+ // Allocated on the stack each time, uses temp allocators
+ ForwardShaderRenderLoop queue;
+ queue.m_Context = &ctx;
+ queue.m_Objects = &objects;
+ queue.m_RenderObjectsCold.reserve(objects.size());
+ const int kEstimatedLightDataPerObject = sizeof(ForwardLightsBlock) + kEstimatedLightsPerObject * sizeof(Light*);
+ queue.m_RenderObjectsLightData.reserve(objects.size() * kEstimatedLightDataPerObject);
+
+ const ActiveLight* mainDirShadowLight = NULL;
+
+ // figure out current rendering options
+ UInt32 currentRenderOptions = GetCurrentRenderOptions ();
+
+ RenderSettings& renderSettings = GetRenderSettings();
+ LightManager& lightManager = GetLightManager();
+ const int pixelLightCount = quality.pixelLightCount;
+ const bool dualLightmapsMode = (GetLightmapSettings().GetLightmapsMode() == LightmapSettings::kDualLightmapsMode);
+
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization ().GetUseLightmapsForRendering ();
+#endif
+
+ const CullResults& cullResults = *ctx.m_CullResults;
+
+ // Figure everything out
+ {
+ PROFILER_AUTO((opaque?gFwdOpaquePrepare:gFwdAlphaPrepare), ctx.m_Camera);
+
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+ const VisibleNode *node = odata.visibleNode;
+ size_t visibleNodeIndex = node - cullResults.nodes.begin();
+
+ BaseRenderer* renderer = node->renderer;
+
+#if UNITY_EDITOR
+ const bool isLightmapped = renderer->IsLightmappedForRendering() && useLightmaps;
+#else
+ const bool isLightmapped = renderer->IsLightmappedForRendering();
+#endif
+
+ ShaderLab::IntShader& slshader = *odata.shader->GetShaderLabShader();
+ RenderObjectDataCold& roDataC = queue.m_RenderObjectsCold.push_back();
+ bool useVertexLights = false;
+ if (odata.subShaderIndex == -1)
+ {
+ int ss = slshader.GetDefaultSubshaderIndex (kRenderPathExtForward);
+ if (ss == -1)
+ {
+ ss = slshader.GetDefaultSubshaderIndex (isLightmapped ? kRenderPathExtVertexLM : kRenderPathExtVertex);
+ useVertexLights = true;
+ }
+ if (ss == -1)
+ continue;
+ roDataC.subshaderIndex = ss;
+ }
+ else
+ {
+ roDataC.subshaderIndex = odata.subShaderIndex;
+ }
+ ShaderLab::SubShader& subshader = slshader.GetSubShader(roDataC.subshaderIndex);
+
+ bool disableAddLights = false;
+ if (!useVertexLights)
+ {
+ // If we only have ForwardBase pass and no ForwardAdd,
+ // disable additive lights completely. Only support main directional,
+ // vertex & SH.
+ disableAddLights = !subshader.GetSupportsForwardAddLights();
+ }
+
+ size_t objectLightsOffset = queue.m_RenderObjectsLightData.size();
+ roDataC.lightsDataOffset = objectLightsOffset;
+
+ lightManager.FindForwardLightsForObject (
+ queue.m_RenderObjectsLightData,
+ GetObjectLightIndices(cullResults, visibleNodeIndex),
+ GetObjectLightCount(cullResults, visibleNodeIndex),
+ activeLights,
+ *node,
+ isLightmapped,
+ dualLightmapsMode,
+ useVertexLights,
+ pixelLightCount,
+ disableAddLights,
+ renderSettings.GetAmbientLightInActiveColorSpace());
+
+ ForwardLightsBlock& roDataL = *reinterpret_cast<ForwardLightsBlock*>(&queue.m_RenderObjectsLightData[objectLightsOffset]);
+ const bool hasAddLights = (roDataL.addLightCount != 0);
+
+ #if ENABLE_SHADOWS
+ bool objectReceivesShadows = renderer->GetReceiveShadows();
+ bool withinShadowDistance = IsObjectWithinShadowRange (*ctx.m_ShadowCullData, node->worldAABB);
+ if (receiveShadows && withinShadowDistance)
+ {
+ queue.m_ReceiverObjects.push_back (roIndex);
+
+ if (objectReceivesShadows)
+ {
+ // deal with main directional shadow light
+ if (roDataL.mainLight && roDataL.mainLight->light->GetShadows() != kShadowNone)
+ {
+ if (!mainDirShadowLight)
+ mainDirShadowLight = roDataL.mainLight;
+ if (mainDirShadowLight == roDataL.mainLight)
+ queue.m_MainShadowMap.receiverBounds.Encapsulate (node->worldAABB);
+ }
+
+ // deal with additive shadow lights if needed
+ if (localLightShadows && subshader.GetSupportsFullForwardShadows())
+ {
+ PutAdditionalShadowLights (node->worldAABB, roDataL, ctx.m_CurCameraMatrix, shadowDistance, queue.m_ShadowMaps);
+ }
+ }
+ }
+ #endif
+
+ roDataC.invScale = node->invScale;
+ roDataC.lodFade = node->lodFade;
+
+ int shaderPassCount = subshader.GetValidPassCount();
+
+ // Determine if we will need more than a single pass
+ int suitablePasses = 0;
+ for( int pass = 0; pass < shaderPassCount && suitablePasses < 2; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM, useVertexLights, hasAddLights))
+ ++suitablePasses;
+ }
+
+ // Go over all passes in the shader
+ UInt32 firstPassFlag = kPackFirstPassFlag;
+ const UInt32 multiPassFlag = (suitablePasses > 1)? kPackMultiPassFlag: 0;
+ for( int pass = 0; pass < shaderPassCount; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (!IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM, useVertexLights, hasAddLights))
+ continue; // skip this pass
+
+ RenderPassData rpData;
+ rpData.roIndex = roIndex;
+ rpData.data =
+ ((pass & kPackPassMask) << kPackPassShift) |
+ (passType << kPackTypeShift) |
+ firstPassFlag |
+ multiPassFlag;
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ if (opaque)
+ hashPtr = InsertIntoHashBuffer(&node->invScale, hashPtr);
+ int materialID = odata.material->GetInstanceID();
+ hashPtr = InsertIntoHashBuffer(&materialID, hashPtr);
+ hashPtr = InsertIntoHashBuffer(&roDataC.subshaderIndex, hashPtr);
+ UInt32 shaderPassType = (ShaderPassType)((rpData.data >> kPackTypeShift) & kPackTypeMask);
+ hashPtr = InsertIntoHashBuffer(&shaderPassType, hashPtr);
+ UInt32 passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+ hashPtr = InsertIntoHashBuffer(&passIndex, hashPtr);
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InsertIntoHashBuffer(&odata.staticBatchIndex, hashPtr);
+#endif
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ queue.m_PlainRenderPasses.push_back( rpData );
+
+ firstPassFlag = 0;
+ }
+ }
+ }
+
+ // sort everything
+ {
+ PROFILER_AUTO((opaque?gFwdOpaqueSort:gFwdAlphaSort), ctx.m_Camera);
+ if (opaque)
+ queue.SortRenderPassData<true> (queue.m_PlainRenderPasses);
+ else
+ queue.SortRenderPassData<false> (queue.m_PlainRenderPasses);
+ }
+
+ // Render everything. When transitioning to render queues,
+ // it will invoke camera renderables (halos, and so on)
+ {
+ PROFILER_AUTO_GFX((opaque?gFwdOpaqueRender:gFwdAlphaRender), ctx.m_Camera);
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ queue.PerformRendering (mainDirShadowLight, mainShadowMap, *ctx.m_ShadowCullData, disableDynamicBatching, linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()), clearFrameBuffer);
+ }
+}
diff --git a/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp b/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp
new file mode 100644
index 0000000..60889ad
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp
@@ -0,0 +1,637 @@
+#include "UnityPrefix.h"
+#include "RenderLoopPrivate.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Camera/Renderable.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "RenderLoop.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Camera/LightManager.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapVisualization.h"
+#endif
+#include "BuiltinShaderParamUtility.h"
+#include "External/MurmurHash/MurmurHash2.h"
+
+// Enable/disable hash based forward shader render loop sorting functionality.
+#define ENABLE_VERTEX_LOOP_HASH_SORTING 0
+
+static inline bool CompareLights (VertexLightsBlock const* a, VertexLightsBlock const* b)
+{
+ if (!a || !b)
+ return false;
+
+ if (a->lightCount != b->lightCount)
+ return false;
+
+ const ActiveLight* const* lightsA = a->GetLights();
+ const ActiveLight* const* lightsB = b->GetLights();
+ for (int i = 0; i < a->lightCount; ++i)
+ if (lightsA[i] != lightsB[i])
+ return false;
+
+ return true;
+}
+
+
+struct RODataVLit {
+ // help the compiler here a bit...
+ RODataVLit() { }
+ RODataVLit( const RODataVLit& rhs ) { memcpy(this, &rhs, sizeof(*this)); }
+
+ float invScale; // 4
+ float lodFade; // 4
+ size_t lightsDataOffset; // 4 into memory block with all light data chunks
+ int subshaderIndex; // 4
+
+ // 16 bytes
+};
+
+namespace ForwardVertexRenderLoop_Enum
+{
+// Render pass data here is 8 bytes each; an index of the render object and "the rest" packed
+// into 4 bytes.
+enum {
+ kPackPassShift = 0,
+ kPackPassMask = 0xFF,
+ kPackFirstPassFlag = (1<<16),
+ kPackMultiPassFlag = (1<<17),
+};
+} // namespace ForwardVertexRenderLoop_Enum
+
+struct RPDataVLit {
+ int roIndex;
+ // Packed into UInt32: pass number, first pass flag
+ UInt32 data;
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<RPDataVLit> RenderPassesVLit;
+
+
+struct ForwardVertexRenderState
+{
+ int rendererType;
+ int transformType;
+ float invScale;
+ float lodFade;
+
+ Material* material;
+ Shader* shader;
+ int subshaderIndex;
+ int passIndex;
+
+ const VertexLightsBlock* lights;
+
+ int lightmapIndex;
+ Vector4f lightmapST;
+
+ UInt32 customPropsHash;
+
+ void Invalidate()
+ {
+ rendererType = -1;
+ transformType = -1;
+ invScale = 0.0f;
+ lodFade = 0.0f;
+ material = 0; shader = 0; subshaderIndex = -1; passIndex = -1;
+ lights = 0;
+ lightmapIndex = -1; lightmapST = Vector4f(0,0,0,0);
+ customPropsHash = 0;
+ }
+
+ bool operator == (const ForwardVertexRenderState& rhs) const
+ {
+ if (this == &rhs)
+ return true;
+
+ return (
+ rendererType == rhs.rendererType &&
+ transformType == rhs.transformType &&
+ material == rhs.material &&
+ shader == rhs.shader &&
+ CompareLights(lights, rhs.lights) &&
+ subshaderIndex == rhs.subshaderIndex &&
+ passIndex == rhs.passIndex &&
+ CompareApproximately(invScale,rhs.invScale) &&
+ CompareApproximately(lodFade,rhs.lodFade, LOD_FADE_BATCH_EPSILON) &&
+ lightmapIndex == rhs.lightmapIndex &&
+ CompareMemory(lightmapST, rhs.lightmapST) &&
+ customPropsHash == rhs.customPropsHash);
+ }
+
+ bool operator != (const ForwardVertexRenderState& rhs) const
+ {
+ return !(rhs == *this);
+ }
+};
+
+
+struct ForwardVertexRenderLoop
+{
+ ForwardVertexRenderLoop()
+ : m_RenderObjectsCold (kMemTempAlloc)
+ , m_RenderObjectsLightData (kMemTempAlloc)
+ , m_PlainRenderPasses (kMemTempAlloc)
+ { }
+
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+ dynamic_array<RODataVLit> m_RenderObjectsCold;
+ dynamic_array<UInt8> m_RenderObjectsLightData;
+ RenderPassesVLit m_PlainRenderPasses;
+ BatchRenderer m_BatchRenderer;
+
+ void PerformRendering (bool sSRGBRenderTarget, bool clearFrameBuffer);
+
+ template<bool opaque>
+ struct RenderObjectSorter
+ {
+ bool operator()( const RPDataVLit& ra, const RPDataVLit& rb ) const;
+ const ForwardVertexRenderLoop* queue;
+ };
+ template<bool opaque>
+ void SortRenderPassData( RenderPassesVLit& passes )
+ {
+ RenderObjectSorter<opaque> sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+template<bool opaque>
+bool ForwardVertexRenderLoop::RenderObjectSorter<opaque>::operator() (const RPDataVLit& ra, const RPDataVLit& rb) const
+{
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+
+ if (!opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ if (ra.hash != rb.hash)
+ return ra.hash < rb.hash;
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if (opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#else
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+
+ if (!opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ // sort by lightmap index (fine to do it before source material index
+ // since every part of same mesh will have the same lightmap index)
+ if( dataa.lightmapIndex != datab.lightmapIndex )
+ return dataa.lightmapIndex < datab.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ if( dataa.staticBatchIndex != datab.staticBatchIndex )
+ return dataa.staticBatchIndex < datab.staticBatchIndex;
+
+ // otherwise sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.staticBatchIndex == 0 && dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#else
+ // Sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#endif
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if (opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#endif
+}
+
+void ForwardVertexRenderLoop::PerformRendering (bool sSRGBRenderTarget, bool clearFrameBuffer)
+{
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ GfxDevice& device = GetGfxDevice();
+ const RenderSettings& renderSettings = GetRenderSettings();
+
+ const RenderManager::Renderables& renderables = GetRenderManager ().GetRenderables ();
+ RenderManager::Renderables::const_iterator renderablesBegin = renderables.begin(), renderablesEnd = renderables.end();
+
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+
+ size_t npasses = m_PlainRenderPasses.size();
+
+ int currentQueueIndex = m_Context->m_RenderQueueStart;
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ ForwardVertexRenderState prevRenderState;
+ prevRenderState.Invalidate();
+
+ // SRGB read/write for vertexRenderLoop
+ device.SetSRGBWrite(sSRGBRenderTarget);
+ if (clearFrameBuffer)
+ m_Context->m_Camera->ClearNoSkybox(false);
+
+ const ChannelAssigns* channels = NULL;
+ int canBatch = 0;
+ StartRenderLoop();
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ const RPDataVLit& rpData = m_PlainRenderPasses[i];
+ DebugAssertIf (rpData.roIndex < 0 || rpData.roIndex >= m_Objects->size() || rpData.roIndex >= m_RenderObjectsCold.size());
+ const RenderObjectData& roDataH = (*m_Objects)[rpData.roIndex];
+ const RODataVLit& roDataC = m_RenderObjectsCold[rpData.roIndex];
+
+ const VertexLightsBlock& roDataL = *reinterpret_cast<VertexLightsBlock*>(&m_RenderObjectsLightData[roDataC.lightsDataOffset]);
+
+ const int roQueueIndex = roDataH.queueIndex;
+ DebugAssertIf( roQueueIndex < currentQueueIndex );
+ if( roQueueIndex > currentQueueIndex )
+ {
+ m_BatchRenderer.Flush();
+ canBatch = 0;
+ EndRenderLoop();
+ // Draw required renderables
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first <= roQueueIndex )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+
+ ++renderablesBegin;
+ }
+ }
+
+ currentQueueIndex = roQueueIndex;
+ StartRenderLoop();
+ }
+
+ const VisibleNode* node = roDataH.visibleNode;
+ const UInt16 subsetIndex = roDataH.subsetIndex;
+
+ ForwardVertexRenderState rs;
+ {
+ rs.rendererType = node->renderer->GetRendererType();
+ rs.transformType = node->transformType;
+ rs.invScale = roDataC.invScale;
+ rs.lodFade = roDataC.lodFade;
+
+ rs.material = roDataH.material;
+ rs.shader = roDataH.shader;
+ rs.subshaderIndex = roDataC.subshaderIndex;
+ rs.passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+
+ rs.lights = &roDataL;
+
+ rs.lightmapIndex = roDataH.lightmapIndex;
+ DebugAssert(rs.lightmapIndex == node->renderer->GetLightmapIndex());
+ rs.lightmapST = node->renderer->GetLightmapSTForRendering();
+ rs.customPropsHash = node->renderer->GetCustomPropertiesHash();
+ }
+
+ // multi-pass requires vertex position values to be EXACTLY the same for all passes
+ // therefore do NOT batch dynamic multi-pass nodes
+ const bool multiPass = (rpData.data & kPackMultiPassFlag) == kPackMultiPassFlag;
+ const bool dynamicAndMultiPass = (node->renderer->GetStaticBatchIndex() == 0) && multiPass;
+
+ if (dynamicAndMultiPass ||
+ prevRenderState != rs)
+ {
+ m_BatchRenderer.Flush();
+ prevRenderState = rs;
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ // NOTE: identity matrix has to be set on OpenGLES before lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ renderSettings.SetupAmbient ();
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ node->renderer->ApplyCustomProperties(*rs.material, rs.shader, rs.subshaderIndex);
+
+ // only setup lights & pass when not batching
+ if (canBatch < 1)
+ {
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, true);
+
+ LightManager::SetupVertexLights(rs.lights->lightCount, rs.lights->GetLights());
+ channels = rs.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ }
+ if (channels)
+ {
+ m_BatchRenderer.Add(node->renderer, subsetIndex, channels, node->worldMatrix, rs.transformType);
+ }
+ }
+
+ m_BatchRenderer.Flush();
+ EndRenderLoop();
+ device.SetSRGBWrite(false);
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+
+ // After everything we might still have renderables that should be drawn at the
+ // very end. Do it.
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while (renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueStart)
+ ++renderablesBegin;
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueEnd )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+
+ ++renderablesBegin;
+ }
+ }
+}
+
+
+ForwardVertexRenderLoop* CreateForwardVertexRenderLoop()
+{
+ return new ForwardVertexRenderLoop();
+}
+
+void DeleteForwardVertexRenderLoop (ForwardVertexRenderLoop* queue)
+{
+ delete queue;
+}
+
+
+static bool IsPassSuitable (UInt32 currentRenderOptions, UInt32 passRenderOptions, ShaderPassType passType,
+ bool isLightmapped, bool useRGBM)
+{
+ // All options that a pass requires must be on
+ if( (currentRenderOptions & passRenderOptions) != passRenderOptions )
+ return false; // some options are off, skip this pass
+
+ if (passType != kPassAlways && passType != kPassVertex &&
+ passType != kPassVertexLM && passType != kPassVertexLMRGBM)
+ return false; // unsuitable pass type
+
+ // Use either lightmapped or non-lightmapped pass
+ if ((passType == kPassVertex && isLightmapped) ||
+ ((passType == kPassVertexLM || passType == kPassVertexLMRGBM) && !isLightmapped))
+ return false;
+
+ // Use pass that can properly decode the lightmap
+ if ((passType == kPassVertexLM && useRGBM) ||
+ (passType == kPassVertexLMRGBM && !useRGBM))
+ return false;
+
+ return true;
+}
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InsertIntoHashBufferVtx(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+}
+#endif
+
+void DoForwardVertexRenderLoop (RenderLoopContext& ctx, RenderObjectDataContainer& objects, bool opaque, ActiveLights& activeLights, bool linearLighting, bool clearFrameBuffer)
+{
+ GPU_AUTO_SECTION(opaque ? kGPUSectionOpaquePass : kGPUSectionTransparentPass);
+
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ // Allocated on the stack each time, uses temp allocators
+ ForwardVertexRenderLoop queue;
+ queue.m_Context = &ctx;
+ queue.m_Objects = &objects;
+ queue.m_RenderObjectsCold.reserve(objects.size());
+ queue.m_PlainRenderPasses.reserve(objects.size());
+ const int kEstimatedLightDataPerObject = sizeof(VertexLightsBlock) + kEstimatedLightsPerObject * sizeof(Light*);
+ queue.m_RenderObjectsLightData.reserve(objects.size() * kEstimatedLightDataPerObject);
+
+ const CullResults& cullResults = *ctx.m_CullResults;
+
+ // figure out current rendering options
+ UInt32 currentRenderOptions = GetCurrentRenderOptions ();
+
+ //RenderSettings& renderSettings = GetRenderSettings();
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization().GetUseLightmapsForRendering();
+#endif
+
+ bool useRGBM = gGraphicsCaps.SupportsRGBM();
+
+ // Figure everything out
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+
+ const VisibleNode* node = odata.visibleNode;
+ RODataVLit& roDataC = queue.m_RenderObjectsCold.push_back();
+ size_t visibleNodeIndex = node - cullResults.nodes.begin();
+
+ LightmapSettings::TextureTriple lmTextures = lightmapper.GetLightmapTexture (node->renderer->GetLightmapIndex());
+#if UNITY_EDITOR
+ bool isLightmapped = useLightmaps && lmTextures.first.m_ID;
+#else
+ bool isLightmapped = lmTextures.first.m_ID;
+#endif
+ ShaderLab::IntShader& slshader = *odata.shader->GetShaderLabShader();
+ int vlitSS = odata.subShaderIndex;
+ if (vlitSS == -1)
+ {
+ vlitSS = slshader.GetDefaultSubshaderIndex (isLightmapped ? kRenderPathExtVertexLM : kRenderPathExtVertex);
+ if (vlitSS == -1)
+ continue;
+ }
+ roDataC.subshaderIndex = vlitSS;
+
+ size_t objectLightsOffset = queue.m_RenderObjectsLightData.size();
+ roDataC.lightsDataOffset = objectLightsOffset;
+
+ GetLightManager().FindVertexLightsForObject (
+ queue.m_RenderObjectsLightData,
+ GetObjectLightIndices(cullResults, visibleNodeIndex),
+ GetObjectLightCount(cullResults, visibleNodeIndex),
+ activeLights, *node);
+
+ roDataC.invScale = node->invScale;
+ roDataC.lodFade = node->lodFade;
+
+ // Go over all passes in the shader and add suitable ones for rendering
+ ShaderLab::SubShader& subshader = slshader.GetSubShader(roDataC.subshaderIndex);
+ int shaderPassCount = subshader.GetValidPassCount();
+
+ // Determine if we will need more than a single pass
+ int suitablePasses = 0;
+ for( int pass = 0; pass < shaderPassCount && suitablePasses < 2; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM))
+ ++suitablePasses;
+ }
+
+ // Go over all passes in the shader
+ UInt32 firstPassFlag = kPackFirstPassFlag;
+ const UInt32 multiPassFlag = (suitablePasses > 1)? kPackMultiPassFlag: 0;
+ for (int pass = 0; pass < shaderPassCount; ++pass)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (!IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM))
+ continue;
+
+ RPDataVLit& rpData = queue.m_PlainRenderPasses.push_back();
+ rpData.roIndex = roIndex;
+ rpData.data =
+ ((pass & kPackPassMask) << kPackPassShift) |
+ firstPassFlag |
+ multiPassFlag;
+ firstPassFlag = 0;
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ UInt32 rendererType = static_cast<UInt32>(node->renderer->GetRendererType());
+ hashPtr = InsertIntoHashBufferVtx(&rendererType, hashPtr);
+ UInt32 lightmapIndex = odata.lightmapIndex;
+ hashPtr = InsertIntoHashBufferVtx(&lightmapIndex, hashPtr);
+ UInt32 sourceMaterialIndex = 0;
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InsertIntoHashBufferVtx(&odata.staticBatchIndex, hashPtr);
+ if (odata.staticBatchIndex == 0)
+ sourceMaterialIndex = odata.sourceMaterialIndex;
+#else
+ sourceMaterialIndex = odata.sourceMaterialIndex;
+#endif
+ hashPtr = InsertIntoHashBufferVtx(&sourceMaterialIndex, hashPtr);
+
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ }
+ }
+
+ // sort everything
+ if (opaque)
+ queue.SortRenderPassData<true> (queue.m_PlainRenderPasses);
+ else
+ queue.SortRenderPassData<false> (queue.m_PlainRenderPasses);
+
+ // Render everything. When transitioning to render queues,
+ // it will invoke camera renderables (halos, and so on).
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ queue.PerformRendering (linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()), clearFrameBuffer);
+}
diff --git a/Runtime/Camera/RenderLoops/GlobalLayeringData.h b/Runtime/Camera/RenderLoops/GlobalLayeringData.h
new file mode 100644
index 0000000..176edde
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/GlobalLayeringData.h
@@ -0,0 +1,26 @@
+#pragma once
+
+struct GlobalLayeringData
+{
+ // Per-renderer sorting data.
+ SInt16 layer; // Layer order.
+ SInt16 order; // In-layer order.
+};
+
+inline GlobalLayeringData GlobalLayeringDataCleared () { GlobalLayeringData data = {0,0}; return data; }
+
+inline bool CompareGlobalLayeringData(const GlobalLayeringData& lhs, const GlobalLayeringData& rhs, bool& result)
+{
+ if (lhs.layer != rhs.layer)
+ {
+ result = lhs.layer < rhs.layer;
+ return true;
+ }
+ else if (lhs.order != rhs.order)
+ {
+ result = lhs.order < rhs.order;
+ return true;
+ }
+
+ return false;
+}
diff --git a/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp b/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp
new file mode 100644
index 0000000..8aa3ab6
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp
@@ -0,0 +1,1958 @@
+#include "UnityPrefix.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+
+#if GFX_SUPPORTS_RENDERLOOP_PREPASS
+#include "RenderLoopPrivate.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/ImageFilters.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Camera/Shadows.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Graphics/GeneratedTextures.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "ReplacementRenderLoop.h"
+#if UNITY_EDITOR
+#include "Runtime/BaseClasses/Tags.h"
+#endif
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Math/SphericalHarmonics.h"
+#include "Runtime/Camera/LightManager.h"
+#include "External/MurmurHash/MurmurHash2.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Graphics/DrawUtil.h"
+
+// Enable/disable hash based pre pass render loop sorting functionality.
+#define ENABLE_PRE_PASS_LOOP_HASH_SORTING 0
+
+#define SEPERATE_PREPASS_SPECULAR UNITY_XENON
+
+PROFILER_INFORMATION(gPrepassSort, "RenderPrePass.Sort", kProfilerRender)
+PROFILER_INFORMATION(gPrepassGeom, "RenderPrePass.GeometryPass", kProfilerRender)
+PROFILER_INFORMATION(gPrepassLighting, "RenderPrePass.Lighting", kProfilerRender)
+PROFILER_INFORMATION(gPrepassLight, "RenderPrePass.Light", kProfilerRender)
+PROFILER_INFORMATION(gPrepassFinal, "RenderPrePass.FinalPass", kProfilerRender)
+PROFILER_INFORMATION(gPrepassFwdDepth, "RenderPrePass.ForwardObjectsToDepth", kProfilerRender)
+PROFILER_INFORMATION(gPrepassCombineDepthNormals, "RenderPrePass.CombineDepthNormals", kProfilerRender)
+
+
+static SHADERPROP (LightPos);
+static SHADERPROP (LightDir);
+static SHADERPROP (LightColor);
+static SHADERPROP (LightTexture0);
+static SHADERPROP (LightBuffer);
+static SHADERPROP (LightAsQuad);
+
+// ShadowMapTexture must be in namespace or otherwise it conflicts with property in
+// ForwardShaderRenderLoop.cpp in batched Android build.
+namespace PrePassPrivate
+{
+static SHADERPROP (ShadowMapTexture);
+}
+
+#if SEPERATE_PREPASS_SPECULAR
+static SHADERPROP (LightSpecBuffer);
+#endif
+
+static Material* s_LightMaterial = NULL;
+static Material* s_CollectMaterial = NULL;
+
+static ShaderKeyword kKeywordHDRLightPrepassOn = keywords::Create ("HDR_LIGHT_PREPASS_ON");
+
+static PPtr<Mesh> s_Icosahedron = NULL;
+static PPtr<Mesh> s_Icosphere = NULL;
+static PPtr<Mesh> s_Pyramid = NULL;
+
+
+enum {
+ kLightingLayerCount = 4, // bits of stencil used for lighting layers
+
+ // 3 highest bits used for excluding lights for other reasons.
+ kStencilMaskSomething = (1<<7), // any object (i.e. not background)
+ kStencilMaskNonLightmapped = (1<<6), // non-lightmapped object
+ kStencilMaskBeyondShadowDistace = (1<<5), // beyond shadow distance
+ kStencilMaskLightBackface = (1<<4), // don't render light where it's backface passes z test
+
+ // Next 4 highest bits (3 down to 0) used for lighting layers.
+ kStencilBitLayerStart = 0, // start of lighting layer bits
+ kStencilMaskLayers = ((1<<kLightingLayerCount)-1) << kStencilBitLayerStart,
+
+ kStencilGeomWriteMask = kStencilMaskSomething | kStencilMaskNonLightmapped | kStencilMaskBeyondShadowDistace | kStencilMaskLayers,
+};
+
+
+
+// Lights can illuminate arbitrary layer masks. Say we have several lights:
+// La = XXXXXXXX
+// Lb = XXXXXXX-
+// Lc = XXXX-XXX
+// Ld = XXXX-X--
+// Layers used for excluding lights are then:
+// ----O-OO (3 in total)
+// In stencil buffer, we allocate 3 consecutive bits to handle this:
+// LaS = ---
+// LbS = --O
+// LcS = O--
+// LdS = OOO
+//
+// When rendering an object, set that bit if object belongs to one of light layers.
+//
+// When drawing a light, set stencil mask to light layer stencil mask, and stencil
+// test should be equal to zero in those bits.
+
+struct LightingLayers
+{
+ enum { kLayerCount = 32 };
+
+ LightingLayers (UInt32 lightMask)
+ : lightingLayerMask(lightMask)
+ {
+ for (int i = 0; i < kLayerCount; ++i)
+ layerToStencil[i] = -1;
+
+ int bit = kStencilBitLayerStart + kLightingLayerCount - 1;
+ lightLayerCount = 0;
+ UInt32 mask = 1;
+ for (int i = 0; i < kLayerCount; ++i, mask<<=1)
+ {
+ if (lightMask & mask)
+ {
+ if (lightLayerCount < kLightingLayerCount)
+ layerToStencil[i] = bit;
+ --bit;
+ ++lightLayerCount;
+ }
+ }
+ }
+
+ UInt32 lightingLayerMask;
+ int layerToStencil[kLayerCount];
+ int lightLayerCount;
+};
+
+struct PrePassRenderData {
+ int roIndex;
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<PrePassRenderData> PreRenderPasses;
+
+
+struct PrePassRenderLoop
+{
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ BatchRenderer m_BatchRenderer;
+ #endif
+
+ PreRenderPasses m_PlainRenderPasses;
+
+ RenderTexture* RenderBasePass (RenderTexture* rtMain, const LightingLayers& lightingLayers, RenderObjectDataContainer& outRemainingObjects, MinMaxAABB& receiverBounds);
+
+ void RenderLighting (
+ ActiveLights& activeLights,
+ RenderTexture* rtMain,
+ TextureID depthTextureID,
+ RenderTexture* rtNormalsSpec,
+ RenderTexture*& rtLight,
+
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture*& rtLightSpec,
+#endif
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ MinMaxAABB& receiverBounds,
+ RenderTexture** outMainShadowMap);
+
+ void RenderFinalPass (RenderTexture* rtMain,
+ RenderTexture* rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec,
+#endif
+ bool hdr,
+ bool linearLighting);
+
+ struct RenderPrePassObjectSorterHash
+ {
+ bool operator()( const PrePassRenderData& ra, const PrePassRenderData& rb ) const;
+ const PrePassRenderLoop* queue;
+ };
+
+ void SortPreRenderPassData( PreRenderPasses& passes )
+ {
+ RenderPrePassObjectSorterHash sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+struct RenderPrePassObjectSorter {
+ bool operator()( const RenderObjectData& ra, const RenderObjectData& rb ) const;
+};
+
+
+
+
+bool RenderPrePassObjectSorter::operator()( const RenderObjectData& ra, const RenderObjectData& rb ) const
+{
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by render queues first
+ if( ra.queueIndex != rb.queueIndex )
+ return ra.queueIndex < rb.queueIndex;
+
+ // sort by lightmap index
+ if( ra.lightmapIndex != rb.lightmapIndex )
+ return ra.lightmapIndex < rb.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ if( ra.staticBatchIndex != rb.staticBatchIndex )
+ return ra.staticBatchIndex > rb.staticBatchIndex; // assuming that statically batched geometry occlude more - render it first
+#endif
+
+ // then sort by material (maybe better sort by shader?)
+ if( ra.material != rb.material )
+ return ra.material->GetInstanceID() < rb.material->GetInstanceID(); // just compare instance IDs
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+bool PrePassRenderLoop::RenderPrePassObjectSorterHash::operator()( const PrePassRenderData& ra, const PrePassRenderData& rb ) const
+{
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ // sort by hash
+ if( ra.hash != rb.hash )
+ return ra.hash < rb.hash;
+
+ // Sort front to back
+ return dataa.distance > datab.distance;
+}
+#endif
+
+static Texture* defaultSpotCookie = NULL;
+
+static void AssignCookieToMaterial(const Light& light, Material* lightMaterial)
+{
+ //@TODO: when computing positions from screen space, mipmapping of cookie will really play against
+ // us, when some adjacent pixels will happen to have very similar UVs. It will sample high levels which
+ // will be mostly black!
+ // Proper fix would be manual derivatives based on something else in the shader, but that needs SM3.0 on D3D
+ // and GLSL in GL. So just use bad mip bias for now.
+
+ Texture* cookie = light.GetCookie();
+
+ if(cookie)
+ {
+ lightMaterial->SetTexture (kSLPropLightTexture0, cookie);
+ }
+ else if(light.GetType() == kLightSpot)
+ {
+ if(!defaultSpotCookie)
+ {
+ defaultSpotCookie = (Texture*)GetRenderSettings().GetDefaultSpotCookie();
+ }
+ lightMaterial->SetTexture (kSLPropLightTexture0, defaultSpotCookie);
+ }
+}
+
+
+// To properly collect & blur directional light's screen space shadow map,
+// we need to have shadow receivers that are forward-rendered in the depth buffer.
+// Also, if camera needs a depth texture, forward-rendered objects should be there
+// as well.
+static void RenderForwardObjectsIntoDepth (
+ const RenderLoopContext& ctx,
+ RenderTexture* rt,
+ RenderObjectDataContainer* forwardRenderedObjects,
+ RenderSurfaceHandle rtColorSurface,
+ RenderSurfaceHandle rtDepthSurface,
+ int width, int height,
+ bool cameraNeedsDepthTexture)
+{
+ Assert (rt);
+
+ if (!forwardRenderedObjects || forwardRenderedObjects->size() == 0)
+ return; // nothing to do
+
+ PROFILER_AUTO_GFX(gPrepassFwdDepth, ctx.m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionOpaquePass);
+
+ Shader* depthShader = GetCameraDepthTextureShader ();
+ if (!depthShader)
+ return;
+
+ // If we do not need the depth texture, leave only the objects that will possibly receive shadows;
+ // no need to render all forward objects.
+ RenderObjectDataContainer forwardRenderedShadowReceivers;
+ if (!cameraNeedsDepthTexture)
+ {
+ size_t n = forwardRenderedObjects->size();
+ forwardRenderedShadowReceivers.reserve (n / 4);
+ for (size_t i = 0; i < n; ++i)
+ {
+ RenderObjectData& roData = (*forwardRenderedObjects)[i];
+ DebugAssert (roData.visibleNode);
+ BaseRenderer* renderer = roData.visibleNode->renderer;
+ DebugAssert (renderer);
+ if (!renderer->GetReceiveShadows())
+ continue; // does not receive shadows
+ Shader* shader = roData.shader;
+ int ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex (kRenderPathExtForward);
+ if (ss == -1)
+ continue; // is not forward rendered
+ forwardRenderedShadowReceivers.push_back (roData);
+ }
+
+ if (forwardRenderedShadowReceivers.size() == 0)
+ return; // nothing left to render
+ forwardRenderedObjects = &forwardRenderedShadowReceivers;
+ }
+
+ RenderTexture::SetActive (1, &rtColorSurface, rtDepthSurface, rt);
+ RenderSceneShaderReplacement (*forwardRenderedObjects, depthShader, "RenderType");
+}
+
+static RenderTexture* ComputeScreenSpaceShadowMap (
+ const RenderLoopContext& ctx,
+ RenderTexture* shadowMap,
+ float blurWidth,
+ float blurFade,
+ ShadowType shadowType)
+{
+ Assert (shadowMap);
+
+ GfxDevice& device = GetGfxDevice();
+
+ if (!s_CollectMaterial)
+ {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Internal-PrePassCollectShadows");
+ s_CollectMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ SetShadowsKeywords (kLightDirectional, shadowType, false, false);
+ RenderBufferManager& rbm = GetRenderBufferManager ();
+
+ RenderTexture* screenShadowMap = rbm.GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (screenShadowMap);
+
+ // Clear so that tiled and multi-GPU systems don't do a RT unresolve
+ float clearColor[4] = {1,0,1,0};
+ device.Clear(kGfxClearColor, clearColor, 1.0f, 0);
+
+ LoadFullScreenOrthoMatrix ();
+ s_CollectMaterial->SetTexture (PrePassPrivate::kSLPropShadowMapTexture, shadowMap);
+ s_CollectMaterial->SetPass (0);
+
+ Vector3f ray;
+ device.ImmediateBegin (kPrimitiveQuads);
+
+ float x1 = 0.0f;
+ float x2 = 1.0f;
+ float y1 = 0.0f;
+ float y2 = 1.0f;
+ float f = ctx.m_Camera->GetProjectionFar();
+
+ const Transform& camtr = ctx.m_Camera->GetComponent(Transform);
+ Matrix4x4f cameraWorldToLocalNoScale = camtr.GetWorldToLocalMatrixNoScale();
+
+ device.ImmediateTexCoord (0, x1, y1, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x1, y1, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x1, y1, 0.1f);
+
+ device.ImmediateTexCoord (0, x2, y1, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x2, y1, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x2, y1, 0.1f);
+
+ device.ImmediateTexCoord (0, x2, y2, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x2, y2, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x2, y2, 0.1f);
+
+ device.ImmediateTexCoord (0, x1, y2, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x1, y2, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x1, y2, 0.1f);
+
+ device.ImmediateEnd ();
+ GPU_TIMESTAMP();
+
+ rbm.ReleaseTempBuffer (shadowMap);
+
+ // possibly blur into another screen-space render texture
+ SetShadowsKeywords (kLightDirectional, shadowType, true, true);
+ if (IsSoftShadow(shadowType) && GetSoftShadowsEnabled())
+ return BlurScreenShadowMap (screenShadowMap, shadowType, f, blurWidth, blurFade);
+
+ return screenShadowMap;
+}
+
+static void RenderLightGeom (const RenderLoopContext& ctx, const ActiveLight& light, const Vector3f& lightPos, const Matrix4x4f& lightMatrix, const bool renderAsQuad)
+{
+ // Spot and point lights: render as tight geometry. If it doesn't intersect near or far, stencil optimisation will be used
+ // (rendering the z tested back faces into stencil and then front faces will only pass for these pixels).
+ // If it intersects near, back faces with z test greater will be rendered (shouldn't use that when not intersecting near, because
+ // then there could be objects between the cam and the light, not touching the light).
+ // If it intersects far, render front faces without any gimmicks.
+ // If it intersects both near and far, render as a quad.
+
+ GfxDevice& device = GetGfxDevice();
+ Light& l = *light.light;
+ float r = l.GetRange();
+ float n = ctx.m_Camera->GetProjectionNear() * 1.001f;
+
+ if (l.GetType() == kLightPoint && !renderAsQuad)
+ {
+ #if GFX_USE_SPHERE_FOR_POINT_LIGHT
+ ChannelAssigns ch;
+ ch.Bind (kShaderChannelVertex, kVertexCompVertex);
+
+ // Older content might have included/overriden old Internal-PrePassLighting.shader,
+ // which relied on normals being zeros here. Light .fbx files have zero normals just for that.
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+ ch.Bind (kShaderChannelNormal, kVertexCompNormal);
+
+ Matrix4x4f m;
+ m.SetTranslate (lightPos);
+ m.Get (0, 0) = r;
+ m.Get (1, 1) = r;
+ m.Get (2, 2) = r;
+ // Point lights bigger than 0.25 of the screen height can be rendered with high-poly, but tighter geometry.
+ DrawUtil::DrawMesh (ch, light.screenRect.height > 0.25f ? *s_Icosphere : *s_Icosahedron, m, -1);
+ #else
+ // PS3 is not the best at vertex processing, so stick to low-poly meshes
+ device.ImmediateShape(lightPos.x, lightPos.y, lightPos.z, r, GfxDevice::kShapeCube);
+ #endif
+ }
+ else if (l.GetType() == kLightSpot && !renderAsQuad)
+ {
+ Matrix4x4f m (lightMatrix);
+ ChannelAssigns ch;
+ ch.Bind (kShaderChannelVertex, kVertexCompVertex);
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+ ch.Bind (kShaderChannelNormal, kVertexCompNormal);
+ float sideLength = r / l.GetCotanHalfSpotAngle ();
+ m.Scale (Vector3f(sideLength, sideLength, r));
+ DrawUtil::DrawMesh (ch, *s_Pyramid, m, -1);
+ }
+ else // Directional light or spot/point that needs to be rendered as a quad
+ {
+ DeviceViewProjMatricesState preserveViewProj;
+
+ const Camera* camera = ctx.m_Camera;
+ float nearPlane = 0;
+
+ float x1 = light.screenRect.x;
+ float x2 = light.screenRect.x + light.screenRect.width;
+ float y1 = light.screenRect.y;
+ float y2 = light.screenRect.y + light.screenRect.height;
+
+ // Calculate rays pointing from the camera to the near plane's corners in camera space
+ Vector3f ray1 = camera->ViewportToCameraPoint (Vector3f(x1, y1, n));
+ Vector3f ray2 = camera->ViewportToCameraPoint (Vector3f(x1, y2, n));
+ Vector3f ray3 = camera->ViewportToCameraPoint (Vector3f(x2, y2, n));
+ Vector3f ray4 = camera->ViewportToCameraPoint (Vector3f(x2, y1, n));
+
+ // Set up orthographic projection not to have to deal with precision problems
+ // that show up when drawing a full screen quad in perspective projection in world space.
+ LoadFullScreenOrthoMatrix (nearPlane, camera->GetProjectionFar(), true);
+
+ // Draw the fullscreen quad on the near plane
+ device.ImmediateBegin (kPrimitiveQuads);
+
+ device.ImmediateNormal (ray1.x, ray1.y, ray1.z);
+ device.ImmediateVertex (x1, y1, nearPlane);
+
+ device.ImmediateNormal (ray2.x, ray2.y, ray2.z);
+ device.ImmediateVertex (x1, y2, nearPlane);
+
+ device.ImmediateNormal (ray3.x, ray3.y, ray3.z);
+ device.ImmediateVertex (x2, y2, nearPlane);
+
+ device.ImmediateNormal (ray4.x, ray4.y, ray4.z);
+ device.ImmediateVertex (x2, y1, nearPlane);
+
+ device.ImmediateEnd ();
+ GPU_TIMESTAMP();
+ }
+}
+
+static UInt32 LightMask (const Light& l, const LightingLayers& lightingLayers)
+{
+ UInt32 mask = 0U;
+ UInt32 lightExcludeLayers = ~l.GetCullingMask();
+ int bit = 0;
+ while (lightExcludeLayers)
+ {
+ if (lightExcludeLayers & 1)
+ {
+ int layerStencilBit = lightingLayers.layerToStencil[bit];
+ if (layerStencilBit != -1)
+ mask |= 1 << layerStencilBit;
+ }
+ lightExcludeLayers >>= 1;
+ ++bit;
+ }
+ return mask;
+}
+
+static RenderTexture* RenderLight (
+ const RenderLoopContext& ctx,
+ const ShadowCullData& shadowCullData,
+ QualitySettings::ShadowQuality shadowQuality,
+ const LightmapSettings::LightmapsMode lightmapsMode,
+ RenderTexture*& rtLight,
+ RenderTexture* rtMain,
+ int width, int height,
+ DeviceStencilState* devStDisabled,
+ const MinMaxAABB& receiverBounds,
+ const DeviceMVPMatricesState& mvpState,
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ const ActiveLight& light,
+#if SEPERATE_PREPASS_SPECULAR
+ bool specularPass,
+#endif
+ bool returnShadowMap)
+{
+ Light& l = *light.light;
+
+ PROFILER_AUTO_GFX(gPrepassLight, &l);
+
+ const Light::Lightmapping lightmappingMode = l.GetLightmappingForRender();
+ const Transform& trans = l.GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+ Vector3f lightPos = lightMatrix.GetPosition();
+
+ Assert(light.isVisibleInPrepass);
+ Assert(!light.screenRect.IsEmpty());
+
+ ShadowType lightShadows = l.GetShadows();
+ // Shadows on local lights are Pro only
+ if (lightShadows != kShadowNone && l.GetType() != kLightDirectional &&
+ !GetBuildSettings().hasLocalLightShadows)
+ lightShadows = kShadowNone;
+
+ // Check if soft shadows are allowed by license, quality settings etc.
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1) &&
+ lightShadows > kShadowHard && !GetSoftShadowsEnabled())
+ lightShadows = kShadowHard;
+
+ GfxDevice& device = GetGfxDevice();
+ BuiltinShaderParamValues& params = device.GetBuiltinParamValues();
+
+ RenderSurfaceHandle rtSurfaceColor;
+ RenderSurfaceHandle rtSurfaceDepth = rtMain->GetDepthSurfaceHandle(); // re-use depth from final target
+ RenderSurfaceHandle rtSurfaceMainColor = rtMain->GetColorSurfaceHandle(); // will allocate color later (if any lights will actually be present)
+
+ bool hdr = ctx.m_Camera->GetUsingHDR();
+ float white[] = {1,1,1,1};
+ float black[] = {0,0,0,0};
+ UInt32 rtFlags = RenderTexture::kFlagDontRestoreColor;
+
+ if (!rtLight)
+ {
+ rtLight = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, hdr ? GetGfxDevice().GetDefaultHDRRTFormat() : kRTFormatARGB32, 0, kRTReadWriteLinear);
+
+ if (!rtLight->IsCreated())
+ rtLight->Create();
+
+ rtLight->SetFilterMode (kTexFilterNearest);
+ rtSurfaceColor = rtLight->GetColorSurfaceHandle();
+
+
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtLight, 0, kCubeFaceUnknown, rtFlags);
+ GraphicsHelper::Clear(kGfxClearColor, hdr ? black : white, 1.0f, 0);
+ GPU_TIMESTAMP();
+ }
+
+ rtSurfaceColor = rtLight->GetColorSurfaceHandle();
+
+ l.SetLightKeyword();
+
+ Vector3f lightDir = lightMatrix.GetAxisZ();
+ ColorRGBAf lightCol = GammaToActiveColorSpace (l.GetColor()) * l.GetIntensity() * 2.0f;
+
+ Matrix4x4f temp1, temp2, temp3;
+ if (l.GetType() == kLightSpot)
+ {
+ Matrix4x4f worldToLight = l.GetWorldToLocalMatrix();
+ {
+ temp1.SetScale (Vector3f (-.5f, -.5f, 1.0f));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0.0f));
+ temp3.SetPerspectiveCotan( l.GetCotanHalfSpotAngle(), 0.0f, l.GetRange() );
+ // temp2 * temp3 * temp1 * worldToLight
+ Matrix4x4f temp4;
+ MultiplyMatrices4x4 (&temp2, &temp3, &temp4);
+ MultiplyMatrices4x4 (&temp4, &temp1, &temp2);
+ MultiplyMatrices4x4 (&temp2, &worldToLight, &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+ }
+ }
+ else if (l.GetCookie())
+ {
+ if (l.GetType() == kLightPoint)
+ {
+ params.SetMatrixParam(kShaderMatLightMatrix, l.GetWorldToLocalMatrix());
+ }
+ else if (l.GetType() == kLightDirectional)
+ {
+ float scale = 1.0f / l.GetCookieSize();
+ temp1.SetScale (Vector3f (scale, scale, 0));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0));
+ // temp2 * temp1 * l.GetWorldToLocalMatrix()
+ MultiplyMatrices4x4 (&temp2, &temp1, &temp3);
+ MultiplyMatrices4x4 (&temp3, &l.GetWorldToLocalMatrix(), &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+ }
+ }
+
+ AssignCookieToMaterial(l, s_LightMaterial);
+
+ const bool renderAsQuad = light.intersectsNear && light.intersectsFar || l.GetType() == kLightDirectional;
+ ShaderLab::g_GlobalProperties->SetFloat(kSLPropLightAsQuad, renderAsQuad ? 1.0f : 0.0f);
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightPos, lightPos.x, lightPos.y, lightPos.z, 1.0f / (l.GetRange() * l.GetRange()));
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightDir, lightDir.x, lightDir.y, lightDir.z, 0.0f);
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightColor, lightCol.GetPtr());
+ ///@TODO: cleanup, remove this from Internal-PrePassLighting shader
+ s_LightMaterial->SetTexture (ShaderLab::Property("_LightTextureB0"), builtintex::GetAttenuationTexture());
+
+ RenderTexture* shadowMap = NULL;
+ ShadowCameraData camData(shadowCullData);
+
+
+ if (light.shadowedLight != NULL && receiverBounds.IsValid() && shadowQuality != QualitySettings::kShadowsDisable)
+ {
+ Assert(light.insideShadowRange);
+
+ ShadowType lightShadows = l.GetShadows();
+
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1))
+ {
+ if (shadowQuality == QualitySettings::kShadowsHardOnly && lightShadows != kShadowNone)
+ lightShadows = kShadowHard;
+ }
+
+ SetShadowsKeywords (l.GetType(), lightShadows, false, true);
+
+ Matrix4x4f shadowMatrices[kMaxShadowCascades];
+ device.SetViewMatrix (ctx.m_CurCameraMatrix.GetPtr());
+ device.SetStencilState (devStDisabled, 0);
+
+ // Rendering shadowmaps will switch away from the lighting buffer and then will switch back.
+ // Nothing we can do about it, so don't produce the warning.
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ shadowMap = RenderShadowMaps (camData, light, receiverBounds, false, shadowMatrices);
+
+ if (!shadowMap)
+ {
+ // If shadow map could not actually be created (no casters, out of VRAM, whatever),
+ // set the no shadows keywords and proceed. So there will be no shadows,
+ // but otherwise it will be ok.
+ SetNoShadowsKeywords();
+ }
+ else
+ {
+ Vector4f data;
+
+ // ambient & shadow fade out
+ data.x = 1.0f - l.GetShadowStrength(); // R = 1-strength
+ data.y = data.z = data.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightShadowData, data);
+
+ if (l.GetType() == kLightDirectional)
+ {
+ params.SetMatrixParam(kShaderMatWorldToShadow, shadowMatrices[0]);
+ SetCascadedShadowShaderParams (shadowMatrices, camData.splitDistances, camData.splitSphereCentersAndSquaredRadii);
+
+ shadowMap = ComputeScreenSpaceShadowMap (
+ ctx,
+ shadowMap,
+ l.GetShadowSoftness(),
+ l.GetShadowSoftnessFade(),
+ lightShadows);
+ }
+ else if (l.GetType() == kLightSpot)
+ {
+ params.SetMatrixParam(kShaderMatWorldToShadow, shadowMatrices[0]);
+ }
+
+ // texel offsets for PCF
+ float offX = 0.5f / shadowMap->GetGLWidth();
+ float offY = 0.5f / shadowMap->GetGLHeight();
+ data.z = 0.0f; data.w = 0.0f;
+ data.x = -offX; data.y = -offY; params.SetVectorParam(kShaderVecShadowOffset0, data);
+ data.x = offX; data.y = -offY; params.SetVectorParam(kShaderVecShadowOffset1, data);
+ data.x = -offX; data.y = offY; params.SetVectorParam(kShaderVecShadowOffset2, data);
+ data.x = offX; data.y = offY; params.SetVectorParam(kShaderVecShadowOffset3, data);
+ s_LightMaterial->SetTexture (PrePassPrivate::kSLPropShadowMapTexture, shadowMap);
+
+ if (rtLight != NULL)
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtLight);
+ else
+ RenderTexture::SetActive (1, &rtSurfaceMainColor, rtSurfaceDepth, rtMain);
+ }
+ device.SetViewMatrix(mvpState.GetView().GetPtr());
+ device.SetProjectionMatrix(mvpState.GetProj());
+ SetClippingPlaneShaderProps();
+
+ // restore the cull mode, since it could be changed by a shadow caster with odd-negative scale
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+ }
+ else
+ {
+ SetNoShadowsKeywords ();
+ }
+
+ // Draw each light in two passes: illuminate non-lightmapped objects; illuminate lightmapped objects.
+ int lightPassCount = 2;
+ int lightPassAddBits[2] = { kStencilMaskNonLightmapped, 0 };
+ if (lightmappingMode == Light::kLightmappingRealtimeOnly)
+ {
+ // If light is realtime only, it's enough to draw one pass; that illuminates any object
+ // be it lightmapped or not.
+ lightPassCount = 1;
+ lightPassAddBits[0] = 0;
+ }
+ else if (lightmappingMode == Light::kLightmappingAuto && lightmapsMode != LightmapSettings::kDualLightmapsMode)
+ {
+ // If it's an auto light but we're in single lightmaps mode, draw in one pass only to illuminate
+ // non-lightmapped objects
+ // TODO: realtime shadows from auto lights won't be received by lightmapped objects. Do we want to fix it?
+ lightPassCount = 1;
+ lightPassAddBits[0] = kStencilMaskNonLightmapped;
+ }
+
+ //TODO: skip if smaller than certain size
+ const bool useStencilMask = !light.intersectsNear && !light.intersectsFar &&
+ (lightmappingMode == Light::kLightmappingRealtimeOnly) &&
+ (l.GetType() == kLightSpot || l.GetType() == kLightPoint );
+
+ const UInt32 lightmask = LightMask (l, lightingLayers);
+
+ // Render stencil mask, to discard all light pixels, at which the light is fully in front of scene geometry.
+ if (useStencilMask)
+ {
+ Material::GetDefault ()->SetPass (0);
+ #if UNITY_XENON
+ device.SetNullPixelShader ();
+ #endif
+
+ GfxBlendState blendstate;
+ blendstate.renderTargetWriteMask = 0U;
+ device.SetBlendState (device.CreateBlendState(blendstate), 0);
+
+ GfxRasterState rasterstate;
+ rasterstate.cullMode = kCullOff;
+ device.SetRasterState (device.CreateRasterState(rasterstate));
+
+ GfxDepthState depthstate;
+ depthstate.depthWrite = false;
+ depthstate.depthFunc = kFuncLEqual;
+ device.SetDepthState (device.CreateDepthState(depthstate));
+
+ GfxStencilState lightStencil;
+ lightStencil.stencilEnable = true;
+ lightStencil.readMask = 0xFFU;
+ lightStencil.writeMask = kStencilMaskLightBackface;
+ lightStencil.stencilZFailOpBack = kStencilOpInvert;
+ lightStencil.stencilZFailOpFront = kStencilOpInvert;
+ lightStencil.stencilPassOpBack = kStencilOpKeep;
+ lightStencil.stencilPassOpFront = kStencilOpKeep;
+ lightStencil.stencilFuncBack = (lightmask != 0 ) ? kFuncNotEqual : kFuncAlways;
+ lightStencil.stencilFuncFront = (lightmask != 0 ) ? kFuncNotEqual : kFuncAlways;
+ device.SetStencilState (device.CreateStencilState(lightStencil), lightmask|kStencilMaskSomething|kStencilMaskNonLightmapped);
+
+ #if UNITY_XENON
+ // Clear within light-geom, sets all HiS to cull.
+ // Set to cull where equal to background (to deal with lightmasks), unoptimal but works
+ if (useStencilMask)
+ device.SetHiStencilState (false, true, kStencilMaskSomething|kStencilMaskNonLightmapped, kFuncEqual);
+ #endif
+
+ RenderLightGeom (ctx, light, lightPos, lightMatrix, renderAsQuad);
+
+ blendstate.renderTargetWriteMask = KColorWriteAll;
+ device.SetBlendState (device.CreateBlendState(blendstate), 0);
+
+ #if UNITY_XENON
+ device.HiStencilFlush (kHiSflush_sync);
+ #endif
+ }
+
+ for (int pp = 0; pp < lightPassCount; ++pp)
+ {
+ Vector4f lightingFade = lightFade;
+ Vector4f shadowFade = lightFade;
+ shadowFade.x = 1.0f - l.GetShadowStrength();
+ if (pp == 0 || lightmappingMode == Light::kLightmappingRealtimeOnly)
+ lightingFade.z = lightingFade.w = 0.0f;
+ else
+ shadowFade.z = shadowFade.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightmapFade, lightingFade);
+ params.SetVectorParam(kShaderVecLightShadowData, shadowFade);
+
+ // Disable mipmapping on light cookies
+ ShaderLab::TexEnv* cookieEnv = s_LightMaterial->GetProperties().GetTexEnv(kSLPropLightTexture0);
+ if (cookieEnv)
+ {
+ cookieEnv->TextureMipBiasChanged (-8);
+ }
+
+ #if SEPERATE_PREPASS_SPECULAR
+ if (s_LightMaterial->GetPassCount () > 2 && ctx.m_Camera->GetUsingHDR() && specularPass)
+ s_LightMaterial->SetPass (2);
+ else
+ #endif
+ if (s_LightMaterial->GetPassCount () > 1 && ctx.m_Camera->GetUsingHDR())
+ s_LightMaterial->SetPass (1);
+ else
+ s_LightMaterial->SetPass (0);
+
+ // Construct stencil read mask
+ GfxStencilState stencil;
+ stencil.stencilEnable = true;
+ stencil.stencilFuncFront = stencil.stencilFuncBack = kFuncEqual;
+ stencil.readMask = kStencilMaskSomething;
+ // Check lightmapped vs. non-lightmapped unless it's a realtime light that
+ // only cares about not illuminating non-something.
+ if (lightmappingMode != Light::kLightmappingRealtimeOnly)
+ stencil.readMask |= kStencilMaskNonLightmapped;
+
+ if (pp != 0 && lightmappingMode != Light::kLightmappingRealtimeOnly)
+ stencil.readMask |= kStencilMaskBeyondShadowDistace;
+
+ stencil.readMask |= lightmask;
+ int stencilRef = kStencilMaskSomething + lightPassAddBits[pp];
+
+ if (useStencilMask)
+ {
+ // Clear stencil while rendering
+ stencil.writeMask = kStencilMaskLightBackface;
+ stencil.stencilZFailOpBack = kStencilOpZero;
+ stencil.stencilZFailOpFront = kStencilOpZero;
+ stencil.stencilPassOpBack = kStencilOpZero;
+ stencil.stencilPassOpFront = kStencilOpZero;
+ // Clear the kStencilMaskLightBackface bit even if rejecting pixel due to stencil layer mask
+ stencil.stencilFailOpBack = kStencilOpZero;
+ stencil.stencilFailOpFront = kStencilOpZero;
+
+ stencil.readMask |= kStencilMaskLightBackface;
+ stencilRef |= kStencilMaskLightBackface;
+ }
+
+ DeviceStencilState* devStCheck = device.CreateStencilState (stencil);
+ device.SetStencilState (devStCheck, stencilRef);
+
+ #if UNITY_XENON
+ // Set to cull when all == background (to deal with lightmasks), unoptimal but works
+ if (useStencilMask)
+ device.SetHiStencilState (true, true, kStencilMaskSomething|kStencilMaskNonLightmapped, kFuncEqual);
+ #endif
+
+ // Draw light shape
+ GfxRasterState rasterstate;
+ GfxDepthState depthstate;
+ depthstate.depthWrite = false;
+ if (light.intersectsNear && !light.intersectsFar && (l.GetType() == kLightSpot || l.GetType() == kLightPoint))
+ {
+ // When near (but not far) plane intersects the light, render back faces (tighter than rendering a bounding quad).
+ // Can't use this when not intersecting, since it would waste processing for objects between
+ // the light and the cam, even when they don't touch the light.
+ rasterstate.cullMode = kCullFront;
+ depthstate.depthFunc = kFuncGreater;
+ }
+ else
+ {
+ depthstate.depthFunc = kFuncLEqual;
+ #if UNITY_XENON
+ device.SetHiZEnable (kHiZEnable);
+ #endif
+ }
+ device.SetRasterState (device.CreateRasterState (rasterstate));
+ device.SetDepthState (device.CreateDepthState (depthstate));
+
+ RenderLightGeom (ctx, light, lightPos, lightMatrix, renderAsQuad);
+
+ #if UNITY_XENON
+ device.SetHiZEnable (kHiZAuto);
+ if (useStencilMask)
+ device.HiStencilFlush (kHiSflush_async);
+ #endif
+ }
+
+ if (shadowMap && !returnShadowMap)
+ GetRenderBufferManager().ReleaseTempBuffer (shadowMap);
+
+ return returnShadowMap ? shadowMap : NULL;
+}
+
+
+void PrePassRenderLoop::RenderLighting (
+ ActiveLights& activeLights,
+ RenderTexture* rtMain,
+ TextureID depthTextureID,
+ RenderTexture* rtNormalsSpec,
+ RenderTexture*& rtLight,
+
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture*& rtLightSpec,
+#endif
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ MinMaxAABB& receiverBounds,
+ RenderTexture** outMainShadowMap)
+{
+ PROFILER_AUTO_GFX(gPrepassLighting, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionDeferedLighting);
+ *outMainShadowMap = NULL;
+
+ Assert(rtLight == NULL);
+#if SEPERATE_PREPASS_SPECULAR
+ Assert(rtLightSpec == NULL);
+#endif
+ const QualitySettings::ShadowQuality shadowQuality = static_cast<QualitySettings::ShadowQuality>(GetQualitySettings().GetCurrent().shadows);
+ const LightmapSettings::LightmapsMode lightmapsMode = static_cast<LightmapSettings::LightmapsMode>(GetLightmapSettings().GetLightmapsMode());
+
+ ShadowCameraData camData(*m_Context->m_ShadowCullData);
+
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ receiverBounds.Expand( 0.01f );
+
+ const Rectf screenRect = m_Context->m_Camera->GetScreenViewportRect();
+
+ if (!s_LightMaterial) {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Internal-PrePassLighting");
+ s_LightMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ if (s_Icosahedron.IsNull ())
+ s_Icosahedron = GetBuiltinResource<Mesh> ("icosahedron.fbx");
+ if (s_Icosphere.IsNull ())
+ s_Icosphere = GetBuiltinResource<Mesh> ("icosphere.fbx");
+ if (s_Pyramid.IsNull ())
+ s_Pyramid = GetBuiltinResource<Mesh> ("pyramid.fbx");
+
+ static SHADERPROP (CameraDepthTexture);
+ static SHADERPROP (CameraNormalsTexture);
+ const int width = rtNormalsSpec->GetGLWidth();
+ const int height = rtNormalsSpec->GetGLHeight();
+ if (gGraphicsCaps.hasStencilInDepthTexture)
+ {
+ ShaderLab::g_GlobalProperties->SetRectTextureID (
+ kSLPropCameraDepthTexture,
+ depthTextureID,
+ width,
+ height,
+ rtMain->GetTexelSizeX(),
+ rtMain->GetTexelSizeY(),
+ rtMain->GetUVScaleX(),
+ rtMain->GetUVScaleY()
+ );
+ }
+
+ // set as _CameraNormalsTexture for external access
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropCameraNormalsTexture, rtNormalsSpec);
+
+ GfxDevice& device = GetGfxDevice();
+
+ SetAndRestoreWireframeMode setWireframeOff(false); // turn off wireframe; will restore old value in destructor
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ DeviceStencilState* devStDisabled = device.CreateStencilState (GfxStencilState());
+
+
+ DeviceMVPMatricesState preserveMVP;
+
+ device.SetWorldMatrix (Matrix4x4f::identity.GetPtr());
+
+ RenderTexture** currentLightTex = &rtLight;
+#if SEPERATE_PREPASS_SPECULAR
+ //Do 2 passes for HDR prepass lighting on xenon
+ for (int lp = 0; lp < (m_Context->m_Camera->GetUsingHDR() ? 2 : 1); ++lp)
+ {
+ if (lp == 0)
+ currentLightTex = &rtLight;
+ else
+ currentLightTex = &rtLightSpec;
+#endif
+
+ const ActiveLight* mainActiveLight = GetMainActiveLight(activeLights);
+ ActiveLights::Array::iterator it, itEnd = activeLights.lights.end();
+ for (it = activeLights.lights.begin(); it != itEnd; ++it)
+ {
+ if (!it->isVisibleInPrepass)
+ continue;
+ if (&*it == mainActiveLight)
+ {
+ // skip main light now; will render it last
+ continue;
+ }
+ RenderLight (*m_Context, camData, shadowQuality, lightmapsMode,
+ *currentLightTex,
+ rtMain,
+ width, height, devStDisabled, receiverBounds,
+ preserveMVP, lightFade, lightingLayers, *it,
+#if SEPERATE_PREPASS_SPECULAR
+ lp == 1,
+#endif
+ false);
+ }
+
+ #if UNITY_XENON
+ device.SetStencilState (devStDisabled, 0);
+ device.SetHiStencilState (false, false, 0, kFuncEqual);
+ #endif
+
+ // render main light
+ if (mainActiveLight)
+ {
+ RenderTexture* shadowMap = RenderLight (
+ *m_Context, camData, shadowQuality, lightmapsMode,
+ *currentLightTex,
+ rtMain,
+ width, height, devStDisabled, receiverBounds,
+ preserveMVP, lightFade, lightingLayers, *mainActiveLight,
+#if SEPERATE_PREPASS_SPECULAR
+ lp == 1,
+#endif
+ true);
+ if (shadowMap)
+ {
+ AddRenderLoopTempBuffer (m_Context->m_RenderLoop, shadowMap);
+ *outMainShadowMap = shadowMap;
+ }
+ }
+#if SEPERATE_PREPASS_SPECULAR
+ }
+#endif
+ SetNoShadowsKeywords ();
+
+ Vector4f lightmapFade = lightFade;
+ // if we're not in dual lightmaps mode, always use the far lightmap, i.e. lightmapFade = 1
+ if (GetLightmapSettings().GetLightmapsMode() != LightmapSettings::kDualLightmapsMode)
+ lightmapFade.z = lightmapFade.w = 1.0f;
+
+ device.GetBuiltinParamValues().SetVectorParam(kShaderVecLightmapFade, lightmapFade);
+
+ device.SetStencilState (devStDisabled, 0);
+
+ #if !UNITY_XENON
+ // Ok, we didn't really have any lights worth rendering.
+ // Create a small render texture and clear it to white and pass it as the lighting buffer.
+ // Don't do that on 360; pointless and saves a resolve.
+ if (!rtLight)
+ {
+ rtLight = GetRenderBufferManager().GetTempBuffer (16, 16, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (rtLight);
+ float white[] = {1,1,1,1};
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearColor, m_Context->m_Camera->GetUsingHDR() ? black : white, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // We just switched away from a Z buffer (only in case when no lights were there!),
+ // and we'll switch back to it. So ignore the unresolve warning on it.
+ device.IgnoreNextUnresolveOnRS(rtMain->GetDepthSurfaceHandle());
+ }
+ #endif
+}
+
+
+static RenderTexture* CombineDepthNormalsTexture (const RenderLoopContext& ctx, RenderObjectDataContainer& remainingObjects)
+{
+ PROFILER_AUTO_GFX(gPrepassCombineDepthNormals, ctx.m_Camera);
+
+ static Material* s_CombineMaterial = NULL;
+ if (!s_CombineMaterial)
+ {
+ Shader* shader = GetScriptMapper ().FindShader ("Hidden/Internal-CombineDepthNormals");
+ if (shader)
+ s_CombineMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ if (!s_CombineMaterial) {
+ AssertString ("Coult not find depth+normals combine shader");
+ return NULL;
+ }
+ }
+
+ RenderTexture* depthNormals = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (depthNormals);
+ GraphicsHelper::Clear (kGfxClearColor, ColorRGBAf(0.5f,0.5f,1.0f,1.0f).GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // Combine depth & normals into single texture
+ ImageFilters::Blit (NULL, depthNormals, s_CombineMaterial, 0, false);
+
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, depthNormals);
+
+ static SHADERPROP (CameraDepthNormalsTexture);
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropCameraDepthNormalsTexture, depthNormals);
+
+ return depthNormals;
+}
+
+
+
+// Separate pass to render depth into a separate target. Only used on Macs with Radeon HDs, since
+// only there doing it the regular way is broken.
+#if GFX_SUPPORTS_OPENGL
+static RenderTexture* RenderBasePassDepth (const RenderLoopContext& ctx, RenderObjectDataContainer& renderData, PreRenderPasses& plainRenderPasses)
+{
+ GPU_AUTO_SECTION(kGPUSectionDeferedPrePass);
+
+ GfxDevice& device = GetGfxDevice();
+
+ RenderTexture* rt = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormat24, kRTFormatDepth, 0, kRTReadWriteLinear);
+ rt->SetFilterMode (kTexFilterNearest);
+ if (!rt->IsCreated())
+ rt->Create();
+ RenderTexture::SetActive (rt);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rt);
+
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearAll, black, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ device.SetViewMatrix (ctx.m_CurCameraMatrix.GetPtr());
+
+ size_t ndata = renderData.size();
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = plainRenderPasses[i];
+ const RenderObjectData& roData = renderData[rpData.roIndex];
+ Shader* shader = roData.shader;
+ int ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ if (ss == -1)
+ continue;
+
+ const VisibleNode *node = roData.visibleNode;
+
+ SetObjectScale (device, node->lodFade, node->invScale);
+
+ //@TODO: if this returns true and we have any sort of batching, we'd have to break batches here
+ node->renderer->ApplyCustomProperties(*roData.material, shader, ss);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassBase)
+ continue;
+
+ const ChannelAssigns* channels = roData.material->SetPassWithShader(p, shader, ss);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ node->renderer->Render( roData.subsetIndex, *channels );
+ }
+ }
+ }
+
+ return rt;
+}
+#endif
+
+inline float MultiplyAbsVectorZ (const Matrix4x4f& m, const Vector3f& v)
+{
+ return Abs(m.m_Data[2]) * v.x + Abs(m.m_Data[6]) * v.y + Abs(m.m_Data[10]) * v.z;
+}
+
+
+RenderTexture* PrePassRenderLoop::RenderBasePass (
+ RenderTexture* rtMain,
+ const LightingLayers& lightingLayers,
+ RenderObjectDataContainer& outRemainingObjects,
+ MinMaxAABB& receiverBounds
+ )
+{
+ PROFILER_AUTO_GFX(gPrepassGeom, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionDeferedPrePass);
+
+ const float shadowDistance = m_Context->m_ShadowCullData->shadowDistance;
+
+ GfxDevice& device = GetGfxDevice();
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ GfxStencilState stRender;
+ stRender.writeMask = kStencilGeomWriteMask;
+ stRender.stencilEnable = true;
+ stRender.stencilPassOpFront = stRender.stencilPassOpBack = kStencilOpReplace;
+ DeviceStencilState* devStRender = device.CreateStencilState (stRender);
+
+ RenderTexture* rtNormalsSpec = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ rtNormalsSpec->SetFilterMode (kTexFilterNearest);
+ if (!rtNormalsSpec->IsCreated())
+ rtNormalsSpec->Create();
+ RenderSurfaceHandle rtSurfaceColor = rtNormalsSpec->GetColorSurfaceHandle();
+ RenderSurfaceHandle rtSurfaceDepth = rtMain->GetDepthSurfaceHandle(); // reuse depth buffer from final pass
+
+ UInt32 rtFlags = RenderTexture::kFlagDontRestoreColor;
+ UInt32 gfxClearFlags = kGfxClearAll;
+ // do not clear depth/stencil if camera set to DontClear
+ if (m_Context->m_Camera->GetClearFlags() == Camera::kDontClear)
+ {
+ gfxClearFlags &= ~kGfxClearDepthStencil;
+ }
+ else
+ {
+ rtFlags |= RenderTexture::kFlagDontRestoreDepth;
+ }
+
+ // set base pass render texture
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtNormalsSpec, 0, kCubeFaceUnknown, rtFlags);
+
+ AddRenderLoopTempBuffer (m_Context->m_RenderLoop, rtNormalsSpec);
+
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (gfxClearFlags, black, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ device.SetViewMatrix (m_Context->m_CurCameraMatrix.GetPtr());
+
+ const ChannelAssigns* channels = NULL;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ int prevTransformType = -1;
+ Material* prevMaterial = 0;
+ Shader* prevShader = 0;
+ int prevSubshaderIndex = -1;
+ float prevInvScale = 0.0f;
+ float prevLodFade = 0.0f;
+ UInt32 prevCustomPropsHash = 0;
+ int prevPassIndex = -1;
+ int prevStencilRef = 0;
+
+ int canBatch = 0;
+
+ #endif
+
+ const bool directLightBakedInLightProbes = LightProbes::AreBaked() && GetLightmapSettings().GetLightmapsMode() != LightmapSettings::kDualLightmapsMode;
+
+ size_t ndata = m_Objects->size();
+ outRemainingObjects.reserve (ndata / 16);
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roData = (*m_Objects)[rpData.roIndex];
+ Shader* shader = roData.shader;
+
+ int ss = roData.subShaderIndex;
+ if (ss == -1)
+ ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+
+ const VisibleNode *node = roData.visibleNode;
+
+ bool withinShadowDistance = true;
+ float distanceAlongView = roData.distanceAlongView;
+ if (distanceAlongView > shadowDistance)
+ {
+ // check whether its bounds is actually further than shadow distance
+ // project extents onto camera forward axis
+ float z = MultiplyAbsVectorZ (m_Context->m_CurCameraMatrix, node->worldAABB.GetExtent());
+ Assert(z >= 0.0f);
+ if (distanceAlongView - z > shadowDistance)
+ withinShadowDistance = false;
+ }
+
+ if (ss == -1)
+ {
+ if (withinShadowDistance && node->renderer->GetReceiveShadows())
+ receiverBounds.Encapsulate (node->worldAABB);
+ outRemainingObjects.push_back() = roData;
+ continue;
+ }
+
+
+ const float invScale = node->invScale;
+ const float lodFade = node->lodFade;
+ const int transformType = node->transformType;
+ const UInt32 customPropsHash = node->renderer->GetCustomPropertiesHash();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if (
+ node->renderer->GetStaticBatchIndex() == 0 ||
+ prevTransformType != transformType ||
+ prevMaterial != roData.material ||
+ prevShader != shader ||
+ prevSubshaderIndex != ss ||
+ !CompareApproximately(prevInvScale,invScale) ||
+ !CompareApproximately(prevLodFade,lodFade, LOD_FADE_BATCH_EPSILON) ||
+ prevCustomPropsHash != customPropsHash)
+ {
+ m_BatchRenderer.Flush();
+
+ prevTransformType = transformType;
+ prevMaterial = roData.material;
+ prevShader = shader;
+ prevSubshaderIndex = ss;
+ prevInvScale = invScale;
+ prevLodFade = lodFade;
+ prevCustomPropsHash = customPropsHash;
+
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ #endif
+
+ SetObjectScale (device, lodFade, invScale);
+
+ node->renderer->ApplyCustomProperties(*roData.material, shader, ss);
+
+ const bool lightmapped = node->renderer->IsLightmappedForRendering();
+ const Renderer* renderer = static_cast<Renderer*>(node->renderer);
+ const bool directLightFromLightProbes = directLightBakedInLightProbes && node->renderer->GetRendererType() != kRendererIntermediate && renderer->GetUseLightProbes();
+
+ ShaderLab::SubShader& subshader = shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassBase)
+ continue;
+
+ int stencilRef = kStencilMaskSomething;
+ if (!lightmapped && !directLightFromLightProbes)
+ {
+ stencilRef += kStencilMaskNonLightmapped;
+ }
+
+ if (!withinShadowDistance)
+ stencilRef += kStencilMaskBeyondShadowDistace;
+
+ int layerStencilBit = lightingLayers.layerToStencil[node->renderer->GetLayer()];
+ if (layerStencilBit != -1)
+ stencilRef |= 1<<layerStencilBit;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if ((p != prevPassIndex) ||
+ (stencilRef != prevStencilRef))
+ {
+ m_BatchRenderer.Flush();
+ prevPassIndex = p;
+ prevStencilRef = stencilRef;
+ canBatch = 0;
+ }
+
+ if (canBatch <= 1)
+ #endif
+ {
+ device.SetStencilState (devStRender, stencilRef);
+ channels = roData.material->SetPassWithShader(p, shader, ss);
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ prevPassIndex = p;
+ prevStencilRef = stencilRef;
+ #endif
+ }
+
+ receiverBounds.Encapsulate (node->worldAABB);
+
+ if (channels)
+ {
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Add (node->renderer, roData.subsetIndex, channels, node->worldMatrix, transformType);
+ #else
+ SetupObjectMatrix (node->worldMatrix, transformType);
+ node->renderer->Render (roData.subsetIndex, *channels);
+ #endif
+ }
+ }
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Flush();
+ #endif
+
+ return rtNormalsSpec;
+}
+
+void PrePassRenderLoop::RenderFinalPass (RenderTexture* rtMain,
+ RenderTexture* rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec,
+#endif
+ bool hdr,
+ bool linearLighting)
+{
+ PROFILER_AUTO_GFX(gPrepassFinal, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionOpaquePass);
+
+ GfxDevice& device = GetGfxDevice();
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ RenderTexture::SetActive (rtMain);
+
+ // Clear with background. Do not clear depth since we need the already
+ // filled one from the base pass.
+ device.SetSRGBWrite(!hdr && linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()) );
+ m_Context->m_Camera->ClearNoSkybox(true);
+
+ if(rtLight)
+ rtLight->SetGlobalProperty (kSLPropLightBuffer);
+ else
+ {
+ ShaderLab::TexEnv *te = ShaderLab::g_GlobalProperties->SetTexture (kSLPropLightBuffer, hdr ? builtintex::GetBlackTexture() : builtintex::GetWhiteTexture());
+ te->ClearMatrix();
+ }
+
+#if SEPERATE_PREPASS_SPECULAR
+ if(rtLightSpec)
+ rtLightSpec->SetGlobalProperty (kSLPropLightSpecBuffer);
+ else
+ {
+ ShaderLab::TexEnv *te = ShaderLab::g_GlobalProperties->SetTexture (kSLPropLightSpecBuffer, hdr ? builtintex::GetBlackTexture() : builtintex::GetWhiteTexture());
+ te->ClearMatrix();
+ }
+#endif
+
+ const ChannelAssigns* channels = NULL;
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ int prevPassIndex = -1;
+
+ int prevLightmapIndex = -1;
+ Vector4f prevLightmapST (0,0,0,0);
+ int prevTransformType = -1;
+ Material* prevMaterial = 0;
+ Shader* prevShader = 0;
+ int prevSubshaderIndex = -1;
+ float prevInvScale = 0.0f;
+ float prevLodFade = 0.0f;
+ UInt32 prevCustomPropsHash = 0;
+
+ int canBatch = 0;
+
+ #endif
+
+ if (hdr)
+ g_ShaderKeywords.Enable (kKeywordHDRLightPrepassOn);
+ else
+ g_ShaderKeywords.Disable (kKeywordHDRLightPrepassOn);
+
+ LightProbes* lightProbes = GetLightProbes();
+ const bool areLightProbesBaked = LightProbes::AreBaked();
+ BuiltinShaderParamValues& builtinParamValues = GetGfxDevice().GetBuiltinParamValues();
+ Vector3f ambientSH;
+ SHEvalAmbientLight(GetRenderSettings().GetAmbientLightInActiveColorSpace(), &ambientSH[0]);
+
+ size_t ndata = m_Objects->size();
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roData = (*m_Objects)[rpData.roIndex];
+
+ const VisibleNode *node = roData.visibleNode;
+ Shader* shader = roData.shader;
+
+ int ss = roData.subShaderIndex;
+ if (ss == -1)
+ ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ if (ss == -1)
+ continue;
+
+ const Vector4f lightmapST = node->renderer->GetLightmapSTForRendering();
+ const int lightmapIndex = roData.lightmapIndex;
+ DebugAssert(lightmapIndex == node->renderer->GetLightmapIndex());
+
+ const float invScale = node->invScale;
+ const float lodFade = node->lodFade;
+ const int transformType = node->transformType;
+ const UInt32 customPropsHash = node->renderer->GetCustomPropertiesHash();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if (
+ node->renderer->GetStaticBatchIndex() == 0 ||
+ prevTransformType != transformType ||
+ prevMaterial != roData.material ||
+ prevShader != shader ||
+ prevSubshaderIndex != ss ||
+ prevLightmapIndex != lightmapIndex ||
+ !CompareMemory(prevLightmapST, lightmapST) ||
+ !CompareApproximately(prevInvScale,invScale) ||
+ !CompareApproximately(prevLodFade,lodFade) ||
+ prevCustomPropsHash != customPropsHash)
+ {
+ m_BatchRenderer.Flush();
+
+ prevLightmapIndex = lightmapIndex;
+ prevLightmapST = lightmapST;
+ prevTransformType = transformType;
+ prevMaterial = roData.material;
+ prevShader = shader;
+ prevSubshaderIndex = ss;
+ prevInvScale = invScale;
+ prevLodFade = lodFade;
+ prevCustomPropsHash = customPropsHash;
+
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ #endif
+
+ SetObjectScale (device, lodFade, invScale);
+
+ node->renderer->ApplyCustomProperties(*roData.material, roData.shader, ss);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassFinal)
+ continue;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ if (p != prevPassIndex)
+ {
+ m_BatchRenderer.Flush();
+ canBatch = 0;
+ }
+
+ if (canBatch <= 1)
+ #endif
+ {
+ // lightmap
+ SetupObjectLightmaps (lightmapper, lightmapIndex, lightmapST, false);
+
+ // light probes
+ // TODO: figure how does that interact with lightmaps and with batching;
+ // if we are about to use light probes and the renderer gets different coeffs (maybe a simpler check?) => can't batch
+ float lightProbeCoeffs[9][3];
+ memset (lightProbeCoeffs, 0, sizeof(lightProbeCoeffs));
+ if (areLightProbesBaked && node->renderer->GetRendererType() != kRendererIntermediate)
+ {
+ Renderer* renderer = static_cast<Renderer*>(node->renderer);
+ if (renderer && renderer->GetUseLightProbes())
+ lightProbes->GetInterpolatedLightProbe(renderer->GetLightProbeInterpolationPosition(node->worldAABB), renderer, &(lightProbeCoeffs[0][0]));
+ }
+ lightProbeCoeffs[0][0] += ambientSH[0];
+ lightProbeCoeffs[0][1] += ambientSH[1];
+ lightProbeCoeffs[0][2] += ambientSH[2];
+ SetSHConstants (lightProbeCoeffs, builtinParamValues);
+
+ // set pass
+ channels = roData.material->SetPassWithShader(p, shader, ss);
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ prevPassIndex = p;
+ #endif
+
+ if (channels)
+ {
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Add (node->renderer, roData.subsetIndex, channels, node->worldMatrix, transformType);
+ #else
+ SetupObjectMatrix (node->worldMatrix, transformType);
+ node->renderer->Render (roData.subsetIndex, *channels);
+ #endif
+ }
+ }
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Flush();
+ #endif
+
+ GetGfxDevice().SetSRGBWrite(false);
+}
+
+
+PrePassRenderLoop* CreatePrePassRenderLoop()
+{
+ return new PrePassRenderLoop();
+}
+
+void DeletePrePassRenderLoop (PrePassRenderLoop* queue)
+{
+ delete queue;
+}
+
+
+static UInt32 CalculateLightingLayers ()
+{
+ // TODO: Use active lights instead
+ const LightManager::Lights& lights = GetLightManager().GetAllLights();
+ LightManager::Lights::const_iterator it, itEnd = lights.end();
+ UInt32 layers = ~0;
+ for (it = lights.begin(); it != itEnd; ++it)
+ {
+ UInt32 mask = it->GetCullingMask();
+ if (mask == 0)
+ continue;
+ layers &= mask;
+ }
+ return ~layers;
+}
+
+
+#if UNITY_EDITOR
+static void CheckLightLayerUsage (const LightingLayers& layers)
+{
+ static bool s_UsageWasOK = true;
+ bool usageIsOK = (layers.lightLayerCount <= kLightingLayerCount);
+
+ // Only log/remove warning message when broken vs. okay has changed
+ if (usageIsOK == s_UsageWasOK)
+ return;
+
+ s_UsageWasOK = usageIsOK;
+
+ // Remove any previous error
+ // Use instanceID of QualitySettings as log identifier
+ RemoveErrorWithIdentifierFromConsole (GetQualitySettings().GetInstanceID());
+
+ if (!usageIsOK)
+ {
+ std::string msg = Format(
+ "Too many layers used to exclude objects from lighting. Up to %i layers can be used to exclude lights, while your lights use %i:",
+ kLightingLayerCount,
+ layers.lightLayerCount);
+ for (int i = 0; i < LightingLayers::kLayerCount; ++i)
+ {
+ if (layers.lightingLayerMask & (1<<i))
+ {
+ std::string layerName = LayerToString (i);
+ if (layerName.empty())
+ layerName = "Unnamed " + IntToString (i);
+ msg += " '" + layerName + "'";
+ }
+ }
+ // Use instanceID of QualitySettings as log identifier
+ DebugStringToFile (msg, 0, __FILE__, __LINE__, kScriptingWarning, 0, GetQualitySettings().GetInstanceID());
+ }
+}
+#endif
+
+static void ResolveDepthIntoTextureIfNeeded (
+ GfxDevice& device,
+ RenderLoop& renderLoop,
+ DepthBufferFormat depthFormat,
+ RenderTexture*& outDepthRT,
+ TextureID* outDepthTextureID,
+ bool* outDepthWasCopied)
+{
+ // TODO FIXME!! Should add GLES20 here as well, but it's missing GfxDevice::ResolveDepthIntoTexture!
+
+#if GFX_SUPPORTS_D3D9 || GFX_SUPPORTS_D3D11 || GFX_SUPPORTS_OPENGL || GFX_SUPPORTS_OPENGLES30
+ bool needsDepthResolve = false;
+#if GFX_SUPPORTS_D3D9
+ // If doing depth tests & sampling as INTZ is very slow,
+ // do a depth resolve into a separate texture first.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererD3D9 && gGraphicsCaps.hasStencilInDepthTexture && gGraphicsCaps.d3d.hasDepthResolveRESZ && gGraphicsCaps.d3d.slowINTZSampling);
+#endif
+#if GFX_SUPPORTS_D3D11
+ // Always needs resolve on D3D11.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererD3D11);
+#endif
+#if GFX_SUPPORTS_OPENGL
+ // Needs resolve on OpenGL, unless we did the slow RenderBasePassDepth().
+ // TODO: get rid of buggyPackedDepthStencil
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererOpenGL) && !gGraphicsCaps.gl.buggyPackedDepthStencil;
+#endif
+#if GFX_SUPPORTS_OPENGLES30
+ // Always needs resolve on GLES30.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererOpenGLES30);
+#endif
+
+ if (needsDepthResolve)
+ {
+ DebugAssert (depthFormat != kDepthFormatNone);
+ RenderTexture* depthCopy = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, depthFormat, kRTFormatDepth, RenderBufferManager::kRBSampleOnlyDepth, kRTReadWriteLinear);
+ depthCopy->SetFilterMode (kTexFilterNearest);
+ if (!depthCopy->IsCreated())
+ depthCopy->Create();
+ AddRenderLoopTempBuffer (&renderLoop, depthCopy);
+
+ device.ResolveDepthIntoTexture (depthCopy->GetColorSurfaceHandle (), depthCopy->GetDepthSurfaceHandle ());
+
+ outDepthRT = depthCopy;
+ *outDepthTextureID = depthCopy->GetTextureID ();
+ *outDepthWasCopied = true;
+ }
+
+#endif
+}
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InstertToHashBufferPreLoop(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+ }
+#endif
+
+void DoPrePassRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ RenderObjectDataContainer& outRemainingObjects,
+ RenderTexture*& outDepthRT,
+ RenderTexture*& outDepthNormalsRT,
+ RenderTexture*& outMainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool* outDepthWasCopied)
+{
+ outDepthRT = NULL;
+ outDepthNormalsRT = NULL;
+ *outDepthWasCopied = false;
+
+ // Allocated on the stack each time, uses temp allocators
+ PrePassRenderLoop loop;
+ loop.m_Context = &ctx;
+ loop.m_Objects = &objects;
+
+ loop.m_PlainRenderPasses.resize_uninitialized(0);
+
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+ const VisibleNode *node = odata.visibleNode;
+ BaseRenderer* renderer = node->renderer;
+
+ PrePassRenderData rpData;
+ rpData.roIndex = roIndex;
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ UInt32 transformType = static_cast<UInt32>(renderer->GetTransformInfo().transformType);
+ hashPtr = InstertToHashBufferPreLoop(&transformType, hashPtr);
+ hashPtr = InstertToHashBufferPreLoop(&node->invScale, hashPtr);
+ hashPtr = InstertToHashBufferPreLoop(&node->lodFade, hashPtr);
+ int materialID = odata.material->GetInstanceID();
+ hashPtr = InstertToHashBufferPreLoop(&materialID, hashPtr);
+ int shaderID = odata.shader->GetInstanceID();
+ hashPtr = InstertToHashBufferPreLoop(&shaderID, hashPtr);
+ int ss = odata.shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ hashPtr = InstertToHashBufferPreLoop(&ss, hashPtr);
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InstertToHashBufferPreLoop(&odata.staticBatchIndex, hashPtr);
+ #endif
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ loop.m_PlainRenderPasses.push_back( rpData );
+ }
+
+ // Sort objects
+ {
+ PROFILER_AUTO(gPrepassSort, ctx.m_Camera);
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+ loop.SortPreRenderPassData(loop.m_PlainRenderPasses);
+#else
+ std::sort (objects.begin(), objects.end(), RenderPrePassObjectSorter());
+#endif
+ }
+
+ // Setup shadow distance, fade and ambient parameters
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+ Vector4f lightFade;
+ Vector4f fadeCenterAndType;
+ CalculateLightShadowFade (*ctx.m_Camera, 1.0f, lightFade, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecLightmapFade, lightFade);
+ params.SetVectorParam(kShaderVecShadowFadeCenterAndType, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecUnityAmbient, Vector4f(GetRenderSettings().GetAmbientLightInActiveColorSpace().GetPtr()));
+
+ GfxDevice& device = GetGfxDevice();
+
+ // Prepare for rendering
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ Assert (rtMain);
+ if (!rtMain->IsCreated())
+ rtMain->Create();
+
+ LightingLayers lightingLayers (CalculateLightingLayers ());
+ #if UNITY_EDITOR
+ CheckLightLayerUsage (lightingLayers);
+ #endif
+
+ // Don't allow shaders to set their own stencil state from base pass until
+ // the end of light pass, since it would screw them up.
+ ShaderLab::g_GlobalAllowShaderStencil = false;
+
+ // Render Geometry base pass
+ MinMaxAABB receiverBounds;
+ RenderTexture* rtNormalsSpec = loop.RenderBasePass (rtMain, lightingLayers, outRemainingObjects, receiverBounds);
+ outDepthRT = rtNormalsSpec;
+
+ RenderSurfaceHandle colorSurfaceHandle = rtNormalsSpec->GetColorSurfaceHandle();
+ RenderSurfaceHandle depthTextureHandle = rtMain->GetDepthSurfaceHandle();
+ TextureID depthTextureID = rtMain->GetSecondaryTextureID();
+ DepthBufferFormat depthFormat = rtMain->GetDepthFormat();
+
+ #if GFX_SUPPORTS_OPENGL
+ if (device.GetRenderer() == kGfxRendererOpenGL && gGraphicsCaps.gl.buggyPackedDepthStencil)
+ {
+ // Separate pass to render depth into a separate target. And then use that texture to read depth
+ // in the lighting pass.
+ RenderTexture* rtDepth = RenderBasePassDepth (ctx, objects, loop.m_PlainRenderPasses);
+ depthTextureID = rtDepth->GetTextureID();
+ outDepthRT = rtDepth;
+ colorSurfaceHandle = rtDepth->GetColorSurfaceHandle();
+ depthTextureHandle = rtDepth->GetDepthSurfaceHandle();
+ *outDepthWasCopied = true;
+ }
+ #endif
+
+ if (gGraphicsCaps.hasStencilInDepthTexture)
+ {
+ const ActiveLight* mainActiveLight = GetMainActiveLight(activeLights);
+ Light* mainLight = mainActiveLight ? mainActiveLight->light : NULL;
+ const bool mainLightHasShadows = mainLight && mainLight->GetType() == kLightDirectional && mainLight->GetShadows() != kShadowNone;
+ const bool cameraNeedsDepthTexture = (ctx.m_Camera->GetDepthTextureMode() & Camera::kDepthTexDepthBit);
+ if (mainLightHasShadows || cameraNeedsDepthTexture)
+ {
+ RenderForwardObjectsIntoDepth (
+ ctx,
+ rtMain,
+ &outRemainingObjects,
+ colorSurfaceHandle,
+ depthTextureHandle,
+ rtMain->GetWidth(),
+ rtMain->GetHeight(),
+ cameraNeedsDepthTexture
+ );
+ }
+ }
+
+ ResolveDepthIntoTextureIfNeeded (device, *(ctx.m_RenderLoop), depthFormat, outDepthRT, &depthTextureID, outDepthWasCopied);
+
+ // Render Lighting pass
+ RenderTexture* rtLight = NULL;
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec = NULL;
+#endif
+ loop.RenderLighting (activeLights,
+ rtMain,
+ depthTextureID,
+ rtNormalsSpec,
+ rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ rtLightSpec,
+#endif
+ lightFade,
+ lightingLayers,
+ receiverBounds,
+ &outMainShadowMap);
+
+ // It's again ok for shaders to set their stencil state now.
+ ShaderLab::g_GlobalAllowShaderStencil = true;
+
+ if (ctx.m_Camera->GetClearStencilAfterLightingPass())
+ {
+ float black[] = {0,0,0,0};
+ device.Clear (kGfxClearStencil, black, 1.0f, 0);
+ }
+
+ // Render final Geometry pass
+ loop.RenderFinalPass (rtMain,
+ rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ rtLightSpec,
+#endif
+ ctx.m_Camera->GetUsingHDR(),
+ linearLighting);
+
+ if (rtLight)
+ {
+ // Do not release the light buffer yet; so that image effects or whatever can access it later
+ // if needed (via _LightBuffer)
+ device.SetSurfaceFlags(rtLight->GetColorSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ device.SetSurfaceFlags(rtLight->GetDepthSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rtLight);
+ }
+
+#if SEPERATE_PREPASS_SPECULAR
+ if (rtLightSpec)
+ {
+ device.SetSurfaceFlags(rtLightSpec->GetColorSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ device.SetSurfaceFlags(rtLightSpec->GetDepthSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rtLightSpec);
+ }
+#endif
+
+ // Combine depth+normals if needed
+ if (ctx.m_Camera->GetDepthTextureMode() & Camera::kDepthTexDepthNormalsBit)
+ {
+ outDepthNormalsRT = CombineDepthNormalsTexture (ctx, outRemainingObjects);
+ RenderTexture::SetActive (rtMain);
+ }
+
+ device.SetViewMatrix( ctx.m_CurCameraMatrix.GetPtr() );
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+}
+
+#endif // GFX_SUPPORTS_RENDERLOOP_PREPASS
diff --git a/Runtime/Camera/RenderLoops/RenderLoop.h b/Runtime/Camera/RenderLoops/RenderLoop.h
new file mode 100644
index 0000000..8c54ed8
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoop.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "RenderLoopEnums.h"
+
+class Shader;
+struct RenderLoop;
+class Camera;
+class ImageFilters;
+class RenderTexture;
+struct ShadowCullData;
+struct CullResults;
+
+
+RenderLoop* CreateRenderLoop (Camera& camera);
+void DeleteRenderLoop (RenderLoop* loop);
+void DoRenderLoop (
+ RenderLoop& loop,
+ RenderingPath renderPath,
+ CullResults& contents,
+ // used in the editor for material previews - those should not render projectors, halos etc.
+ bool dontRenderRenderables
+);
+void CleanupAfterRenderLoop (RenderLoop& loop);
+ImageFilters& GetRenderLoopImageFilters (RenderLoop& loop);
+void RenderImageFilters (RenderLoop& loop, RenderTexture* targetTexture, bool afterOpaque);
diff --git a/Runtime/Camera/RenderLoops/RenderLoopEnums.h b/Runtime/Camera/RenderLoops/RenderLoopEnums.h
new file mode 100644
index 0000000..e6ec07e
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopEnums.h
@@ -0,0 +1,29 @@
+#pragma once
+
+enum RenderingPath {
+ kRenderPathVertex = 0,
+ kRenderPathForward,
+ kRenderPathPrePass,
+ kRenderPathCount
+};
+
+enum OcclusionQueryType {
+ kOcclusionQueryTypeMostAccurate = 0,
+ kOcclusionQueryTypeFastest,
+ kOcclusionQueryTypeCount
+};
+
+enum
+{
+ kBackgroundRenderQueue = 1000,
+ kGeometryRenderQueue = 2000,
+ kAlphaTestRenderQueue = 2450, // we want it to be in the end of geometry queue
+ kTransparentRenderQueue = 3000,
+ kOverlayRenderQueue = 4000,
+
+ kQueueIndexMin = 0,
+ kQueueIndexMax = 5000,
+
+ kGeometryQueueIndexMin = kGeometryRenderQueue-500,
+ kGeometryQueueIndexMax = kGeometryRenderQueue+500,
+};
diff --git a/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp b/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp
new file mode 100644
index 0000000..823c8fa
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp
@@ -0,0 +1,469 @@
+#include "UnityPrefix.h"
+#include "RenderLoopPrivate.h"
+#include "RenderLoop.h"
+#include "Runtime/Camera/UnityScene.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/ShadowCulling.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/ImageFilters.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Shaders/Shader.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "ReplacementRenderLoop.h"
+
+#if UNITY_EDITOR
+ #include "Editor/Src/EditorUserBuildSettings.h"
+#endif
+
+PROFILER_INFORMATION(gRenderPrepareObjects, "Render.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gRenderOpaque, "Render.OpaqueGeometry", kProfilerRender)
+PROFILER_INFORMATION(gRenderTransparent, "Render.TransparentGeometry", kProfilerRender)
+PROFILER_INFORMATION(gPrePassFwdDepthTex, "RenderPrePass.FwdObjectsIntoDepth", kProfilerRender)
+PROFILER_INFORMATION(gPrePassFwdDepthNormalsTex, "RenderPrePass.FwdObjectsIntoDepthNormals", kProfilerRender)
+PROFILER_INFORMATION(gCameraResolveProfile, "Camera.AAResolve", kProfilerRender)
+
+
+namespace ShaderLab { void ClearGrabPassFrameState (); } // pass.cpp
+
+
+struct RenderLoop {
+public:
+ RenderLoop (Camera& camera);
+ ~RenderLoop ();
+
+ void PrepareFrame (bool dontRenderRenderables, bool renderingShaderReplace);
+
+public:
+ RenderLoopContext m_Context;
+ ShadowCullData m_ShadowCullData;
+ RenderObjectDataContainer m_Objects[kPartCount];
+ ImageFilters m_ImageFilters;
+
+ enum { kMaxCreatedTempBuffers = 8 };
+ RenderTexture* m_TempBuffers[kMaxCreatedTempBuffers];
+ int m_TempBufferCount;
+};
+
+
+RenderLoop* CreateRenderLoop (Camera& camera)
+{
+ return new RenderLoop(camera);
+}
+
+void DeleteRenderLoop (RenderLoop* loop)
+{
+ delete loop;
+}
+
+ImageFilters& GetRenderLoopImageFilters (RenderLoop& loop)
+{
+ return loop.m_ImageFilters;
+}
+
+
+RenderLoop::RenderLoop(Camera& camera)
+{
+ m_Context.m_Camera = &camera;
+ m_Context.m_RenderLoop = this;
+
+ for (int i = 0; i < kMaxCreatedTempBuffers; ++i) {
+ m_TempBuffers[i] = NULL;
+ }
+ m_TempBufferCount = 0;
+}
+
+RenderLoop::~RenderLoop()
+{
+ Assert (m_TempBufferCount == 0);
+}
+
+inline float MultiplyPointZ (const Matrix4x4f& m, const Vector3f& v)
+{
+ return m.m_Data[2] * v.x + m.m_Data[6] * v.y + m.m_Data[10] * v.z + m.m_Data[14];
+}
+
+// Both distances become smaller (more negative) when moving forward from the camera.
+// outDistanceForSort is for sorting only, and it can be square of the actual distance, and so on.
+// outDistnaceAlongView is projection of the center along camera's view.
+static void EvaluateObjectDepth (const RenderLoopContext& ctx, const TransformInfo& info, float& outDistanceForSort, float& outDistanceAlongView)
+{
+ Vector3f center = info.worldAABB.GetCenter();
+ if (ctx.m_SortOrthographic)
+ {
+ const float d = MultiplyPointZ (ctx.m_CurCameraMatrix, center);
+ outDistanceForSort = d;
+ outDistanceAlongView = d;
+ }
+ else
+ {
+ outDistanceAlongView = MultiplyPointZ (ctx.m_CurCameraMatrix, center);
+ center -= ctx.m_CurCameraPos;
+ outDistanceForSort = -SqrMagnitude(center);
+ }
+
+ // A distance of NaN can cause inconsistent sorting results, if input order is inconsistent.
+ Assert(IsFinite(outDistanceForSort));
+ Assert(IsFinite(outDistanceAlongView));
+}
+
+
+void RenderLoop::PrepareFrame (bool dontRenderRenderables, bool renderingShaderReplace)
+{
+ Camera& camera = *m_Context.m_Camera;
+ m_Context.m_CurCameraMatrix = camera.GetWorldToCameraMatrix();
+ m_Context.m_CurCameraPos = camera.GetComponent(Transform).GetPosition();
+ m_Context.m_CameraViewport = camera.GetRenderRectangle();
+ switch (camera.GetSortMode())
+ {
+ case Camera::kSortPerspective: m_Context.m_SortOrthographic = false; break;
+ case Camera::kSortOrthographic: m_Context.m_SortOrthographic = true; break;
+ default: m_Context.m_SortOrthographic = camera.GetOrthographic(); break;
+ }
+ m_Context.m_DontRenderRenderables = dontRenderRenderables;
+ m_Context.m_RenderingShaderReplace = renderingShaderReplace;
+
+ for (int i = 0; i < kPartCount; ++i)
+ m_Objects[i].resize_uninitialized(0);
+
+ #if DEBUGMODE
+ for (int i = 0; i < kMaxCreatedTempBuffers; ++i) {
+ Assert (m_TempBuffers[i] == NULL);
+ }
+ #endif
+ m_TempBufferCount = 0;
+}
+
+
+static RenderTexture* ResolveScreenToTextureIfNeeded (RenderLoop& loop, bool forceIntoRT, bool beforeOpaqueImageFx)
+{
+ // If we use screen to composite image effects, resolve screen into the render texture now
+ bool usingScreenToComposite = loop.m_ImageFilters.HasImageFilter() && loop.m_Context.m_Camera->GetUsesScreenForCompositing(forceIntoRT);
+ RenderTexture* rt = NULL;
+ if (usingScreenToComposite)
+ {
+ // Do a screen to RT resolve here.
+ rt = beforeOpaqueImageFx ? loop.m_ImageFilters.GetTargetBeforeOpaque () : loop.m_ImageFilters.GetTargetAfterOpaque (forceIntoRT, usingScreenToComposite);
+ if (!rt)
+ return NULL;
+
+ PROFILER_AUTO_GFX(gCameraResolveProfile, loop.m_Context.m_Camera)
+ GPU_AUTO_SECTION(kGPUSectionPostProcess);
+
+ // We should insert proper discard/clear/... on backbuffer when doing MSAA
+ // resolved off it. However that's for the future (case 549705),
+ // for now just silence the RT unresolve warning.
+ GetGfxDevice().IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ Rectf r = loop.m_Context.m_Camera->GetPhysicalViewportRect();
+ int rect[4];
+ RectfToViewport( r, rect );
+ Assert (rect[2] == rt->GetGLWidth() && rect[3] == rt->GetGLHeight());
+ rt->GrabPixels (rect[0], rect[1], rect[2], rect[3]);
+
+ // D3D and GL use different notions of how Y texture coordinates go.
+ // In effect, we have to flip any sampling from the first texture in the image filters
+ // stack on D3D.
+ rt->CorrectVerticalTexelSize(false);
+ }
+
+ return rt;
+}
+
+
+void RenderImageFilters (RenderLoop& loop, RenderTexture* targetTexture, bool afterOpaque)
+{
+ bool forceIntoRT = loop.m_Context.m_Camera->CalculateNeedsToRenderIntoRT();
+ ResolveScreenToTextureIfNeeded (loop, forceIntoRT, afterOpaque);
+ bool usingScreenToComposite = loop.m_ImageFilters.HasImageFilter() && loop.m_Context.m_Camera->GetUsesScreenForCompositing(forceIntoRT);
+ loop.m_ImageFilters.DoRender (targetTexture, forceIntoRT, afterOpaque, usingScreenToComposite, loop.m_Context.m_Camera->GetUsingHDR());
+ if (afterOpaque && !usingScreenToComposite)
+ loop.m_Context.m_Camera->SetCurrentTargetTexture (loop.m_ImageFilters.GetTargetAfterOpaque(forceIntoRT,usingScreenToComposite));
+}
+
+
+static void UpdateCameraDepthTextures (Camera& camera, RenderTexture* rtDepth, RenderTexture* rtDepthNormals, RenderObjectDataContainer& objects, bool depthWasCopied, bool skipDepthTexture, bool afterOpaque)
+{
+ if (!rtDepth || objects.size() == 0)
+ return;
+
+ // use depth buffer from final target
+ RenderTexture* rtFinal = camera.GetCurrentTargetTexture();
+ Assert (rtFinal);
+ RenderSurfaceHandle rtSurfaceDepth = rtFinal->GetDepthSurfaceHandle();
+
+ int renderFlags = Camera::kRenderFlagSetRenderTarget;
+ if (!afterOpaque)
+ renderFlags |= Camera::kRenderFlagSetRenderTargetFinal;
+
+ if (!skipDepthTexture && gGraphicsCaps.hasStencilInDepthTexture && (camera.GetDepthTextureMode() & Camera::kDepthTexDepthBit))
+ {
+ Shader* shader = GetCameraDepthTextureShader ();
+ if (shader)
+ {
+ PROFILER_AUTO_GFX(gPrePassFwdDepthTex, &camera);
+ // If we did separate pass or depth resolve in deferred to work around depth+stencil texture bugs,
+ // render into the copy in that case.
+ if (depthWasCopied)
+ {
+ RenderTexture::SetActive (rtDepth);
+ }
+ else
+ {
+ RenderSurfaceHandle rtSurfaceColor = rtDepth->GetColorSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtDepth);
+ }
+
+ RenderSceneShaderReplacement (objects, shader, "RenderType");
+ camera.SetupRender (renderFlags);
+ }
+ }
+
+ if (rtDepthNormals && (camera.GetDepthTextureMode() & Camera::kDepthTexDepthNormalsBit))
+ {
+ Shader* shader = GetCameraDepthNormalsTextureShader ();
+ if (shader)
+ {
+ PROFILER_AUTO_GFX(gPrePassFwdDepthNormalsTex, &camera);
+ RenderSurfaceHandle rtSurfaceColor = rtDepthNormals->GetColorSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtDepthNormals);
+
+ RenderSceneShaderReplacement (objects, shader, "RenderType");
+ camera.SetupRender (renderFlags);
+ }
+ }
+}
+
+bool gInsideRenderLoop = false;
+void StartRenderLoop()
+{
+ Assert (!gInsideRenderLoop);
+ gInsideRenderLoop = true;
+}
+void EndRenderLoop()
+{
+ Assert (gInsideRenderLoop);
+ gInsideRenderLoop = false;
+}
+bool IsInsideRenderLoop()
+{
+ return gInsideRenderLoop;
+}
+
+void DoRenderLoop (
+ RenderLoop& loop,
+ RenderingPath renderPath,
+ CullResults& contents,
+ bool dontRenderRenderables)
+{
+ Assert (loop.m_TempBufferCount == 0);
+ Assert (contents.shadowCullData);
+
+ loop.m_Context.m_ShadowCullData = contents.shadowCullData;
+ loop.m_Context.m_CullResults = &contents;
+
+ // save wireframe state, restore at exit
+ SetAndRestoreWireframeMode saveAndRestoreWireframe;
+
+ const bool licenseAllowsStaticBatching = GetBuildSettings().hasAdvancedVersion;
+ Camera& camera = *loop.m_Context.m_Camera;
+
+ Shader* replacementShader = contents.shaderReplaceData.replacementShader;
+ const bool replacementTagSet = contents.shaderReplaceData.replacementTagSet;
+ const int replacementTagID = contents.shaderReplaceData.replacementTagID;
+
+
+ {
+ PROFILER_AUTO(gRenderPrepareObjects, &camera);
+
+ loop.PrepareFrame (dontRenderRenderables, replacementShader);
+
+ const bool useOldRenderQueueLogic = !IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_2_a1);
+
+ // sort out objects into opaque & alpha parts
+ VisibleNodes::iterator itEnd = contents.nodes.end();
+ for (VisibleNodes::iterator it = contents.nodes.begin(); it != itEnd; ++it)
+ {
+ if (!IsFinite(it->invScale))
+ continue;
+
+ BaseRenderer* renderer = it->renderer;
+
+ float distanceForSort, distanceAlongView;
+ EvaluateObjectDepth (loop.m_Context, *it, distanceForSort, distanceAlongView);
+ distanceForSort -= renderer->GetSortingFudge ();
+ distanceAlongView = -distanceAlongView; // make that so increases with distance
+
+ const int matCount = renderer->GetMaterialCount();
+ const int batchIndex = (licenseAllowsStaticBatching)? renderer->GetStaticBatchIndex(): 0;
+ const UInt16 lightmapIndex = renderer->GetLightmapIndex();
+
+ for (int mi = 0; mi < matCount; ++mi)
+ {
+ Material* mat = renderer->GetMaterial (mi);
+ if( mat == NULL )
+ mat = Material::GetDefault();
+ Shader* shader = mat->GetShader();
+
+ int usedSubshaderIndex = -1;
+ if (replacementShader)
+ {
+ if (replacementTagSet)
+ {
+ int subshaderTypeID = shader->GetShaderLabShader()->GetTag (replacementTagID, true);
+ if (subshaderTypeID < 0)
+ continue; // skip rendering
+ usedSubshaderIndex = replacementShader->GetSubShaderWithTagValue (replacementTagID, subshaderTypeID);
+ if (usedSubshaderIndex == -1)
+ continue; // skip rendering
+ }
+ else
+ {
+ usedSubshaderIndex = 0;
+ }
+ }
+
+ const int matIndex = renderer->GetSubsetIndex(mi);
+
+ // Figure out rendering queue to use
+ int queueIndex = mat->GetCustomRenderQueue(); // any per-material overriden queue takes priority
+ if (queueIndex < 0)
+ {
+ // When no shader replacement or old content, take queue from the shader
+ if (!replacementShader || useOldRenderQueueLogic)
+ {
+ queueIndex = shader->GetShaderLabShader()->GetRenderQueue();
+ }
+ // Otherwise take from replacement shader
+ else
+ {
+ queueIndex = replacementShader->GetShaderLabShader()->GetRenderQueue(usedSubshaderIndex);
+ }
+ }
+
+ RenderPart part;
+ if (queueIndex <= kGeometryQueueIndexMax)
+ part = kPartOpaque;
+ else
+ part = kPartAfterOpaque;
+
+ RenderObjectData& odata = loop.m_Objects[part].push_back ();
+ DebugAssertIf (!mat);
+ odata.material = mat;
+ odata.queueIndex = queueIndex;
+ odata.subsetIndex = matIndex;
+ odata.subShaderIndex = usedSubshaderIndex;
+ odata.sourceMaterialIndex = (UInt16)mi;
+ odata.lightmapIndex = lightmapIndex;
+ odata.staticBatchIndex = batchIndex;
+ odata.distance = distanceForSort;
+ odata.distanceAlongView = distanceAlongView;
+ odata.visibleNode = &*it;
+ odata.shader = replacementShader ? replacementShader : shader;
+ odata.globalLayeringData = renderer->GetGlobalLayeringData();
+ }
+ }
+ }
+
+ // want linear lighting?
+ bool linearLighting = GetActiveColorSpace() == kLinearColorSpace;
+
+ // opaque: deferred or forward
+ RenderTexture *rtDepth = NULL, *rtDepthNormals = NULL;
+ bool prepassDepthWasCopied = false;
+ {
+ PROFILER_AUTO_GFX(gRenderOpaque, &camera);
+
+ loop.m_Context.m_RenderQueueStart = kQueueIndexMin; loop.m_Context.m_RenderQueueEnd = kGeometryQueueIndexMax+1;
+ if (renderPath == kRenderPathPrePass)
+ {
+ #if GFX_SUPPORTS_RENDERLOOP_PREPASS
+ RenderTexture *rtShadowMap = NULL;
+ RenderObjectDataContainer remainingObjects;
+ DoPrePassRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], remainingObjects, rtDepth, rtDepthNormals, rtShadowMap, contents.activeLights, linearLighting, &prepassDepthWasCopied);
+ if (remainingObjects.size() != 0)
+ {
+ // Objects/shaders that don't handle deferred: render with forward path, and pass main shadowmap to it
+ // Also disable dynamic batching of those objects. They are already rendered into
+ // the depth buffer, and dynamic batching would make them be rendered at slightly
+ // different positions, failing depth test at places.
+ DoForwardShaderRenderLoop (loop.m_Context, remainingObjects, true, true, rtShadowMap, contents.activeLights, linearLighting, false);
+
+ UpdateCameraDepthTextures (camera, rtDepth, rtDepthNormals, remainingObjects, prepassDepthWasCopied, true, true);
+ }
+ #else
+ ErrorString ("Pre-pass rendering loop should never happen on this platform!");
+ #endif
+ }
+ else if (renderPath == kRenderPathForward)
+ {
+ DoForwardShaderRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], true, false, NULL, contents.activeLights, linearLighting, true);
+ }
+ else
+ {
+ DoForwardVertexRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], true, contents.activeLights, linearLighting, true);
+ }
+ }
+
+ // render skybox after opaque (sRGB conversions needed if using linear rendering)
+ {
+ GetGfxDevice().SetSRGBWrite(linearLighting);
+ camera.RenderSkybox();
+ GetGfxDevice().SetSRGBWrite(false);
+ }
+
+ RenderImageFilters (loop, camera.GetTargetTexture(), true);
+
+ // after opaque: forward
+ {
+ PROFILER_AUTO_GFX(gRenderTransparent, &camera);
+
+ loop.m_Context.m_RenderQueueStart = kGeometryQueueIndexMax+1; loop.m_Context.m_RenderQueueEnd = kQueueIndexMax;
+ if (renderPath != kRenderPathVertex)
+ {
+ DoForwardShaderRenderLoop (loop.m_Context, loop.m_Objects[kPartAfterOpaque], false, false, NULL, contents.activeLights, linearLighting, false);
+ }
+ else
+ {
+ DoForwardVertexRenderLoop (loop.m_Context, loop.m_Objects[kPartAfterOpaque], false, contents.activeLights, linearLighting, false);
+ }
+
+ UpdateCameraDepthTextures (camera, rtDepth, rtDepthNormals, loop.m_Objects[kPartAfterOpaque], prepassDepthWasCopied, false, false);
+ }
+
+ loop.m_Context.m_ShadowCullData = NULL;
+ loop.m_Context.m_CullResults = NULL;
+}
+
+void CleanupAfterRenderLoop (RenderLoop& loop)
+{
+ Assert (loop.m_TempBufferCount >= 0 && loop.m_TempBufferCount < RenderLoop::kMaxCreatedTempBuffers);
+ RenderBufferManager& rbm = GetRenderBufferManager();
+ for (int i = 0; i < loop.m_TempBufferCount; ++i) {
+ Assert (loop.m_TempBuffers[i]);
+ rbm.ReleaseTempBuffer (loop.m_TempBuffers[i]);
+ loop.m_TempBuffers[i] = NULL;
+ }
+ loop.m_TempBufferCount = 0;
+ ShaderLab::ClearGrabPassFrameState();
+}
+
+void AddRenderLoopTempBuffer (RenderLoop* loop, RenderTexture* rt)
+{
+ Assert (loop && rt);
+ Assert (loop->m_TempBufferCount < RenderLoop::kMaxCreatedTempBuffers);
+
+ loop->m_TempBuffers[loop->m_TempBufferCount++] = rt;
+}
diff --git a/Runtime/Camera/RenderLoops/RenderLoopPrivate.h b/Runtime/Camera/RenderLoops/RenderLoopPrivate.h
new file mode 100644
index 0000000..c64927c
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopPrivate.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Camera/CullResults.h"
+#include "GlobalLayeringData.h"
+
+namespace Unity { class Material; }
+class Camera;
+class Shader;
+class RenderTexture;
+struct ShadowCullData;
+struct RenderLoop;
+
+struct RenderObjectData {
+ Unity::Material* material; // 4
+ SInt16 queueIndex; // 2
+ UInt16 subsetIndex; // 2
+ SInt16 subShaderIndex; // 2
+ UInt16 sourceMaterialIndex;// 2
+ UInt16 lightmapIndex; // 2
+ int staticBatchIndex; // 4
+ float distance; // 4
+
+ //@TODO: cold?
+ float distanceAlongView; // 4
+ VisibleNode* visibleNode; // 4
+ Shader* shader; // 4 shader to use
+ GlobalLayeringData
+ globalLayeringData; // 4
+ // 36 bytes
+};
+
+enum RenderPart { kPartOpaque, kPartAfterOpaque, kPartCount };
+
+typedef dynamic_array<RenderObjectData> RenderObjectDataContainer;
+
+struct RenderLoopContext
+{
+ Camera* m_Camera;
+
+ const CullResults* m_CullResults;
+ const ShadowCullData* m_ShadowCullData;
+ Matrix4x4f m_CurCameraMatrix;
+ Rectf m_CameraViewport;
+ Vector3f m_CurCameraPos;
+ bool m_SortOrthographic;
+ bool m_DontRenderRenderables;
+ bool m_RenderingShaderReplace;
+
+ int m_RenderQueueStart;
+ int m_RenderQueueEnd;
+
+ RenderLoop* m_RenderLoop;
+};
+
+void AddRenderLoopTempBuffer (RenderLoop* loop, RenderTexture* rt);
+
+void DoForwardVertexRenderLoop (RenderLoopContext& ctx, RenderObjectDataContainer& objects, bool opaque, ActiveLights& activeLights, bool linearLighting, bool clearFrameBuffer);
+void DoForwardShaderRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ bool opaque,
+ bool disableDynamicBatching,
+ RenderTexture* mainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool clearFrameBuffer);
+
+void DoPrePassRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ RenderObjectDataContainer& outRemainingObjects,
+ RenderTexture*& outDepthRT,
+ RenderTexture*& outDepthNormalsRT,
+ RenderTexture*& outMainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool* outDepthWasCopied);
+
+// This is only usable by GfxDeviceGLES, because GfxDeviceGLES only supports ForwardVertexRenderLoop, you'll only see these functions there
+// If IsInsideRenderLoop() == true, no state caching will be performed by GfxDeviceGLES
+void StartRenderLoop();
+void EndRenderLoop();
+bool IsInsideRenderLoop();
diff --git a/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp
new file mode 100644
index 0000000..fcba391
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp
@@ -0,0 +1,245 @@
+#include "UnityPrefix.h"
+#include "ReplacementRenderLoop.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/Camera.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/UnityScene.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+
+struct RODataReplacement {
+ float distance;
+ int subshaderIndex;
+ Material* material;
+ const VisibleNode* visibleNode;
+ Shader* shader;
+ int materialIndex;
+ GlobalLayeringData globalLayeringData;
+};
+
+typedef UNITY_TEMP_VECTOR(RODataReplacement) RenderObjects;
+
+struct ROSorterReplacement {
+ bool operator()( const RODataReplacement& ra, const RODataReplacement& rb ) const;
+};
+
+
+bool ROSorterReplacement::operator()( const RODataReplacement& ra, const RODataReplacement& rb ) const
+{
+ // Sort by layering depth. //@TODO:should this be here?
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by subshader index used
+ if (ra.subshaderIndex != rb.subshaderIndex)
+ return ra.subshaderIndex < rb.subshaderIndex;
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+
+static inline float EvaluateObjectDepth (const Matrix4x4f& cameraMatrix, const TransformInfo& info)
+{
+ Vector3f center = info.worldAABB.GetCenter();
+ float d = cameraMatrix.MultiplyPoint3( center ).z;
+ Assert(IsFinite(d));
+ return d;
+}
+
+
+static void PerformRenderingReplacement (Camera& camera, const Matrix4x4f& curCameraMatrix, RenderObjects& renderData)
+{
+ // Sort
+ std::sort (renderData.begin(), renderData.end(), ROSorterReplacement());
+
+
+ GfxDevice& device = GetGfxDevice();
+ size_t ndata = renderData.size();
+ device.SetViewMatrix (curCameraMatrix.GetPtr());
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const RODataReplacement& roData = renderData[i];
+
+ const VisibleNode* node = roData.visibleNode;
+ Assert (node);
+ BaseRenderer* renderer = node->renderer;
+ Assert (renderer);
+ Shader* shader = roData.shader;
+
+ device.SetInverseScale(1.0f);
+
+ //@TODO: if this returns true and we have any sort of batching, we'd have to break batches here
+ renderer->ApplyCustomProperties(*roData.material, shader, roData.subshaderIndex);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (roData.subshaderIndex);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ const ChannelAssigns* channels = roData.material->SetPassWithShader(p, shader, roData.subshaderIndex);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ renderer->Render( renderer->GetSubsetIndex(roData.materialIndex), *channels );
+ }
+ }
+ }
+}
+
+static void AddReplacementObject (
+ RenderObjects& renderObjects,
+ Material* mat,
+ Shader* replacementShader,
+ bool noReplacementTag,
+ int replacementTagID,
+ const VisibleNode* visibleNode,
+ float distanceForSort,
+ int materialIndex,
+ GlobalLayeringData globalLayeringData
+
+ )
+{
+ if( mat == NULL )
+ mat = Material::GetDefault();
+ Shader *shader = mat->GetShader();
+
+ // Note: do not check whether object is in geometry queue range,
+ // let shader replacement handle that. E.g. terrain billboard shaders are actually
+ // beyond geometry queue, but still can output meaningful depth/normals information.
+
+ // Handle shader replacement
+ // Given a replacement shader and tag name:
+ // 1. if tag name is empty, then all objects are just rendered with replacement shader's first subshader
+ // 2. if tag name is given:
+ // * real object's subshader is queried for tag value.
+ // * if it does not have that tag, the object is not rendered.
+ // * subshader is found in the replacement shader, that has given tag with the given value. If no subshader found, object is not rendered.
+ // * that subshader is used instead to render the object.
+ int usedSubshaderIndex;
+ if (noReplacementTag)
+ {
+ usedSubshaderIndex = 0;
+ }
+ else
+ {
+ int subshaderTypeID = shader->GetShaderLabShader()->GetTag (replacementTagID, true);
+ if (subshaderTypeID < 0)
+ return; // skip rendering
+ usedSubshaderIndex = replacementShader->GetSubShaderWithTagValue (replacementTagID, subshaderTypeID);
+ if (usedSubshaderIndex == -1)
+ return; // skip rendering
+ }
+
+ renderObjects.push_back(RODataReplacement());
+ RODataReplacement& roData = renderObjects.back();
+ roData.visibleNode = visibleNode;
+ roData.distance = distanceForSort;
+
+ DebugAssertIf( !mat );
+ roData.material = mat;
+ roData.materialIndex = materialIndex;
+
+ roData.shader = replacementShader;
+ roData.subshaderIndex = usedSubshaderIndex;
+
+ roData.globalLayeringData = globalLayeringData;
+}
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, Shader* shader, const std::string& shaderReplaceTag)
+{
+ ShaderReplaceData replaceData;
+ replaceData.replacementShader = shader;
+ replaceData.replacementTagSet = !shaderReplaceTag.empty();
+ replaceData.replacementTagID = ShaderLab::GetShaderTagID(shaderReplaceTag);
+
+ RenderSceneShaderReplacement(contents, replaceData);
+}
+
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, const ShaderReplaceData& shaderReplace)
+{
+ Assert (shaderReplace.replacementShader != NULL);
+
+ const bool noReplacementTag = !shaderReplace.replacementTagSet;
+ const int replacementTagID = shaderReplace.replacementTagID;
+ Shader* replacementShader = shaderReplace.replacementShader;
+ Camera& camera = GetRenderManager().GetCurrentCamera();
+ Matrix4x4f curCameraMatrix = camera.GetWorldToCameraMatrix();
+
+ RenderObjects renderObjects;
+ renderObjects.reserve (contents.size()/4);
+
+ // Go over the objects
+ for( VisibleNodes::const_iterator i = contents.begin(); i != contents.end(); ++i )
+ {
+ float distanceForSort = EvaluateObjectDepth (curCameraMatrix, *i);
+
+ const BaseRenderer* renderer = i->renderer;
+
+ int matCount = renderer->GetMaterialCount();
+ for (int mi = 0; mi < matCount; ++mi)
+ {
+ Material* mat = renderer->GetMaterial(mi);
+ AddReplacementObject (
+ renderObjects,
+ mat,
+ replacementShader,
+ noReplacementTag,
+ replacementTagID,
+ &*i,
+ distanceForSort,
+ mi,
+ renderer->GetGlobalLayeringData()
+ );
+ }
+ }
+
+ // Render
+ PerformRenderingReplacement (camera, curCameraMatrix, renderObjects);
+}
+
+void RenderSceneShaderReplacement (const RenderObjectDataContainer& contents, Shader* replacementShader, const std::string& replacementTag)
+{
+ Assert (replacementShader);
+
+ const bool noReplacementTag = replacementTag.empty();
+ const int replacementTagID = ShaderLab::GetShaderTagID(replacementTag);
+
+ Camera& camera = GetRenderManager().GetCurrentCamera();
+ Matrix4x4f curCameraMatrix = camera.GetWorldToCameraMatrix();
+
+ RenderObjects renderObjects;
+ renderObjects.reserve (contents.size()/4);
+
+ // Go over the objects
+ for (RenderObjectDataContainer::const_iterator i = contents.begin(); i != contents.end(); ++i)
+ {
+ const RenderObjectData& ro = *i;
+ const BaseRenderer* renderer = ro.visibleNode->renderer;
+ Assert (renderer);
+ Material* mat = renderer->GetMaterial(ro.sourceMaterialIndex);
+ AddReplacementObject (
+ renderObjects,
+ mat,
+ replacementShader,
+ noReplacementTag,
+ replacementTagID,
+ ro.visibleNode,
+ ro.distance,
+ ro.sourceMaterialIndex,
+ renderer->GetGlobalLayeringData()
+ );
+ }
+
+ // Render
+ PerformRenderingReplacement (camera, curCameraMatrix, renderObjects);
+}
+
diff --git a/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h
new file mode 100644
index 0000000..0adce81
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "Runtime/Camera/CullResults.h"
+#include "RenderLoopPrivate.h"
+#include <string>
+
+class Shader;
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, const ShaderReplaceData& shaderReplace);
+void RenderSceneShaderReplacement (const VisibleNodes& contents, Shader* shader, const std::string& shaderReplaceTag);
+void RenderSceneShaderReplacement (const RenderObjectDataContainer& contents, Shader* shader, const std::string& shaderReplaceTag);