Przeglądaj źródła

Post process effects moved to the new node based architecture

BearishSun 8 lat temu
rodzic
commit
f6519d94f6

+ 23 - 60
Source/RenderBeast/Include/BsPostProcessing.h

@@ -46,13 +46,10 @@ namespace bs { namespace ct
 		DownsampleMat();
 
 		/** Renders the post-process effect with the provided parameters. */
-		void execute(const SPtr<Texture>& target, PostProcessInfo& ppInfo);
+		void execute(const SPtr<Texture>& input, const SPtr<RenderTarget>& output);
 
-		/** Releases the output render target. */
-		void release(PostProcessInfo& ppInfo);
-
-		/** Returns the render texture where the output will be written. */
-		SPtr<RenderTexture> getOutput() const { return mOutput; }
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc(const SPtr<Texture>& target);
 
 		/** Returns the downsample material variation matching the provided parameters. */
 		static DownsampleMat* getVariation(UINT32 quality, bool msaa);
@@ -61,9 +58,6 @@ namespace bs { namespace ct
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 		GpuParamTexture mInputTexture;
 
-		POOLED_RENDER_TEXTURE_DESC mOutputDesc;
-		SPtr<RenderTexture> mOutput;
-
 		static ShaderVariation VAR_LowQuality_NoMSAA;
 		static ShaderVariation VAR_LowQuality_MSAA;
 		static ShaderVariation VAR_HighQuality_NoMSAA;
@@ -87,22 +81,19 @@ namespace bs { namespace ct
 		EyeAdaptHistogramMat();
 
 		/** Executes the post-process effect with the provided parameters. */
-		void execute(PostProcessInfo& ppInfo);
-
-		/** Releases the output render target. */
-		void release(PostProcessInfo& ppInfo);
+		void execute(const SPtr<Texture>& input, const SPtr<Texture>& output, const AutoExposureSettings& settings);
 
-		/** Returns the render texture where the output was written. */
-		SPtr<RenderTexture> getOutput() const { return mOutput; }
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc(const SPtr<Texture>& target);
 
-		/** Calculates the number of thread groups that need to execute to cover the provided render target. */
-		static Vector2I getThreadGroupCount(const SPtr<RenderTexture>& target);
+		/** Calculates the number of thread groups that need to execute to cover the provided texture. */
+		static Vector2I getThreadGroupCount(const SPtr<Texture>& target);
 
 		/** 
 		 * Returns a vector containing scale and offset (in that order) that will be applied to luminance values
 		 * to determine their position in the histogram. 
 		 */
-		static Vector2 getHistogramScaleOffset(const PostProcessInfo& ppInfo);
+		static Vector2 getHistogramScaleOffset(const AutoExposureSettings& settings);
 
 		static const UINT32 THREAD_GROUP_SIZE_X = 8;
 		static const UINT32 THREAD_GROUP_SIZE_Y = 8;
@@ -113,9 +104,6 @@ namespace bs { namespace ct
 		GpuParamTexture mSceneColor;
 		GpuParamLoadStoreTexture mOutputTex;
 
-		POOLED_RENDER_TEXTURE_DESC mOutputDesc;
-		SPtr<RenderTexture> mOutput;
-
 		static const UINT32 LOOP_COUNT_X = 8;
 		static const UINT32 LOOP_COUNT_Y = 8;
 	};
@@ -135,21 +123,16 @@ namespace bs { namespace ct
 		EyeAdaptHistogramReduceMat();
 
 		/** Executes the post-process effect with the provided parameters. */
-		void execute(PostProcessInfo& ppInfo);
-
-		/** Releases the output render target. */
-		void release(PostProcessInfo& ppInfo);
+		void execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& histogram, const SPtr<Texture>& prevFrame,
+			const SPtr<RenderTarget>& output);
 
-		/** Returns the render texture where the output was written. */
-		SPtr<RenderTexture> getOutput() const { return mOutput; }
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
 	private:
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 
 		GpuParamTexture mHistogramTex;
 		GpuParamTexture mEyeAdaptationTex;
-
-		POOLED_RENDER_TEXTURE_DESC mOutputDesc;
-		SPtr<RenderTexture> mOutput;
 	};
 
 	BS_PARAM_BLOCK_BEGIN(EyeAdaptationParamDef)
@@ -167,7 +150,11 @@ namespace bs { namespace ct
 		EyeAdaptationMat();
 
 		/** Executes the post-process effect with the provided parameters. */
-		void execute(PostProcessInfo& ppInfo, float frameDelta);
+		void execute(const SPtr<Texture>& reducedHistogram, const SPtr<RenderTarget>& output, float frameDelta, 
+			const AutoExposureSettings& settings, float exposureScale);
+
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
 	private:
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 		GpuParamTexture mReducedHistogramTex;
@@ -204,10 +191,10 @@ namespace bs { namespace ct
 		CreateTonemapLUTMat();
 
 		/** Executes the post-process effect with the provided parameters. */
-		void execute(PostProcessInfo& ppInfo);
+		void execute(const SPtr<Texture>& output, const StandardPostProcessSettings& settings);
 
-		/** Releases the output render target. */
-		void release(PostProcessInfo& ppInfo);
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
 
 		/** Size of the 3D color lookup table. */
 		static const UINT32 LUT_SIZE = 32;
@@ -235,8 +222,8 @@ namespace bs { namespace ct
 		TonemappingMat();
 
 		/** Executes the post-process effect with the provided parameters. */
-		void execute(const SPtr<Texture>& sceneColor, const SPtr<RenderTarget>& outputRT, const Rect2& outputRect,
-			PostProcessInfo& ppInfo);
+		void execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& eyeAdaptation, const SPtr<Texture>& colorLUT,
+			const SPtr<RenderTarget>& output, const StandardPostProcessSettings& settings);
 
 		/** Returns the material variation matching the provided parameters. */
 		static TonemappingMat* getVariation(bool gammaOnly, bool autoExposure, bool MSAA);
@@ -422,26 +409,6 @@ namespace bs { namespace ct
 		static ShaderVariation VAR_Near_NoFar;
 	};
 
-	/** Performs Gaussian depth of field effect with the help of various related shaders. */
-	class GaussianDOF
-	{
-	public:
-		/** 
-		 * Executes the depth of field effect on the provided scene color texture.
-		 * 
-		 * @param[in]	sceneColor	Input texture containing scene color.
-		 * @param[in]	sceneDepth	Input depth buffer texture that will be used for determining pixel depth.
-		 * @param[in]	output		Texture to output the results to.
-		 * @param[in]	view		View through which the depth of field effect is viewed.
-		 * @param[in]	settings	Settings used to control depth of field rendering. 
-		 */
-		void execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& sceneDepth, const SPtr<RenderTarget>& output, 
-			const RendererView& view, const DepthOfFieldSettings& settings);
-
-		/** Checks does the depth of field effect need to execute. */
-		static bool requiresDOF(const DepthOfFieldSettings& settings);
-	};
-
 	/** Shader that calculates a single level of the hierarchical Z mipmap chain. */
 	class BuildHiZMat : public RendererMaterial<BuildHiZMat>
 	{
@@ -850,10 +817,6 @@ namespace bs { namespace ct
 		 */
 		void buildSSAO(const RendererView& view);
 	private:
-		EyeAdaptHistogramReduceMat mEyeAdaptHistogramReduce;
-		EyeAdaptationMat mEyeAdaptation;
-
-		GaussianDOF mGaussianDOF;
 		SSAO mSSAO;
 	};
 

+ 1 - 1
Source/RenderBeast/Include/BsRenderBeast.h

@@ -166,7 +166,7 @@ namespace bs
 		 *			
 		 * @note	Core thread only.
 		 */
-		void renderView(const RendererViewGroup& viewGroup, RendererView* viewInfo, float frameDelta);
+		void renderView(const RendererViewGroup& viewGroup, RendererView* viewInfo, const FrameInfo& frameInfo);
 
 		/**
 		 * Renders all overlay callbacks of the provided view.

+ 181 - 46
Source/RenderBeast/Include/BsRenderCompositor.h

@@ -10,28 +10,37 @@ namespace bs { namespace ct
 	class RendererViewGroup;
 	class RenderCompositorNode;
 	struct PooledStorageBuffer;
+	struct FrameInfo;
 
 	/** @addtogroup RenderBeast
 	 *  @{
 	 */
 	
-	// TODO - Doc
+	/** Inputs provided to each node in the render compositor hierarchy */
 	struct RenderCompositorNodeInputs
 	{
 		RenderCompositorNodeInputs(const RendererViewGroup& viewGroup, const RendererView& view, const SceneInfo& scene, 
-			const RenderBeastOptions& options, const SmallVector<RenderCompositorNode*, 4>& inputNodes)
-			: viewGroup(viewGroup), view(view), scene(scene), options(options), inputNodes(inputNodes)
+			const RenderBeastOptions& options, const FrameInfo& frameInfo, 
+			const SmallVector<RenderCompositorNode*, 4>& inputNodes)
+			: viewGroup(viewGroup), view(view), scene(scene), options(options), frameInfo(frameInfo), inputNodes(inputNodes)
 		{ }
 
 		const RendererViewGroup& viewGroup;
 		const RendererView& view;
 		const SceneInfo& scene;
 		const RenderBeastOptions& options;
+		const FrameInfo& frameInfo;
 
 		SmallVector<RenderCompositorNode*, 4> inputNodes;
 	};
 
-	// TODO - Doc
+	/** 
+	 * Node in the render compositor hierarchy. Nodes can be implemented to perform specific rendering tasks. Each node
+	 * can depend on other nodes in the hierarchy.
+	 * 
+	 * @note	Implementations must provide a getNodeId() and getDependencies() static method, which are expected to
+	 *			return a unique name for the implemented node, as well as a set of nodes it depends on.
+	 */
 	class RenderCompositorNode
 	{
 	public:
@@ -40,18 +49,24 @@ namespace bs { namespace ct
 	protected:
 		friend class RenderCompositor;
 
-		// TODO - Doc
+		/** Executes the task implemented in the node. */
 		virtual void render(const RenderCompositorNodeInputs& inputs) = 0;
 
-		// TODO - Doc. Note this is meant to clean up data within a single render pass, if there is permanent data lasting
-		// multiple frames, only clear that when the node is destroyed.
+		/** 
+		 * Cleans up any temporary resources allocated in a render() call. Any resources lasting longer than one frame
+		 * should be kept alive and released in some other manner.
+		 */
 		virtual void clear() = 0;
 	};
 
-	// TODO - Doc
+	/**
+	 * Performs rendering by iterating over a hierarchy of render nodes. Each node in the hierarchy performs a specific
+	 * rendering tasks and passes its output to the dependant node. The system takes care of initializing, rendering and
+	 * cleaning up nodes automatically depending on their dependencies.
+	 */
 	class RenderCompositor
 	{
-		// TODO - Doc
+		/** Contains internal information about a single render node. */
 		struct NodeInfo
 		{
 			RenderCompositorNode* node;
@@ -59,28 +74,34 @@ namespace bs { namespace ct
 			SmallVector<RenderCompositorNode*, 4> inputs;
 		};
 	public:
-		~RenderCompositor()
-		{
-			clear();
-		}
-
-		// TODO - Doc
+		~RenderCompositor();
+
+		/**
+		 * Rebuilds the render node hierarchy. Call this whenever some setting that may influence the render node 
+		 * dependencies changes.
+		 * 
+		 * @param[in]	view		Parent view to which this compositor belongs to.
+		 * @param[in]	finalNode	Identifier of the final node in the node hierarchy. This node is expected to write
+		 *							to the views render target. All other nodes will be deduced from this node's
+		 *							dependencies.
+		 */
 		void build(const RendererView& view, const StringID& finalNode);
 
-		// TODO - Doc
-		void execute(const RendererViewGroup& viewGroup, const RendererView& view, const SceneInfo& scene,
-			const RenderBeastOptions& options) const;
+		/**
+		 * Performs rendering using the current render node hierarchy. This is expected to be called once per frame.
+		 * 
+		 * @param[in]	viewGroup		Information about the current view group.
+		 * @param[in]	view			Information about the view this compositor belongs to.
+		 * @param[in]	scene			Information about the entire scene.
+		 * @param[in]	frameInfo		Information about the current frame.
+		 * @param[in]	options			Global renderer options.
+		 */
+		void execute(const RendererViewGroup& viewGroup, const RendererView& view, const SceneInfo& scene, 
+			const FrameInfo& frameInfo, const RenderBeastOptions& options) const;
 
 	private:
-		// TODO - Doc
-		void clear()
-		{
-			for (auto& entry : mNodeInfos)
-				bs_delete(entry.node);
-
-			mNodeInfos.clear();
-			mIsValid = false;
-		}
+		/** Clears the render node hierarchy. */
+		void clear();
 
 		Vector<NodeInfo> mNodeInfos;
 		bool mIsValid = false;
@@ -89,35 +110,39 @@ namespace bs { namespace ct
 		/* 							NODE TYPES	                     			*/
 		/************************************************************************/
 	public:
-		// TODO - Doc
+		/** Contains information about a specific node type. */
 		struct NodeType
 		{
 			virtual ~NodeType() {};
 
-			// TODO - Doc
+			/** Creates a new node of this type. */
 			virtual RenderCompositorNode* create() const = 0;
 
-			// TODO - Doc
+			/** Returns identifier for all the dependencies of a node of this type. */
 			virtual SmallVector<StringID, 4> getDependencies(const RendererView& view) const = 0;
 
 			StringID id;
 		};
 
-		// TODO - Doc
+		
+		/** Templated implementation of NodeType. */
 		template<class T>
 		struct TNodeType : NodeType
 		{
-			// TODO - Doc
+			/** @copydoc NodeType::create() */
 			RenderCompositorNode* create() const override { return bs_new<T>(); }
 
-			// TODO - Doc
+			/** @copydoc NodeType::getDependencies() */
 			SmallVector<StringID, 4> getDependencies(const RendererView& view) const override
 			{
 				return T::getDependencies(view);
 			}
 		};
 
-		// TODO - Doc
+		/** 
+		 * Registers a new type of node with the system. Each node type must first be registered before it can be used
+		 * in the node hierarchy.
+		 */
 		template<class T>
 		static void registerNodeType()
 		{
@@ -128,7 +153,7 @@ namespace bs { namespace ct
 			mNodeTypes[T::getNodeId()] = bs_new<TNodeType<T>>();
 		}
 
-		// TODO - Doc
+		/** Releases any information about render node types. */
 		static void cleanUp()
 		{
 			for (auto& entry : mNodeTypes)
@@ -137,6 +162,7 @@ namespace bs { namespace ct
 			mNodeTypes.clear();
 		}
 
+	private:
 		static UnorderedMap<StringID, NodeType*> mNodeTypes;
 	};
 
@@ -144,7 +170,7 @@ namespace bs { namespace ct
 	/* 							BASE PASS NODES	                     		*/
 	/************************************************************************/
 
-	// TODO - Doc
+	/** Initializes the scene depth texture. Does not perform any rendering. */
 	class RCNodeSceneDepth : public RenderCompositorNode
 	{
 	public:
@@ -161,7 +187,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/** 
+	 * Initializes the GBuffer textures and renders the base pass into the GBuffer. The base pass includes all the opaque
+	 * objects visible to the view.
+	 */
 	class RCNodeGBuffer : public RenderCompositorNode
 	{
 	public:
@@ -182,7 +211,7 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/** Initializes the scene color texture and/or buffer. Does not perform any rendering. */
 	class RCNodeSceneColor : public RenderCompositorNode
 	{
 	public:
@@ -215,7 +244,7 @@ namespace bs { namespace ct
 	/* 							LIGHTING NODES                     			*/
 	/************************************************************************/
 
-	// TODO - Doc
+	/** Initializes the light accumulation texture and/or buffer. Does not perform any rendering. */
 	class RCNodeLightAccumulation : public RenderCompositorNode
 	{
 	public:
@@ -244,7 +273,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/** 
+	 * Performs tiled deferred lighting, outputing any lighting information in the light accumulation buffer. 
+	 * By default only non-shadowed lights are rendered, as shadowed ones are handled using standard deferred.
+	 */
 	class RCNodeTiledDeferredLighting : public RenderCompositorNode
 	{
 	public:
@@ -261,7 +293,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/**
+	 * Performs standard deferred lighting, outputting any lighting information in the light accumulation buffer.
+	 * Only renders shadowed lights.
+	 */
 	class RCNodeStandardDeferredLighting : public RenderCompositorNode
 	{
 	public:
@@ -281,7 +316,10 @@ namespace bs { namespace ct
 		SPtr<RenderTexture> mRenderTarget;
 	};
 
-	// TODO - Doc
+	/**
+	 * In case light accumulation was rendered into a buffer instead of a texture (if MSAA is used), this node will
+	 * unflatten the buffer and write its contents into the light accumulation texture.
+	 */
 	class RCNodeUnflattenLightAccum : public RenderCompositorNode
 	{
 	public:
@@ -298,7 +336,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/**
+	 * Perform tiled deferred image based lighting, combines it with direct lighting present in the light accumulation
+	 * buffer and outputs the results to the scene color texture or buffer.
+	 */
 	class RCNodeTiledDeferredIBL : public RenderCompositorNode
 	{
 	public:
@@ -315,7 +356,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/**
+	 * In case scene color was rendered into a buffer instead of a texture (if MSAA is used), this node will
+	 * unflatten the buffer and write its contents into the scene color texture.
+	 */
 	class RCNodeUnflattenSceneColor : public RenderCompositorNode
 	{
 	public:
@@ -332,7 +376,10 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/**
+	 * Renders the skybox into the scene color texture. If skybox texture is not available, a solid color will be rendered
+	 * instead.
+	 */
 	class RCNodeSkybox : public RenderCompositorNode
 	{
 	public:
@@ -346,7 +393,7 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
-	// TODO - Doc
+	/** Moves the contents of the scene color texture into the view's output target. */
 	class RCNodeFinalResolve : public RenderCompositorNode
 	{
 	public:
@@ -360,5 +407,93 @@ namespace bs { namespace ct
 		void clear() override;
 	};
 
+	/************************************************************************/
+	/* 							POST PROCESS NODES                			*/
+	/************************************************************************/
+
+	/** 
+	 * Helper node used for post-processing. Takes care of allocating and switching between textures used for post process
+	 * effects. 
+	 */
+	class RCNodePostProcess : public RenderCompositorNode
+	{
+	public:
+		RCNodePostProcess();
+
+		/** 
+		 * Returns a texture that can be used for rendering a post-process effect, and the result of the previous 
+		 * output. Switches these textures so the next call they are returned in the opposite parameters. 
+		 */
+		void getAndSwitch(const RendererView& view, SPtr<RenderTexture>& output, SPtr<Texture>& lastFrame) const;
+
+		/** Returns a texture that contains the last rendererd post process output. */
+		SPtr<Texture> getLastOutput() const;
+
+		static StringID getNodeId() { return "PostProcess"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+
+		mutable SPtr<PooledRenderTexture> mOutput[2];
+		mutable bool mAllocated[2];
+		mutable UINT32 mCurrentIdx = 0;
+	};
+
+	/**
+	 * Performs tone mapping on the contents of the scene color texture. At the same time resolves MSAA into a non-MSAA
+	 * scene color texture.
+	 */
+	class RCNodeTonemapping : public RenderCompositorNode
+	{
+	public:
+		SPtr<PooledRenderTexture> eyeAdaptation;
+		SPtr<PooledRenderTexture> prevEyeAdaptation;
+
+		~RCNodeTonemapping();
+
+		static StringID getNodeId() { return "Tonemapping"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+
+		SPtr<PooledRenderTexture> mTonemapLUT;
+	};
+
+	/** Renders the depth of field effect using Gaussian blurring. */
+	class RCNodeGaussianDOF : public RenderCompositorNode
+	{
+	public:
+		static StringID getNodeId() { return "GaussianDOF"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+	};
+
+	/** Renders FXAA. */
+	class RCNodeFXAA : public RenderCompositorNode
+	{
+	public:
+		static StringID getNodeId() { return "FXAA"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+	};
+
 	/** @} */
 }}

+ 67 - 307
Source/RenderBeast/Source/BsPostProcessing.cpp

@@ -56,12 +56,12 @@ namespace bs { namespace ct
 		variations.add(VAR_HighQuality_MSAA);
 	}
 
-	void DownsampleMat::execute(const SPtr<Texture>& target, PostProcessInfo& ppInfo)
+	void DownsampleMat::execute(const SPtr<Texture>& input, const SPtr<RenderTarget>& output)
 	{
 		// Set parameters
-		mInputTexture.set(target);
+		mInputTexture.set(input);
 
-		const TextureProperties& rtProps = target->getProperties();
+		const TextureProperties& rtProps = input->getProperties();
 
 		bool MSAA = mVariation.getInt("MSAA") > 0;
 		if(MSAA)
@@ -81,17 +81,8 @@ namespace bs { namespace ct
 			gDownsampleParamDef.gOffsets.set(mParamBuffer, invTextureSize * Vector2(1.0f, 1.0f));
 		}
 
-		// Set output
-		UINT32 width = std::max(1, Math::ceilToInt(rtProps.getWidth() * 0.5f));
-		UINT32 height = std::max(1, Math::ceilToInt(rtProps.getHeight() * 0.5f));
-
-		mOutputDesc = POOLED_RENDER_TEXTURE_DESC::create2D(rtProps.getFormat(), width, height, TU_RENDERTARGET);
-
-		// Render
-		ppInfo.downsampledSceneTex = GpuResourcePool::instance().get(mOutputDesc);
-
 		RenderAPI& rapi = RenderAPI::instance();
-		rapi.setRenderTarget(ppInfo.downsampledSceneTex->renderTexture, FBT_DEPTH | FBT_STENCIL);
+		rapi.setRenderTarget(output, FBT_DEPTH | FBT_STENCIL);
 
 		gRendererUtility().setPass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
@@ -102,14 +93,16 @@ namespace bs { namespace ct
 			gRendererUtility().drawScreenQuad();
 
 		rapi.setRenderTarget(nullptr);
-
-		mOutput = ppInfo.downsampledSceneTex->renderTexture;
 	}
 
-	void DownsampleMat::release(PostProcessInfo& ppInfo)
+	POOLED_RENDER_TEXTURE_DESC DownsampleMat::getOutputDesc(const SPtr<Texture>& target)
 	{
-		GpuResourcePool::instance().release(ppInfo.downsampledSceneTex);
-		mOutput = nullptr;
+		const TextureProperties& rtProps = target->getProperties();
+		
+		UINT32 width = std::max(1, Math::ceilToInt(rtProps.getWidth() * 0.5f));
+		UINT32 height = std::max(1, Math::ceilToInt(rtProps.getHeight() * 0.5f));
+
+		return POOLED_RENDER_TEXTURE_DESC::create2D(rtProps.getFormat(), width, height, TU_RENDERTARGET);
 	}
 
 	DownsampleMat* DownsampleMat::getVariation(UINT32 quality, bool msaa)
@@ -154,52 +147,45 @@ namespace bs { namespace ct
 		variations.add(variation);
 	}
 
-	void EyeAdaptHistogramMat::execute(PostProcessInfo& ppInfo)
+	void EyeAdaptHistogramMat::execute(const SPtr<Texture>& input, const SPtr<Texture>& output, 
+		const AutoExposureSettings& settings)
 	{
 		// Set parameters
-		SPtr<RenderTexture> target = ppInfo.downsampledSceneTex->renderTexture;
-		mSceneColor.set(ppInfo.downsampledSceneTex->texture);
+		mSceneColor.set(input);
 
-		const RenderTextureProperties& props = target->getProperties();
+		const TextureProperties& props = input->getProperties();
 		int offsetAndSize[4] = { 0, 0, (INT32)props.getWidth(), (INT32)props.getHeight() };
 
-		gEyeAdaptHistogramParamDef.gHistogramParams.set(mParamBuffer, getHistogramScaleOffset(ppInfo));
+		gEyeAdaptHistogramParamDef.gHistogramParams.set(mParamBuffer, getHistogramScaleOffset(settings));
 		gEyeAdaptHistogramParamDef.gPixelOffsetAndSize.set(mParamBuffer, Vector4I(offsetAndSize));
 
-		Vector2I threadGroupCount = getThreadGroupCount(target);
+		Vector2I threadGroupCount = getThreadGroupCount(input);
 		gEyeAdaptHistogramParamDef.gThreadGroupCount.set(mParamBuffer, threadGroupCount);
 
-		// Set output
-		UINT32 numHistograms = threadGroupCount.x * threadGroupCount.y;
-
-		mOutputDesc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, HISTOGRAM_NUM_TEXELS, numHistograms,
-			TU_LOADSTORE);
-
 		// Dispatch
-		ppInfo.histogramTex = GpuResourcePool::instance().get(mOutputDesc);
-
-		mOutputTex.set(ppInfo.histogramTex->texture);
+		mOutputTex.set(output);
 
 		RenderAPI& rapi = RenderAPI::instance();
 		gRendererUtility().setComputePass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
 		rapi.dispatchCompute(threadGroupCount.x, threadGroupCount.y);
-
-		mOutput = ppInfo.histogramTex->renderTexture;
 	}
 
-	void EyeAdaptHistogramMat::release(PostProcessInfo& ppInfo)
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptHistogramMat::getOutputDesc(const SPtr<Texture>& target)
 	{
-		GpuResourcePool::instance().release(ppInfo.histogramTex);
-		mOutput = nullptr;
+		Vector2I threadGroupCount = getThreadGroupCount(target);
+		UINT32 numHistograms = threadGroupCount.x * threadGroupCount.y;
+
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, HISTOGRAM_NUM_TEXELS, numHistograms,
+			TU_LOADSTORE);
 	}
 
-	Vector2I EyeAdaptHistogramMat::getThreadGroupCount(const SPtr<RenderTexture>& target)
+	Vector2I EyeAdaptHistogramMat::getThreadGroupCount(const SPtr<Texture>& target)
 	{
 		const UINT32 texelsPerThreadGroupX = THREAD_GROUP_SIZE_X * LOOP_COUNT_X;
 		const UINT32 texelsPerThreadGroupY = THREAD_GROUP_SIZE_Y * LOOP_COUNT_Y;
 
-		const RenderTextureProperties& props = target->getProperties();
+		const TextureProperties& props = target->getProperties();
 	
 		Vector2I threadGroupCount;
 		threadGroupCount.x = ((INT32)props.getWidth() + texelsPerThreadGroupX - 1) / texelsPerThreadGroupX;
@@ -208,13 +194,11 @@ namespace bs { namespace ct
 		return threadGroupCount;
 	}
 
-	Vector2 EyeAdaptHistogramMat::getHistogramScaleOffset(const PostProcessInfo& ppInfo)
+	Vector2 EyeAdaptHistogramMat::getHistogramScaleOffset(const AutoExposureSettings& settings)
 	{
-		const StandardPostProcessSettings& settings = *ppInfo.settings;
-
-		float diff = settings.autoExposure.histogramLog2Max - settings.autoExposure.histogramLog2Min;
+		float diff = settings.histogramLog2Max - settings.histogramLog2Min;
 		float scale = 1.0f / diff;
-		float offset = -settings.autoExposure.histogramLog2Min * scale;
+		float offset = -settings.histogramLog2Min * scale;
 
 		return Vector2(scale, offset);
 	}
@@ -236,35 +220,27 @@ namespace bs { namespace ct
 		// Do nothing
 	}
 
-	void EyeAdaptHistogramReduceMat::execute(PostProcessInfo& ppInfo)
+	void EyeAdaptHistogramReduceMat::execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& histogram,
+		const SPtr<Texture>& prevFrame, const SPtr<RenderTarget>& output)
 	{
 		// Set parameters
-		mHistogramTex.set(ppInfo.histogramTex->texture);
+		mHistogramTex.set(histogram);
 
-		SPtr<PooledRenderTexture> eyeAdaptationRT = ppInfo.eyeAdaptationTex[ppInfo.lastEyeAdaptationTex];
 		SPtr<Texture> eyeAdaptationTex;
-
-		if (eyeAdaptationRT != nullptr) // Could be that this is the first run
-			eyeAdaptationTex = eyeAdaptationRT->texture;
-		else
+		if (prevFrame == nullptr) // Could be that this is the first run
 			eyeAdaptationTex = Texture::WHITE;
+		else
+			eyeAdaptationTex = prevFrame;
 
 		mEyeAdaptationTex.set(eyeAdaptationTex);
 
-		Vector2I threadGroupCount = EyeAdaptHistogramMat::getThreadGroupCount(ppInfo.downsampledSceneTex->renderTexture);
+		Vector2I threadGroupCount = EyeAdaptHistogramMat::getThreadGroupCount(sceneColor);
 		UINT32 numHistograms = threadGroupCount.x * threadGroupCount.y;
 
 		gEyeAdaptHistogramReduceParamDef.gThreadGroupCount.set(mParamBuffer, numHistograms);
 
-		// Set output
-		mOutputDesc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, EyeAdaptHistogramMat::HISTOGRAM_NUM_TEXELS, 2,
-			TU_RENDERTARGET);
-
-		// Render
-		ppInfo.histogramReduceTex = GpuResourcePool::instance().get(mOutputDesc);
-
 		RenderAPI& rapi = RenderAPI::instance();
-		rapi.setRenderTarget(ppInfo.histogramReduceTex->renderTexture, FBT_DEPTH | FBT_STENCIL);
+		rapi.setRenderTarget(output, FBT_DEPTH | FBT_STENCIL);
 
 		gRendererUtility().setPass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
@@ -273,14 +249,12 @@ namespace bs { namespace ct
 		gRendererUtility().drawScreenQuad(drawUV);
 
 		rapi.setRenderTarget(nullptr);
-
-		mOutput = ppInfo.histogramReduceTex->renderTexture;
 	}
 
-	void EyeAdaptHistogramReduceMat::release(PostProcessInfo& ppInfo)
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptHistogramReduceMat::getOutputDesc()
 	{
-		GpuResourcePool::instance().release(ppInfo.histogramReduceTex);
-		mOutput = nullptr;
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, EyeAdaptHistogramMat::HISTOGRAM_NUM_TEXELS, 2,
+			TU_RENDERTARGET);
 	}
 
 	EyeAdaptationParamDef gEyeAdaptationParamDef;
@@ -302,41 +276,30 @@ namespace bs { namespace ct
 		variations.add(variation);
 	}
 
-	void EyeAdaptationMat::execute(PostProcessInfo& ppInfo, float frameDelta)
+	void EyeAdaptationMat::execute(const SPtr<Texture>& reducedHistogram, const SPtr<RenderTarget>& output, 
+		float frameDelta, const AutoExposureSettings& settings, float exposureScale)
 	{
-		bool texturesInitialized = ppInfo.eyeAdaptationTex[0] != nullptr && ppInfo.eyeAdaptationTex[1] != nullptr;
-		if(!texturesInitialized)
-		{
-			POOLED_RENDER_TEXTURE_DESC outputDesc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT32_R, 1, 1, TU_RENDERTARGET);
-			ppInfo.eyeAdaptationTex[0] = GpuResourcePool::instance().get(outputDesc);
-			ppInfo.eyeAdaptationTex[1] = GpuResourcePool::instance().get(outputDesc);
-		}
-
-		ppInfo.lastEyeAdaptationTex = (ppInfo.lastEyeAdaptationTex + 1) % 2; // TODO - Do I really need two targets?
-
 		// Set parameters
-		mReducedHistogramTex.set(ppInfo.histogramReduceTex->texture);
+		mReducedHistogramTex.set(reducedHistogram);
 
-		Vector2 histogramScaleAndOffset = EyeAdaptHistogramMat::getHistogramScaleOffset(ppInfo);
-
-		const StandardPostProcessSettings& settings = *ppInfo.settings;
+		Vector2 histogramScaleAndOffset = EyeAdaptHistogramMat::getHistogramScaleOffset(settings);
 
 		Vector4 eyeAdaptationParams[3];
 		eyeAdaptationParams[0].x = histogramScaleAndOffset.x;
 		eyeAdaptationParams[0].y = histogramScaleAndOffset.y;
 
-		float histogramPctHigh = Math::clamp01(settings.autoExposure.histogramPctHigh);
+		float histogramPctHigh = Math::clamp01(settings.histogramPctHigh);
 
-		eyeAdaptationParams[0].z = std::min(Math::clamp01(settings.autoExposure.histogramPctLow), histogramPctHigh);
+		eyeAdaptationParams[0].z = std::min(Math::clamp01(settings.histogramPctLow), histogramPctHigh);
 		eyeAdaptationParams[0].w = histogramPctHigh;
 
-		eyeAdaptationParams[1].x = std::min(settings.autoExposure.minEyeAdaptation, settings.autoExposure.maxEyeAdaptation);
-		eyeAdaptationParams[1].y = settings.autoExposure.maxEyeAdaptation;
+		eyeAdaptationParams[1].x = std::min(settings.minEyeAdaptation, settings.maxEyeAdaptation);
+		eyeAdaptationParams[1].y = settings.maxEyeAdaptation;
 
-		eyeAdaptationParams[1].z = settings.autoExposure.eyeAdaptationSpeedUp;
-		eyeAdaptationParams[1].w = settings.autoExposure.eyeAdaptationSpeedDown;
+		eyeAdaptationParams[1].z = settings.eyeAdaptationSpeedUp;
+		eyeAdaptationParams[1].w = settings.eyeAdaptationSpeedDown;
 
-		eyeAdaptationParams[2].x = Math::pow(2.0f, settings.exposureScale);
+		eyeAdaptationParams[2].x = Math::pow(2.0f, exposureScale);
 		eyeAdaptationParams[2].y = frameDelta;
 
 		eyeAdaptationParams[2].z = 0.0f; // Unused
@@ -347,10 +310,8 @@ namespace bs { namespace ct
 		gEyeAdaptationParamDef.gEyeAdaptationParams.set(mParamBuffer, eyeAdaptationParams[2], 2);
 
 		// Render
-		SPtr<PooledRenderTexture> eyeAdaptationRT = ppInfo.eyeAdaptationTex[ppInfo.lastEyeAdaptationTex];
-
 		RenderAPI& rapi = RenderAPI::instance();
-		rapi.setRenderTarget(eyeAdaptationRT->renderTexture, FBT_DEPTH | FBT_STENCIL);
+		rapi.setRenderTarget(output, FBT_DEPTH | FBT_STENCIL);
 
 		gRendererUtility().setPass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
@@ -359,6 +320,11 @@ namespace bs { namespace ct
 		rapi.setRenderTarget(nullptr);
 	}
 
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptationMat::getOutputDesc()
+	{
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT32_R, 1, 1, TU_RENDERTARGET);
+	}
+
 	CreateTonemapLUTParamDef gCreateTonemapLUTParamDef;
 	WhiteBalanceParamDef gWhiteBalanceParamDef;
 
@@ -383,10 +349,8 @@ namespace bs { namespace ct
 		variations.add(variation);
 	}
 
-	void CreateTonemapLUTMat::execute(PostProcessInfo& ppInfo)
+	void CreateTonemapLUTMat::execute(const SPtr<Texture>& output, const StandardPostProcessSettings& settings)
 	{
-		const StandardPostProcessSettings& settings = *ppInfo.settings;
-
 		// Set parameters
 		gCreateTonemapLUTParamDef.gGammaAdjustment.set(mParamBuffer, 2.2f / settings.gamma);
 
@@ -418,14 +382,8 @@ namespace bs { namespace ct
 		gWhiteBalanceParamDef.gWhiteTemp.set(mWhiteBalanceParamBuffer, settings.whiteBalance.temperature);
 		gWhiteBalanceParamDef.gWhiteOffset.set(mWhiteBalanceParamBuffer, settings.whiteBalance.tint);
 
-		// Set output
-		POOLED_RENDER_TEXTURE_DESC outputDesc = POOLED_RENDER_TEXTURE_DESC::create3D(PF_R8G8B8A8, 
-			LUT_SIZE, LUT_SIZE, LUT_SIZE, TU_LOADSTORE);
-
 		// Dispatch
-		ppInfo.colorLUT = GpuResourcePool::instance().get(outputDesc);
-
-		mOutputTex.set(ppInfo.colorLUT->texture);
+		mOutputTex.set(output);
 
 		RenderAPI& rapi = RenderAPI::instance();
 		
@@ -434,9 +392,9 @@ namespace bs { namespace ct
 		rapi.dispatchCompute(LUT_SIZE / 8, LUT_SIZE / 8, LUT_SIZE);
 	}
 
-	void CreateTonemapLUTMat::release(PostProcessInfo& ppInfo)
+	POOLED_RENDER_TEXTURE_DESC CreateTonemapLUTMat::getOutputDesc()
 	{
-		GpuResourcePool::instance().release(ppInfo.colorLUT);
+		return POOLED_RENDER_TEXTURE_DESC::create3D(PF_R8G8B8A8, LUT_SIZE, LUT_SIZE, LUT_SIZE, TU_LOADSTORE);
 	}
 
 	TonemappingParamDef gTonemappingParamDef;
@@ -522,35 +480,24 @@ namespace bs { namespace ct
 		variations.add(VAR_NoGamma_NoAutoExposure_NoMSAA);
 	}
 
-	void TonemappingMat::execute(const SPtr<Texture>& sceneColor, 
-		const SPtr<RenderTarget>& outputRT, const Rect2& outputRect, PostProcessInfo& ppInfo)
+	void TonemappingMat::execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& eyeAdaptation, 
+		const SPtr<Texture>& colorLUT, const SPtr<RenderTarget>& output, const StandardPostProcessSettings& settings)
 	{
 		const TextureProperties& texProps = sceneColor->getProperties();
 
-		gTonemappingParamDef.gRawGamma.set(mParamBuffer, 1.0f / ppInfo.settings->gamma);
-		gTonemappingParamDef.gManualExposureScale.set(mParamBuffer, Math::pow(2.0f, ppInfo.settings->exposureScale));
+		gTonemappingParamDef.gRawGamma.set(mParamBuffer, 1.0f / settings.gamma);
+		gTonemappingParamDef.gManualExposureScale.set(mParamBuffer, Math::pow(2.0f, settings.exposureScale));
 		gTonemappingParamDef.gNumSamples.set(mParamBuffer, texProps.getNumSamples());
 
 		// Set parameters
 		mInputTex.set(sceneColor);
-
-		SPtr<Texture> colorLUT;
-		if(ppInfo.colorLUT != nullptr)
-			colorLUT = ppInfo.colorLUT->texture;
-
 		mColorLUT.set(colorLUT);
-
-		SPtr<Texture> eyeAdaptationTexture;
-		if(ppInfo.eyeAdaptationTex[ppInfo.lastEyeAdaptationTex] != nullptr)
-			eyeAdaptationTexture = ppInfo.eyeAdaptationTex[ppInfo.lastEyeAdaptationTex]->texture;
-
-		mEyeAdaptationTex.set(eyeAdaptationTexture);
+		mEyeAdaptationTex.set(eyeAdaptation);
 
 		// Render
 		RenderAPI& rapi = RenderAPI::instance();
 
-		rapi.setRenderTarget(outputRT);
-		rapi.setViewport(outputRect);
+		rapi.setRenderTarget(output);
 
 		gRendererUtility().setPass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
@@ -1002,79 +949,6 @@ namespace bs { namespace ct
 			return get(VAR_NoNear_Far);
 	}
 
-	void GaussianDOF::execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& sceneDepth, 
-		const SPtr<RenderTarget>& output, const RendererView& view, const DepthOfFieldSettings& settings)
-	{
-		bool near = settings.nearBlurAmount > 0.0f;
-		bool far = settings.farBlurAmount > 0.0f;
-
-		// This shouldn't have been called if both are false
-		assert(near || far);
-
-		GaussianDOFSeparateMat* separateMat = GaussianDOFSeparateMat::getVariation(near, far);
-		GaussianDOFCombineMat* combineMat = GaussianDOFCombineMat::getVariation(near, far);
-		GaussianBlurMat* blurMat = GaussianBlurMat::get();
-
-		separateMat->execute(sceneColor, sceneDepth, view, settings);
-
-		SPtr<PooledRenderTexture> nearTex, farTex;
-		if(near && far)
-		{
-			nearTex = separateMat->getOutput(0);
-			farTex = separateMat->getOutput(1);
-		}
-		else
-		{
-			if (near)
-				nearTex = separateMat->getOutput(0);
-			else
-				farTex = separateMat->getOutput(0);
-		}
-
-		// Blur the out of focus pixels
-		// Note: Perhaps set up stencil so I can avoid performing blur on unused parts of the textures?
-		const TextureProperties& texProps = nearTex ? nearTex->texture->getProperties() : farTex->texture->getProperties();
-		POOLED_RENDER_TEXTURE_DESC tempTexDesc = POOLED_RENDER_TEXTURE_DESC::create2D(texProps.getFormat(), 
-			texProps.getWidth(), texProps.getHeight(), TU_RENDERTARGET);
-		SPtr<PooledRenderTexture> tempTexture = GpuResourcePool::instance().get(tempTexDesc);
-
-		SPtr<Texture> blurredNearTex;
-		if(nearTex)
-		{
-			blurMat->execute(nearTex->texture, settings.nearBlurAmount, tempTexture->renderTexture);
-			blurredNearTex = tempTexture->texture;
-		}
-
-		SPtr<Texture> blurredFarTex;
-		if(farTex)
-		{
-			// If temporary texture is used up, re-use the original near texture for the blurred result
-			if(blurredNearTex)
-			{
-				blurMat->execute(farTex->texture, settings.farBlurAmount, nearTex->renderTexture);
-				blurredFarTex = nearTex->texture;
-			}
-			else // Otherwise just use the temporary
-			{
-				blurMat->execute(farTex->texture, settings.farBlurAmount, tempTexture->renderTexture);
-				blurredFarTex = tempTexture->texture;
-			}
-		}
-		
-		combineMat->execute(sceneColor, blurredNearTex, blurredFarTex, sceneDepth, output, view, settings);
-
-		separateMat->release();
-		GpuResourcePool::instance().release(tempTexture);
-	}
-
-	bool GaussianDOF::requiresDOF(const DepthOfFieldSettings& settings)
-	{
-		bool near = settings.nearBlurAmount > 0.0f;
-		bool far = settings.farBlurAmount > 0.0f;
-
-		return settings.enabled && (near || far);
-	}
-	
 	BuildHiZMat::BuildHiZMat()
 	{
 		SPtr<GpuParams> gpuParams = mParamsSet->getGpuParams();
@@ -2061,57 +1935,6 @@ namespace bs { namespace ct
 		PostProcessInfo& ppInfo = viewInfo->getPPInfo();
 		const StandardPostProcessSettings& settings = *ppInfo.settings;
 
-		SPtr<Texture> sceneColor = renderTargets->get(RTT_SceneColor);
-		Rect2 viewportRect = viewProps.nrmViewRect;
-
-		bool hdr = viewProps.isHDR;
-		bool msaa = viewProps.numSamples > 1;
-
-		if(hdr && settings.enableAutoExposure)
-		{
-			DownsampleMat* downsample = DownsampleMat::getVariation(1, msaa);
-			EyeAdaptHistogramMat* eyeAdaptHistogram = EyeAdaptHistogramMat::get();
-
-			downsample->execute(sceneColor, ppInfo);
-			eyeAdaptHistogram->execute(ppInfo);
-			downsample->release(ppInfo);
-
-			mEyeAdaptHistogramReduce.execute(ppInfo);
-			eyeAdaptHistogram->release(ppInfo);
-
-			mEyeAdaptation.execute(ppInfo, frameDelta);
-			mEyeAdaptHistogramReduce.release(ppInfo);
-		}
-
-		bool gammaOnly;
-		bool autoExposure;
-		if (hdr)
-		{
-			if (settings.enableTonemapping)
-			{
-				if (ppInfo.settingDirty) // Rebuild LUT if PP settings changed
-				{
-					CreateTonemapLUTMat* createLUT = CreateTonemapLUTMat::get();
-					createLUT->execute(ppInfo);
-				}
-
-				gammaOnly = false;
-			}
-			else
-				gammaOnly = true;
-
-			autoExposure = settings.enableAutoExposure;
-		}
-		else
-		{
-			gammaOnly = true;
-			autoExposure = false;
-		}
-
-
-
-
-
 		// DEBUG ONLY
 		//SSRTraceMat ssrTrace;
 
@@ -2122,69 +1945,6 @@ namespace bs { namespace ct
 
 		//RenderAPI::instance().setRenderTarget(renderTargets->getRT(RTT_ResolvedSceneColor));
 		//gRendererUtility().blit(renderTargets->get(RTT_ResolvedSceneColorSecondary));
-
-		bool performDOF = GaussianDOF::requiresDOF(settings.depthOfField);
-
-		SPtr<RenderTarget> tonemapTarget;
-		if (!performDOF && !settings.enableFXAA)
-			tonemapTarget = viewProps.target;
-		else
-		{
-			renderTargets->allocate(RTT_ResolvedSceneColorSecondary);
-			tonemapTarget = renderTargets->getRT(RTT_ResolvedSceneColorSecondary);
-		}
-
-		TonemappingMat* tonemapping = TonemappingMat::getVariation(gammaOnly, autoExposure, msaa);
-		tonemapping->execute(sceneColor, tonemapTarget, viewportRect, ppInfo);
-
-
-
-		// DEBUG ONLY
-		//renderTargets->release(RTT_ResolvedSceneColorSecondary);
-		//return;
-
-
-		if(performDOF)
-		{
-			SPtr<RenderTarget> dofTarget;
-
-			// If DOF is the final effect, output to final target, otherwise use a temporary
-			if (settings.enableFXAA)
-			{
-				renderTargets->allocate(RTT_ResolvedSceneColor);
-				dofTarget = renderTargets->getRT(RTT_ResolvedSceneColor);
-			}
-			else
-				dofTarget = viewProps.target;
-
-			SPtr<Texture> sceneDepth = renderTargets->get(RTT_ResolvedDepth);
-
-			mGaussianDOF.execute(renderTargets->get(RTT_ResolvedSceneColorSecondary), sceneDepth, dofTarget, *viewInfo, 
-				settings.depthOfField);
-
-			renderTargets->release(RTT_ResolvedSceneColorSecondary);
-		}
-
-		if(settings.enableFXAA)
-		{
-			SPtr<Texture> fxaaSource;
-			if (performDOF)
-				fxaaSource = renderTargets->get(RTT_ResolvedSceneColor);
-			else
-				fxaaSource = renderTargets->get(RTT_ResolvedSceneColorSecondary);
-
-			// Note: I could skip executing FXAA over DOF and motion blurred pixels
-			FXAAMat* fxaa = FXAAMat::get();
-			fxaa->execute(fxaaSource, viewProps.target);
-
-			if (performDOF)
-				renderTargets->release(RTT_ResolvedSceneColor);
-			else
-				renderTargets->release(RTT_ResolvedSceneColorSecondary);
-		}
-
-		if (ppInfo.settingDirty)
-			ppInfo.settingDirty = false;
 	}
 
 	void PostProcessing::buildSSAO(const RendererView& view)

+ 61 - 83
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -91,6 +91,10 @@ namespace bs { namespace ct
 		RenderCompositor::registerNodeType<RCNodeFinalResolve>();
 		RenderCompositor::registerNodeType<RCNodeSkybox>();
 		RenderCompositor::registerNodeType<RCNodeUnflattenSceneColor>();
+		RenderCompositor::registerNodeType<RCNodePostProcess>();
+		RenderCompositor::registerNodeType<RCNodeTonemapping>();
+		RenderCompositor::registerNodeType<RCNodeGaussianDOF>();
+		RenderCompositor::registerNodeType<RCNodeFXAA>();
 	}
 
 	void RenderBeast::destroyCore()
@@ -345,11 +349,11 @@ namespace bs { namespace ct
 			if (view->getProperties().isOverlay)
 				renderOverlay(view);
 			else
-				renderView(viewGroup, view, frameInfo.timeDelta);
+				renderView(viewGroup, view, frameInfo);
 		}
 	}
 
-	void RenderBeast::renderView(const RendererViewGroup& viewGroup, RendererView* viewInfo, float frameDelta)
+	void RenderBeast::renderView(const RendererViewGroup& viewGroup, RendererView* viewInfo, const FrameInfo& frameInfo)
 	{
 		gProfilerCPU().beginSample("Render");
 
@@ -482,7 +486,8 @@ namespace bs { namespace ct
 		}
 
 		const RenderCompositor& compositor = viewInfo->getCompositor();
-		compositor.execute(viewGroup, *viewInfo, sceneInfo, *mCoreOptions);
+		compositor.execute(viewGroup, *viewInfo, sceneInfo, frameInfo, *mCoreOptions);
+		viewInfo->getPPInfo().settingDirty = false;
 
 		//renderTargets->allocate(RTT_HiZ);
 		//renderTargets->generate(RTT_HiZ);
@@ -529,33 +534,6 @@ namespace bs { namespace ct
 			}
 		}
 
-		// Post-processing and final resolve
-		Rect2 viewportArea = viewProps.nrmViewRect;
-
-		//if (viewProps.runPostProcessing)
-		//{
-		//	// Post-processing code also takes care of writting to the final output target
-		//	PostProcessing::instance().postProcess(viewInfo, renderTargets, frameDelta);
-		//}
-		//else
-		{
-			//// Just copy from scene color to output if no post-processing
-			//SPtr<RenderTarget> target = viewProps.target;
-
-			//RenderAPI& rapi = RenderAPI::instance();
-			//rapi.setRenderTarget(target);
-			//rapi.setViewport(viewportArea);
-
-			//SPtr<Texture> sceneColor = renderTargets->get(RTT_SceneColor);
-			//gRendererUtility().blit(sceneColor, Rect2I::EMPTY, viewProps.flipView);
-		}
-
-		//renderTargets->release(RTT_HiZ);
-		//renderTargets->release(RTT_SceneColor);
-
-		//if (isMSAA)
-		//	renderTargets->release(RTT_ResolvedDepth);
-
 		// Trigger overlay callbacks
 		if (viewProps.triggerCallbacks)
 		{
@@ -761,59 +739,59 @@ namespace bs { namespace ct
 	{
 		SkyInfo& sky = mScene->_getSceneInfo().sky;
 
-		// Get skybox image-based lighting textures if needed/available
-		if (sky.skybox != nullptr && sky.radiance != nullptr)
-		{
-			// If haven't assigned them already, do it now
-			if (sky.filteredReflections == nullptr)
-			{
-				if (!LightProbeCache::instance().isRadianceDirty(sky.skybox->getUUID()))
-					sky.filteredReflections = LightProbeCache::instance().getCachedRadianceTexture(sky.skybox->getUUID());
-				else
-				{
-					TEXTURE_DESC cubemapDesc;
-					cubemapDesc.type = TEX_TYPE_CUBE_MAP;
-					cubemapDesc.format = PF_FLOAT_R11G11B10;
-					cubemapDesc.width = IBLUtility::REFLECTION_CUBEMAP_SIZE;
-					cubemapDesc.height = IBLUtility::REFLECTION_CUBEMAP_SIZE;
-					cubemapDesc.numMips = PixelUtil::getMaxMipmaps(cubemapDesc.width, cubemapDesc.height, 1, cubemapDesc.format);
-					cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
-
-					sky.filteredReflections = Texture::create(cubemapDesc);
-
-					IBLUtility::scaleCubemap(sky.radiance, 0, sky.filteredReflections, 0);
-					IBLUtility::filterCubemapForSpecular(sky.filteredReflections, nullptr);
-					LightProbeCache::instance().setCachedRadianceTexture(sky.skybox->getUUID(), sky.filteredReflections);
-				}
-			}
-
-			if(sky.irradiance == nullptr)
-			{
-				if (!LightProbeCache::instance().isIrradianceDirty(sky.skybox->getUUID()))
-					sky.irradiance = LightProbeCache::instance().getCachedIrradianceTexture(sky.skybox->getUUID());
-				else
-				{
-					TEXTURE_DESC irradianceCubemapDesc;
-					irradianceCubemapDesc.type = TEX_TYPE_CUBE_MAP;
-					irradianceCubemapDesc.format = PF_FLOAT_R11G11B10;
-					irradianceCubemapDesc.width = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
-					irradianceCubemapDesc.height = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
-					irradianceCubemapDesc.numMips = 0;
-					irradianceCubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
-
-					sky.irradiance = Texture::create(irradianceCubemapDesc);
-
-					IBLUtility::filterCubemapForIrradiance(sky.filteredReflections, sky.irradiance);
-					LightProbeCache::instance().setCachedIrradianceTexture(sky.skybox->getUUID(), sky.filteredReflections);
-				}
-			}
-		}
-		else
-		{
-			sky.filteredReflections = nullptr;
-			sky.irradiance = nullptr;
-		}
-	}
+		// Get skybox image-based lighting textures if needed/available
+		if (sky.skybox != nullptr && sky.radiance != nullptr)
+		{
+			// If haven't assigned them already, do it now
+			if (sky.filteredReflections == nullptr)
+			{
+				if (!LightProbeCache::instance().isRadianceDirty(sky.skybox->getUUID()))
+					sky.filteredReflections = LightProbeCache::instance().getCachedRadianceTexture(sky.skybox->getUUID());
+				else
+				{
+					TEXTURE_DESC cubemapDesc;
+					cubemapDesc.type = TEX_TYPE_CUBE_MAP;
+					cubemapDesc.format = PF_FLOAT_R11G11B10;
+					cubemapDesc.width = IBLUtility::REFLECTION_CUBEMAP_SIZE;
+					cubemapDesc.height = IBLUtility::REFLECTION_CUBEMAP_SIZE;
+					cubemapDesc.numMips = PixelUtil::getMaxMipmaps(cubemapDesc.width, cubemapDesc.height, 1, cubemapDesc.format);
+					cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
+
+					sky.filteredReflections = Texture::create(cubemapDesc);
+
+					IBLUtility::scaleCubemap(sky.radiance, 0, sky.filteredReflections, 0);
+					IBLUtility::filterCubemapForSpecular(sky.filteredReflections, nullptr);
+					LightProbeCache::instance().setCachedRadianceTexture(sky.skybox->getUUID(), sky.filteredReflections);
+				}
+			}
+
+			if(sky.irradiance == nullptr)
+			{
+				if (!LightProbeCache::instance().isIrradianceDirty(sky.skybox->getUUID()))
+					sky.irradiance = LightProbeCache::instance().getCachedIrradianceTexture(sky.skybox->getUUID());
+				else
+				{
+					TEXTURE_DESC irradianceCubemapDesc;
+					irradianceCubemapDesc.type = TEX_TYPE_CUBE_MAP;
+					irradianceCubemapDesc.format = PF_FLOAT_R11G11B10;
+					irradianceCubemapDesc.width = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
+					irradianceCubemapDesc.height = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
+					irradianceCubemapDesc.numMips = 0;
+					irradianceCubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
+
+					sky.irradiance = Texture::create(irradianceCubemapDesc);
+
+					IBLUtility::filterCubemapForIrradiance(sky.filteredReflections, sky.irradiance);
+					LightProbeCache::instance().setCachedIrradianceTexture(sky.skybox->getUUID(), sky.filteredReflections);
+				}
+			}
+		}
+		else
+		{
+			sky.filteredReflections = nullptr;
+			sky.irradiance = nullptr;
+		}
+	}
 
 	void RenderBeast::captureSceneCubeMap(const SPtr<Texture>& cubemap, const Vector3& position, bool hdr, const FrameInfo& frameInfo)
 	{

+ 367 - 6
Source/RenderBeast/Source/BsRenderCompositor.cpp

@@ -11,11 +11,17 @@
 #include "BsCamera.h"
 #include "BsRendererScene.h"
 #include "BsIBLUtility.h"
+#include "BsRenderBeast.h"
 
 namespace bs { namespace ct
 {
 	UnorderedMap<StringID, RenderCompositor::NodeType*> RenderCompositor::mNodeTypes;
 
+	RenderCompositor::~RenderCompositor()
+	{
+		clear();
+	}
+
 	void RenderCompositor::build(const RendererView& view, const StringID& finalNode)
 	{
 		clear();
@@ -117,7 +123,7 @@ namespace bs { namespace ct
 	}
 
 	void RenderCompositor::execute(const RendererViewGroup& viewGroup, const RendererView& view, const SceneInfo& scene, 
-		const RenderBeastOptions& options) const
+		const FrameInfo& frameInfo, const RenderBeastOptions& options) const
 	{
 		if (!mIsValid)
 			return;
@@ -129,7 +135,7 @@ namespace bs { namespace ct
 			UINT32 idx = 0;
 			for (auto& entry : mNodeInfos)
 			{
-				RenderCompositorNodeInputs inputs(viewGroup, view, scene, options, entry.inputs);
+				RenderCompositorNodeInputs inputs(viewGroup, view, scene, options, frameInfo, entry.inputs);
 				entry.node->render(inputs);
 
 				activeNodes.push_back(&entry);
@@ -155,6 +161,15 @@ namespace bs { namespace ct
 			mNodeInfos.back().node->clear();
 	}
 
+	void RenderCompositor::clear()
+	{
+		for (auto& entry : mNodeInfos)
+			bs_delete(entry.node);
+
+		mNodeInfos.clear();
+		mIsValid = false;
+	}
+
 	void RCNodeSceneDepth::render(const RenderCompositorNodeInputs& inputs)
 	{
 		GpuResourcePool& resPool = GpuResourcePool::instance();
@@ -708,7 +723,20 @@ namespace bs { namespace ct
 	void RCNodeFinalResolve::render(const RenderCompositorNodeInputs& inputs)
 	{
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
-		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
+
+		SPtr<Texture> input;
+		if(viewProps.runPostProcessing)
+		{
+			RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[0]);
+
+			// Note: Ideally the last PP effect could write directly to the final target and we could avoid this copy
+			input = postProcessNode->getLastOutput();
+		}
+		else
+		{
+			RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
+			input = sceneColorNode->sceneColorTex->texture;
+		}
 
 		SPtr<RenderTarget> target = viewProps.target;
 
@@ -716,8 +744,7 @@ namespace bs { namespace ct
 		rapi.setRenderTarget(target);
 		rapi.setViewport(viewProps.nrmViewRect);
 
-		SPtr<Texture> sceneColor = sceneColorNode->sceneColorTex->texture;
-		gRendererUtility().blit(sceneColor, Rect2I::EMPTY, viewProps.flipView);
+		gRendererUtility().blit(input, Rect2I::EMPTY, viewProps.flipView);
 	}
 
 	void RCNodeFinalResolve::clear()
@@ -725,6 +752,340 @@ namespace bs { namespace ct
 
 	SmallVector<StringID, 4> RCNodeFinalResolve::getDependencies(const RendererView& view)
 	{
-		return{ RCNodeSceneColor::getNodeId(), RCNodeSkybox::getNodeId() };
+		const RendererViewProperties& viewProps = view.getProperties();
+
+		SmallVector<StringID, 4> deps;
+		if(viewProps.runPostProcessing)
+		{
+			deps.push_back(RCNodePostProcess::getNodeId());
+			deps.push_back(RCNodeFXAA::getNodeId());
+		}
+		else
+		{
+			deps.push_back(RCNodeSceneColor::getNodeId());
+			deps.push_back(RCNodeSkybox::getNodeId());
+			
+		}
+
+		return deps;
+	}
+
+	RCNodePostProcess::RCNodePostProcess()
+		:mOutput(), mAllocated()
+	{ }
+
+	void RCNodePostProcess::getAndSwitch(const RendererView& view, SPtr<RenderTexture>& output, SPtr<Texture>& lastFrame) const
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+
+		const RendererViewProperties& viewProps = view.getProperties();
+		UINT32 width = viewProps.viewRect.width;
+		UINT32 height = viewProps.viewRect.height;
+
+		if(!mAllocated[mCurrentIdx])
+		{
+			mOutput[mCurrentIdx] = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8G8B8A8, width, height,
+					TU_RENDERTARGET, 1, false));
+
+			mAllocated[mCurrentIdx] = true;
+		}
+
+		output = mOutput[mCurrentIdx]->renderTexture;
+
+		UINT32 otherIdx = (mCurrentIdx + 1) % 2;
+		if (mAllocated[otherIdx])
+			lastFrame = mOutput[otherIdx]->texture;
+
+		mCurrentIdx = otherIdx;
+	}
+
+	SPtr<Texture> RCNodePostProcess::getLastOutput() const
+	{
+		UINT32 otherIdx = (mCurrentIdx + 1) % 2;
+		if (mAllocated[otherIdx])
+			return mOutput[otherIdx]->texture;
+		
+		return nullptr;
+	}
+
+	void RCNodePostProcess::render(const RenderCompositorNodeInputs& inputs)
+	{
+		// Do nothing, this is just a helper node
+	}
+
+	void RCNodePostProcess::clear()
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+
+		if (mAllocated[0])
+			resPool.release(mOutput[0]);
+
+		if (mAllocated[1])
+			resPool.release(mOutput[1]);
+
+		mAllocated[0] = false;
+		mAllocated[1] = false;
+		mCurrentIdx = 0;
+	}
+
+	SmallVector<StringID, 4> RCNodePostProcess::getDependencies(const RendererView& view)
+	{
+		return {};
+	}
+
+	RCNodeTonemapping::~RCNodeTonemapping()
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+
+		if (mTonemapLUT)
+			resPool.release(mTonemapLUT);
+
+		if (prevEyeAdaptation)
+			resPool.release(prevEyeAdaptation);
+	}
+
+	void RCNodeTonemapping::render(const RenderCompositorNodeInputs& inputs)
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+
+		const RendererViewProperties& viewProps = inputs.view.getProperties();
+		const PostProcessInfo& ppInfo = inputs.view.getPPInfo();
+		const StandardPostProcessSettings& settings = *inputs.view.getPPInfo().settings;
+
+		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
+		RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[2]);
+		SPtr<Texture> sceneColor = sceneColorNode->sceneColorTex->texture;
+
+		bool hdr = viewProps.isHDR;
+		bool msaa = viewProps.numSamples > 1;
+
+		if(hdr && settings.enableAutoExposure)
+		{
+			// Downsample scene
+			DownsampleMat* downsampleMat = DownsampleMat::getVariation(1, msaa);
+			SPtr<PooledRenderTexture> downsampledScene = resPool.get(DownsampleMat::getOutputDesc(sceneColor));
+
+			downsampleMat->execute(sceneColor, downsampledScene->renderTexture);
+
+			// Generate histogram
+			SPtr<PooledRenderTexture> eyeAdaptHistogram = resPool.get(
+				EyeAdaptHistogramMat::getOutputDesc(downsampledScene->texture));
+			EyeAdaptHistogramMat* eyeAdaptHistogramMat = EyeAdaptHistogramMat::get();
+			eyeAdaptHistogramMat->execute(downsampledScene->texture, eyeAdaptHistogram->texture, settings.autoExposure);
+
+			// Reduce histogram
+			SPtr<PooledRenderTexture> reducedHistogram = resPool.get(EyeAdaptHistogramReduceMat::getOutputDesc());
+
+			SPtr<Texture> prevFrameEyeAdaptation;
+			if (prevEyeAdaptation != nullptr)
+				prevFrameEyeAdaptation = prevEyeAdaptation->texture;
+
+			EyeAdaptHistogramReduceMat* eyeAdaptHistogramReduce = EyeAdaptHistogramReduceMat::get();
+			eyeAdaptHistogramReduce->execute(downsampledScene->texture, eyeAdaptHistogram->texture, 
+				prevFrameEyeAdaptation, reducedHistogram->renderTexture);
+
+			resPool.release(downsampledScene);
+			downsampledScene = nullptr;
+
+			resPool.release(eyeAdaptHistogram);
+			eyeAdaptHistogram = nullptr;
+
+			// Generate eye adaptation value
+			eyeAdaptation = resPool.get(EyeAdaptationMat::getOutputDesc());
+			EyeAdaptationMat* eyeAdaptationMat = EyeAdaptationMat::get();
+			eyeAdaptationMat->execute(reducedHistogram->texture, eyeAdaptation->renderTexture, inputs.frameInfo.timeDelta,
+				settings.autoExposure, settings.exposureScale);
+
+			resPool.release(reducedHistogram);
+			reducedHistogram = nullptr;
+		}
+		else
+		{
+			if(prevEyeAdaptation)
+				resPool.release(prevEyeAdaptation);
+
+			prevEyeAdaptation = nullptr;
+			eyeAdaptation = nullptr;
+		}
+
+		bool gammaOnly;
+		bool autoExposure;
+		if (hdr)
+		{
+			if (settings.enableTonemapping)
+			{
+				if (ppInfo.settingDirty) // Rebuild LUT if PP settings changed
+				{
+					if(mTonemapLUT == nullptr)
+						mTonemapLUT = resPool.get(CreateTonemapLUTMat::getOutputDesc());
+
+					CreateTonemapLUTMat* createLUT = CreateTonemapLUTMat::get();
+					createLUT->execute(mTonemapLUT->texture, settings);
+				}
+
+				gammaOnly = false;
+			}
+			else
+				gammaOnly = true;
+
+			autoExposure = settings.enableAutoExposure;
+		}
+		else
+		{
+			gammaOnly = true;
+			autoExposure = false;
+		}
+
+		if(gammaOnly)
+		{
+			if(mTonemapLUT)
+			{
+				resPool.release(mTonemapLUT);
+				mTonemapLUT = nullptr;
+			}
+		}
+
+		TonemappingMat* tonemapping = TonemappingMat::getVariation(gammaOnly, autoExposure, msaa);
+
+		SPtr<RenderTexture> ppOutput;
+		SPtr<Texture> ppLastFrame;
+		postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
+
+		SPtr<Texture> eyeAdaptationTex;
+		if (eyeAdaptation)
+			eyeAdaptationTex = eyeAdaptation->texture;
+
+		SPtr<Texture> tonemapLUTTex;
+		if (mTonemapLUT)
+			tonemapLUTTex = mTonemapLUT->texture;
+
+		tonemapping->execute(sceneColor, eyeAdaptationTex, tonemapLUTTex, ppOutput, settings);
+	}
+
+	void RCNodeTonemapping::clear()
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+
+		// Save eye adaptation for next frame
+		if(prevEyeAdaptation)
+			resPool.release(prevEyeAdaptation);
+
+		std::swap(eyeAdaptation, prevEyeAdaptation);
+	}
+
+	SmallVector<StringID, 4> RCNodeTonemapping::getDependencies(const RendererView& view)
+	{
+		return{ RCNodeSceneColor::getNodeId(), RCNodeSkybox::getNodeId(), RCNodePostProcess::getNodeId() };
+	}
+
+	void RCNodeGaussianDOF::render(const RenderCompositorNodeInputs& inputs)
+	{
+		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
+		RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[2]);
+
+		DepthOfFieldSettings& settings = inputs.view.getPPInfo().settings->depthOfField;
+		bool near = settings.nearBlurAmount > 0.0f;
+		bool far = settings.farBlurAmount > 0.0f;
+
+		bool enabled = settings.enabled && (near || far);
+		if(!enabled)
+			return;
+
+		GaussianDOFSeparateMat* separateMat = GaussianDOFSeparateMat::getVariation(near, far);
+		GaussianDOFCombineMat* combineMat = GaussianDOFCombineMat::getVariation(near, far);
+		GaussianBlurMat* blurMat = GaussianBlurMat::get();
+
+		SPtr<RenderTexture> ppOutput;
+		SPtr<Texture> ppLastFrame;
+		postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
+
+		separateMat->execute(ppLastFrame, sceneDepthNode->depthTex->texture, inputs.view, settings);
+
+		SPtr<PooledRenderTexture> nearTex, farTex;
+		if(near && far)
+		{
+			nearTex = separateMat->getOutput(0);
+			farTex = separateMat->getOutput(1);
+		}
+		else
+		{
+			if (near)
+				nearTex = separateMat->getOutput(0);
+			else
+				farTex = separateMat->getOutput(0);
+		}
+
+		// Blur the out of focus pixels
+		// Note: Perhaps set up stencil so I can avoid performing blur on unused parts of the textures?
+		const TextureProperties& texProps = nearTex ? nearTex->texture->getProperties() : farTex->texture->getProperties();
+		POOLED_RENDER_TEXTURE_DESC tempTexDesc = POOLED_RENDER_TEXTURE_DESC::create2D(texProps.getFormat(), 
+			texProps.getWidth(), texProps.getHeight(), TU_RENDERTARGET);
+		SPtr<PooledRenderTexture> tempTexture = GpuResourcePool::instance().get(tempTexDesc);
+
+		SPtr<Texture> blurredNearTex;
+		if(nearTex)
+		{
+			blurMat->execute(nearTex->texture, settings.nearBlurAmount, tempTexture->renderTexture);
+			blurredNearTex = tempTexture->texture;
+		}
+
+		SPtr<Texture> blurredFarTex;
+		if(farTex)
+		{
+			// If temporary texture is used up, re-use the original near texture for the blurred result
+			if(blurredNearTex)
+			{
+				blurMat->execute(farTex->texture, settings.farBlurAmount, nearTex->renderTexture);
+				blurredFarTex = nearTex->texture;
+			}
+			else // Otherwise just use the temporary
+			{
+				blurMat->execute(farTex->texture, settings.farBlurAmount, tempTexture->renderTexture);
+				blurredFarTex = tempTexture->texture;
+			}
+		}
+
+		combineMat->execute(ppLastFrame, blurredNearTex, blurredFarTex, 
+			sceneDepthNode->depthTex->texture, ppOutput, inputs.view, settings);
+
+		separateMat->release();
+		GpuResourcePool::instance().release(tempTexture);
+	}
+
+	void RCNodeGaussianDOF::clear()
+	{
+		// Do nothing
+	}
+
+	SmallVector<StringID, 4> RCNodeGaussianDOF::getDependencies(const RendererView& view)
+	{
+		return { RCNodeTonemapping::getNodeId(), RCNodeSceneDepth::getNodeId(), RCNodePostProcess::getNodeId() };
+	}
+
+	void RCNodeFXAA::render(const RenderCompositorNodeInputs& inputs)
+	{
+		const StandardPostProcessSettings& settings = *inputs.view.getPPInfo().settings;
+		if (!settings.enableFXAA)
+			return;
+
+		RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[1]);
+
+		SPtr<RenderTexture> ppOutput;
+		SPtr<Texture> ppLastFrame;
+		postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
+
+		// Note: I could skip executing FXAA over DOF and motion blurred pixels
+		FXAAMat* fxaa = FXAAMat::get();
+		fxaa->execute(ppLastFrame, ppOutput);
+	}
+
+	void RCNodeFXAA::clear()
+	{
+		// Do nothing
+	}
+
+	SmallVector<StringID, 4> RCNodeFXAA::getDependencies(const RendererView& view)
+	{
+		return { RCNodeGaussianDOF::getNodeId(), RCNodePostProcess::getNodeId() };
 	}
 }}