// Naive approach: 6 passes
        protected void DrawCoreNaive(RenderDrawContext context)
        {
            var originalTexture = GetSafeInput(0);
            var outputTexture   = GetSafeOutput(0);

            if (rhombiTapOffsetsDirty)
            {
                CalculateRhombiOffsets();
            }

            var tapNumber = 2 * tapCount - 1;

            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.Count, tapCount);
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurKeys.TotalTap, tapNumber);
            directionalBlurEffect.EffectInstance.UpdateEffect(context.GraphicsDevice);
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Radius, Radius);
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.TapWeights, tapWeights);
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.CoCReference, CoCStrength);

            // Vertical blur
            var blurAngle = MathUtil.PiOverTwo + Phase;

            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle)));

            var verticalBlurTexture = NewScopedRenderTarget2D(originalTexture.Description);

            directionalBlurEffect.SetInput(0, originalTexture);
            directionalBlurEffect.SetOutput(verticalBlurTexture);
            directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiABVertical_tap{0}_radius{1}", tapNumber, (int)Radius);

            // Rhombi A (top left)
            blurAngle = 7f * MathUtil.Pi / 6f + Phase;
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle)));

            var rhombiA = NewScopedRenderTarget2D(originalTexture.Description);

            directionalBlurEffect.SetInput(0, verticalBlurTexture);
            directionalBlurEffect.SetOutput(rhombiA);
            directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiA_tap{0}_radius{1}", tapNumber, (int)Radius);

            // Rhombi B (top right)
            blurAngle = -MathUtil.Pi / 6f + Phase;
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle)));

            var rhombiB = NewScopedRenderTarget2D(originalTexture.Description);

            directionalBlurEffect.SetInput(0, verticalBlurTexture);
            directionalBlurEffect.SetOutput(rhombiB);
            directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiB_tap{0}_radius{1}", tapNumber, (int)Radius);

            //Rhombi C (bottom)
            blurAngle = 7f * MathUtil.Pi / 6f + Phase;
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle)));

            var rhombiCTmp = NewScopedRenderTarget2D(originalTexture.Description);

            directionalBlurEffect.SetInput(0, originalTexture);
            directionalBlurEffect.SetOutput(rhombiCTmp);
            directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiCTmp_tap{0}_radius{1}", tapNumber, (int)Radius);

            blurAngle = -MathUtil.Pi / 6f + Phase;
            directionalBlurEffect.Parameters.Set(DepthAwareDirectionalBlurUtilKeys.Direction, new Vector2((float)Math.Cos(blurAngle), (float)Math.Sin(blurAngle)));

            var rhombiC = NewScopedRenderTarget2D(originalTexture.Description);

            directionalBlurEffect.SetInput(0, rhombiCTmp);
            directionalBlurEffect.SetOutput(rhombiC);
            directionalBlurEffect.Draw(context, "TripleRhombiBokeh_RhombiC_tap{0}_radius{1}", tapNumber, (int)Radius);

            // Final pass outputting the average of the 3 blurs
            finalCombineEffect.SetInput(0, rhombiA);
            finalCombineEffect.SetInput(1, rhombiB);
            finalCombineEffect.SetInput(2, rhombiC);
            finalCombineEffect.SetOutput(outputTexture);
            finalCombineEffect.Parameters.Set(TripleRhombiCombineShaderKeys.RhombiTapOffsets, rhombiTapOffsets);
            finalCombineEffect.Draw(context, name: "TripleRhombiBokehCombine");
        }
        protected override void DrawCore(RenderDrawContext context)
        {
            // Inputs:
            Texture colorBuffer             = GetSafeInput(0);
            Texture depthBuffer             = GetSafeInput(1);
            Texture normalsBuffer           = GetSafeInput(2);
            Texture specularRoughnessBuffer = GetSafeInput(3);

            // Output:
            Texture outputBuffer = GetSafeOutput(0);

            // Prepare
            var temporalCache = Prepare(context, outputBuffer);

            FlushCache(context.RenderContext.Time.FrameCount);

            // Get temporary buffers
            var     rayTraceBuffersSize = GetBufferResolution(outputBuffer, RayTracePassResolution);
            var     resolveBuffersSize  = GetBufferResolution(outputBuffer, ResolvePassResolution);
            Texture rayTraceBuffer      = NewScopedRenderTarget2D(rayTraceBuffersSize.Width, rayTraceBuffersSize.Height, RayTraceTargetFormat, 1);
            Texture resolveBuffer       = NewScopedRenderTarget2D(resolveBuffersSize.Width, resolveBuffersSize.Height, ReflectionsFormat, 1);

            // Check if resize depth
            Texture smallerDepthBuffer = depthBuffer;

            if (DepthResolution != ResolutionMode.Full)
            {
                // Smaller depth buffer improves ray tracing performance.

                var depthBuffersSize = GetBufferResolution(depthBuffer, DepthResolution);
                smallerDepthBuffer = NewScopedRenderTarget2D(depthBuffersSize.Width, depthBuffersSize.Height, PixelFormat.R32_Float, 1);

                depthPassShader.SetInput(0, depthBuffer);
                depthPassShader.SetOutput(smallerDepthBuffer);
                depthPassShader.Draw(context, "Downscale Depth");
            }

            // Blur Pass
            Texture blurPassBuffer;

            if (UseColorBufferMips)
            {
                // Note: using color buffer mips maps helps with reducing artifacts
                // and improves resolve pass performance (faster color texture lookups, less cache misses)
                // Also for high surface roughness values it adds more blur to the reflection tail which looks more realistic.

                // Get temp targets
                var     colorBuffersSize      = new Size2(outputBuffer.Width / 2, outputBuffer.Height / 2);
                int     colorBuffersMips      = Texture.CalculateMipMapCount(MipMapCount.Auto, colorBuffersSize.Width, colorBuffersSize.Height);
                Texture colorBuffer0          = NewScopedRenderTarget2D(colorBuffersSize.Width, colorBuffersSize.Height, ReflectionsFormat, colorBuffersMips);
                Texture colorBuffer1          = NewScopedRenderTarget2D(colorBuffersSize.Width / 2, colorBuffersSize.Height / 2, ReflectionsFormat, colorBuffersMips - 1);
                int     colorBuffer1MipOffset = 1; // For colorBuffer1 we could use one mip less (optimized)

                // Cache per color buffer mip views
                int colorBuffer0Mips = colorBuffer0.MipLevels;
                if (cachedColorBuffer0Mips == null || cachedColorBuffer0Mips.Length != colorBuffer0Mips || cachedColorBuffer0Mips[0].ParentTexture != colorBuffer0)
                {
                    cachedColorBuffer0Mips?.ForEach(view => view?.Dispose());
                    cachedColorBuffer0Mips = new Texture[colorBuffer0Mips];
                    for (int mipIndex = 0; mipIndex < colorBuffer0Mips; mipIndex++)
                    {
                        cachedColorBuffer0Mips[mipIndex] = colorBuffer0.ToTextureView(ViewType.Single, 0, mipIndex);
                    }
                }
                int colorBuffer1Mips = colorBuffer1.MipLevels;
                if (cachedColorBuffer1Mips == null || cachedColorBuffer1Mips.Length != colorBuffer1Mips || cachedColorBuffer1Mips[0].ParentTexture != colorBuffer1)
                {
                    cachedColorBuffer1Mips?.ForEach(view => view?.Dispose());
                    cachedColorBuffer1Mips = new Texture[colorBuffer1Mips];
                    for (int mipIndex = 0; mipIndex < colorBuffer1Mips; mipIndex++)
                    {
                        cachedColorBuffer1Mips[mipIndex] = colorBuffer1.ToTextureView(ViewType.Single, 0, mipIndex);
                    }
                }

                // Clone scene frame to mip 0 of colorBuffer0
                Scaler.SetInput(0, colorBuffer);
                Scaler.SetOutput(cachedColorBuffer0Mips[0]);
                Scaler.Draw(context, "Copy frame");

                // Downscale with gaussian blur
                for (int mipLevel = 1; mipLevel < colorBuffersMips; mipLevel++)
                {
                    // Blur H
                    var srcMip = cachedColorBuffer0Mips[mipLevel - 1];
                    var dstMip = cachedColorBuffer1Mips[mipLevel - colorBuffer1MipOffset];
                    blurPassShaderH.SetInput(0, srcMip);
                    blurPassShaderH.SetOutput(dstMip);
                    blurPassShaderH.Draw(context, "Blur H");

                    // Blur V
                    srcMip = dstMip;
                    dstMip = cachedColorBuffer0Mips[mipLevel];
                    blurPassShaderV.SetInput(0, srcMip);
                    blurPassShaderV.SetOutput(dstMip);
                    blurPassShaderV.Draw(context, "Blur V");
                }

                blurPassBuffer = colorBuffer0;
            }
            else
            {
                // Don't use color buffer with mip maps
                blurPassBuffer = colorBuffer;

                cachedColorBuffer0Mips?.ForEach(view => view?.Dispose());
                cachedColorBuffer1Mips?.ForEach(view => view?.Dispose());
            }

            // Ray Trace Pass
            rayTracePassShader.SetInput(0, colorBuffer);
            rayTracePassShader.SetInput(1, smallerDepthBuffer);
            rayTracePassShader.SetInput(2, normalsBuffer);
            rayTracePassShader.SetInput(3, specularRoughnessBuffer);
            rayTracePassShader.SetOutput(rayTraceBuffer);
            rayTracePassShader.Draw(context, "Ray Trace");

            // Resolve Pass
            resolvePassShader.SetInput(0, blurPassBuffer);
            resolvePassShader.SetInput(1, ResolvePassResolution == ResolutionMode.Full ? depthBuffer : smallerDepthBuffer);
            resolvePassShader.SetInput(2, normalsBuffer);
            resolvePassShader.SetInput(3, specularRoughnessBuffer);
            resolvePassShader.SetInput(4, rayTraceBuffer);
            resolvePassShader.SetOutput(resolveBuffer);
            resolvePassShader.Draw(context, "Resolve");

            // Temporal Pass
            Texture reflectionsBuffer = resolveBuffer;

            if (TemporalEffect)
            {
                var temporalSize = outputBuffer.Size;
                temporalCache.Resize(GraphicsDevice, ref temporalSize);
                Texture temporalBuffer0 = NewScopedRenderTarget2D(temporalSize.Width, temporalSize.Height, ReflectionsFormat, 1);

                temporalPassShader.SetInput(0, resolveBuffer);
                temporalPassShader.SetInput(1, temporalCache.TemporalBuffer);
                temporalPassShader.SetInput(2, depthBuffer);
                temporalPassShader.SetOutput(temporalBuffer0);
                temporalPassShader.Draw(context, "Temporal");

                context.CommandList.Copy(temporalBuffer0, temporalCache.TemporalBuffer); // TODO: use Texture.Swap from ContentStreaming branch to make it faster!

                reflectionsBuffer = temporalCache.TemporalBuffer;
            }

            // Combine Pass
            combinePassShader.SetInput(0, colorBuffer);
            combinePassShader.SetInput(1, depthBuffer);
            combinePassShader.SetInput(2, normalsBuffer);
            combinePassShader.SetInput(3, specularRoughnessBuffer);
            combinePassShader.SetInput(4, reflectionsBuffer);
            combinePassShader.SetOutput(outputBuffer);
            combinePassShader.Draw(context, "Combine");

#if SSLR_DEBUG
            if (DebugMode != DebugModes.None)
            {
                // Debug preview of temp targets
                switch (DebugMode)
                {
                case DebugModes.RayTrace:
                    Scaler.SetInput(0, rayTraceBuffer);
                    break;

                case DebugModes.Resolve:
                    Scaler.SetInput(0, resolveBuffer);
                    break;

                case DebugModes.Temporal:
                    if (temporalCache != null)
                    {
                        Scaler.SetInput(0, temporalCache.TemporalBuffer);
                    }
                    break;
                }
                Scaler.SetOutput(outputBuffer);
                Scaler.Draw(context);
            }
#endif
        }
        protected override void DrawCore(RenderDrawContext context)
        {
            var originalColorBuffer = GetSafeInput(0);
            var originalDepthBuffer = GetSafeInput(1);

            var outputTexture = GetSafeOutput(0);

            var renderView = context.RenderContext.RenderView;

            //---------------------------------
            // Ambient Occlusion
            //---------------------------------

            var tempWidth  = (originalColorBuffer.Width * (int)TempSize) / (int)TemporaryBufferSize.SizeFull;
            var tempHeight = (originalColorBuffer.Height * (int)TempSize) / (int)TemporaryBufferSize.SizeFull;
            var aoTexture1 = NewScopedRenderTarget2D(tempWidth, tempHeight, PixelFormat.R8_UNorm, 1);
            var aoTexture2 = NewScopedRenderTarget2D(tempWidth, tempHeight, PixelFormat.R8_UNorm, 1);

            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOKeys.Count, NumberOfSamples > 0 ? NumberOfSamples : 9);

            // Set Near/Far pre-calculated factors to speed up the linear depth reconstruction
            aoRawImageEffect.Parameters.Set(CameraKeys.ZProjection, CameraKeys.ZProjectionACalculate(renderView.NearClipPlane, renderView.FarClipPlane));

            Vector4 screenSize = new Vector4(originalColorBuffer.Width, originalColorBuffer.Height, 0, 0);

            screenSize.Z = screenSize.X / screenSize.Y;
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ScreenInfo, screenSize);

            // Projection infor used to reconstruct the View space position from linear depth
            var     p00      = renderView.Projection.M11;
            var     p11      = renderView.Projection.M22;
            var     p02      = renderView.Projection.M13;
            var     p12      = renderView.Projection.M23;
            Vector4 projInfo = new Vector4(-2.0f / (screenSize.X * p00), -2.0f / (screenSize.Y * p11), (1.0f - p02) / p00, (1.0f + p12) / p11);

            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ProjInfo, projInfo);

            //**********************************
            // User parameters
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ParamProjScale, ParamProjScale);
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ParamIntensity, ParamIntensity);
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ParamBias, ParamBias);
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ParamRadius, ParamRadius);
            aoRawImageEffect.Parameters.Set(AmbientOcclusionRawAOShaderKeys.ParamRadiusSquared, ParamRadius * ParamRadius);

            aoRawImageEffect.SetInput(0, originalDepthBuffer);
            aoRawImageEffect.SetOutput(aoTexture1);
            aoRawImageEffect.Draw(context, "AmbientOcclusionRawAO");

            for (int bounces = 0; bounces < NumberOfBounces; bounces++)
            {
                if (offsetsWeights == null)
                {
                    offsetsWeights = new[]
                    {
                        //  0.356642f, 0.239400f, 0.072410f, 0.009869f,
                        //  0.398943f, 0.241971f, 0.053991f, 0.004432f, 0.000134f, // stddev = 1.0
                        0.153170f, 0.144893f, 0.122649f, 0.092902f, 0.062970f,     // stddev = 2.0
                        //  0.111220f, 0.107798f, 0.098151f, 0.083953f, 0.067458f, 0.050920f, 0.036108f, // stddev = 3.0
                    };

                    nameGaussianBlurH = string.Format("AmbientOcclusionBlurH{0}x{0}", offsetsWeights.Length);
                    nameGaussianBlurV = string.Format("AmbientOcclusionBlurV{0}x{0}", offsetsWeights.Length);
                }

                // Set Near/Far pre-calculated factors to speed up the linear depth reconstruction
                var zProj = CameraKeys.ZProjectionACalculate(renderView.NearClipPlane, renderView.FarClipPlane);
                blurH.Parameters.Set(CameraKeys.ZProjection, ref zProj);
                blurV.Parameters.Set(CameraKeys.ZProjection, ref zProj);

                // Update permutation parameters
                blurH.Parameters.Set(AmbientOcclusionBlurKeys.Count, offsetsWeights.Length);
                blurH.Parameters.Set(AmbientOcclusionBlurKeys.BlurScale, BlurScale);
                blurH.Parameters.Set(AmbientOcclusionBlurKeys.EdgeSharpness, EdgeSharpness);
                blurH.EffectInstance.UpdateEffect(context.GraphicsDevice);

                blurV.Parameters.Set(AmbientOcclusionBlurKeys.Count, offsetsWeights.Length);
                blurV.Parameters.Set(AmbientOcclusionBlurKeys.BlurScale, BlurScale);
                blurV.Parameters.Set(AmbientOcclusionBlurKeys.EdgeSharpness, EdgeSharpness);
                blurV.EffectInstance.UpdateEffect(context.GraphicsDevice);

                // Update parameters
                blurH.Parameters.Set(AmbientOcclusionBlurShaderKeys.Weights, offsetsWeights);
                blurV.Parameters.Set(AmbientOcclusionBlurShaderKeys.Weights, offsetsWeights);

                // Horizontal pass
                blurH.SetInput(0, aoTexture1);
                blurH.SetInput(1, originalDepthBuffer);
                blurH.SetOutput(aoTexture2);
                blurH.Draw(context, nameGaussianBlurH);

                // Vertical pass
                blurV.SetInput(0, aoTexture2);
                blurV.SetInput(1, originalDepthBuffer);
                blurV.SetOutput(aoTexture1);
                blurV.Draw(context, nameGaussianBlurV);
            }

            aoApplyImageEffect.SetInput(0, originalColorBuffer);
            aoApplyImageEffect.SetInput(1, aoTexture1);
            aoApplyImageEffect.SetOutput(outputTexture);
            aoApplyImageEffect.Draw(context, "AmbientOcclusionApply");
        }