Beispiel #1
0
        protected override async Task LoadContent()
        {
            await base.LoadContent();

            effectInstance = new DynamicEffectInstance("CustomEffect.CustomSubEffect");
            effectInstance.Initialize(Services);
        }
Beispiel #2
0
        public ComputeEffectNode(NodeContext nodeContext, EffectNodeDescription description) : base(nodeContext, description)
        {
            graphicsDevice = description.Factory.DeviceManager.GraphicsDevice;
            instance       = new DynamicEffectInstance("ComputeEffectShader");
            // TODO: Same code as in description
            instance.Parameters.Set(ComputeEffectShaderKeys.ComputeShaderName, description.Name);
            instance.Parameters.Set(ComputeEffectShaderKeys.ThreadNumbers, new Int3(1));
            instance.Initialize(description.Factory.ServiceRegistry);
            instance.UpdateEffect(graphicsDevice);
            parameters     = instance.Parameters;
            Inputs         = description.CreateNodeInputs(this, parameters);
            Outputs        = description.CreateNodeOutputs(this, parameters);
            perFrameParams = parameters.GetWellKnownParameters(WellKnownParameters.PerFrameMap).ToArray();
            perViewParams  = parameters.GetWellKnownParameters(WellKnownParameters.PerViewMap).ToArray();
            perDrawParams  = parameters.GetWellKnownParameters(WellKnownParameters.PerDrawMap).ToArray();
            if (perDrawParams.Length > 0)
            {
                worldPin = Inputs.OfType <ConvertedValueParameterPin <Matrix, SharpDX.Matrix> >().FirstOrDefault(p => p.Key == TransformationKeys.World);
            }

            Inputs.SelectPin(EffectNodeDescription.ComputeDispatchCountInput, ref dispatchCountPin);
            Inputs.SelectPin(EffectNodeDescription.ComputeThreadNumbersInput, ref threadNumbersPin);
            Inputs.SelectPin(EffectNodeDescription.ComputeIterationCountInput, ref iterationCountPin);
            Inputs.SelectPin(EffectNodeDescription.ParameterSetterInput, ref parameterSetterPin);
            Inputs.SelectPin(EffectNodeDescription.ComputeIterationParameterSetterInput, ref iterationParameterSetterPin);
            Inputs.SelectPin(EffectNodeDescription.ComputeEnabledInput, ref enabledPin);
        }
        protected override void InitializeCore()
        {
            base.InitializeCore();

            _skyViewLutTexture = Texture.New2D(Context.GraphicsDevice, SkyViewLutSettings.Width, SkyViewLutSettings.Height, SkyViewLutSettings.Format, TextureFlags.RenderTarget | TextureFlags.ShaderResource);
            _atmosphereCameraScatteringVolumeTexture = Texture.New3D(Context.GraphicsDevice, AtmosphereCameraScatteringVolumeSettings.Size, AtmosphereCameraScatteringVolumeSettings.Size, AtmosphereCameraScatteringVolumeSettings.Slices, AtmosphereCameraScatteringVolumeSettings.Format, TextureFlags.RenderTarget | TextureFlags.ShaderResource);
            _multiScatteringTexture        = Texture.New2D(Context.GraphicsDevice, MultiScatteringTextureSettings.Size, MultiScatteringTextureSettings.Size, MultiScatteringTextureSettings.Format, TextureFlags.UnorderedAccess | TextureFlags.ShaderResource);
            TransmittanceLutTexture        = Texture.New2D(Context.GraphicsDevice, TransmittanceLutSettings.Width, TransmittanceLutSettings.Height, TransmittanceLutSettings.Format, TextureFlags.UnorderedAccess | TextureFlags.RenderTarget | TextureFlags.ShaderResource);
            _atmosphereCubeMapRenderTarget = Texture.New2D(Context.GraphicsDevice, 64, 64, PixelFormat.R16G16B16A16_Float, TextureFlags.RenderTarget | TextureFlags.ShaderResource);
            _atmosphereCubeMap             = Texture.NewCube(Context.GraphicsDevice, 64, PixelFormat.R16G16B16A16_Float, TextureFlags.ShaderResource);
            _atmosphereCubeMapSpecular     = Texture.NewCube(Context.GraphicsDevice, 64, MipMapCount.Auto, PixelFormat.R16G16B16A16_Float, TextureFlags.ShaderResource | TextureFlags.UnorderedAccess);

            _transmittanceLutEffect = new ImageEffectShader("AtmosphereRenderTransmittanceLutEffect");
            _skyViewLutEffect       = new ImageEffectShader("AtmosphereRenderSkyViewLutEffect");
            _renderMultipleScatteringTextureEffect = new ComputeEffectShader(Context)
            {
                ShaderSourceName = "AtmosphereMultipleScatteringTextureEffect"
            };

            _renderAtmosphereScatteringVolumeEffect = new DynamicEffectInstance("AtmosphereRenderScatteringCameraVolumeEffect");
            _renderAtmosphereScatteringVolumeEffect.Initialize(Context.Services);

            _renderAtmosphereScatteringVolumePipelineState = new MutablePipelineState(Context.GraphicsDevice);
            _renderAtmosphereScatteringVolumePipelineState.State.SetDefaults();
            _renderAtmosphereScatteringVolumePipelineState.State.PrimitiveType = PrimitiveType.TriangleList;

            _atmosphereLogicalGroupKey = CreateDrawLogicalGroup("Atmosphere");

            _spriteBatch = new SpriteBatch(Context.GraphicsDevice);
        }
Beispiel #4
0
        protected override void InitializeCore()
        {
            base.InitializeCore();

            // Initalize the shader
            _decalShader = new DynamicEffectInstance("DecalShader");
            _decalShader.Initialize(Context.Services);
        }
Beispiel #5
0
        protected override Task Initialize()
        {
            specificMipmapSamplerState = SamplerState.New(Game.GraphicsDevice, new SamplerStateDescription(TextureFilter.ComparisonPoint, TextureAddressMode.Clamp));

            currentEffect.Initialize(Game.Services);

            return(base.Initialize());
        }
Beispiel #6
0
        //private Matrix viewprojection = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);


        protected override void InitializeCore()
        {
            base.InitializeCore();
            myCustomShader = new DynamicEffectInstance("test_shader1");
            myCustomShader.Initialize(Context.Services);
            pipelineState = new MutablePipelineState(Context.GraphicsDevice);
            pipelineState.State.SetDefaults();
            pipelineState.State.InputElements            = VertexDeclaration.CreateInputElements();
            pipelineState.State.PrimitiveType            = PrimitiveType.TriangleStrip;
            pipelineState.State.BlendState               = BlendStates.Default;
            pipelineState.State.RasterizerState.CullMode = CullMode.None;
        }
Beispiel #7
0
        protected override void InitializeCore()
        {
            base.InitializeCore();

            // Light accumulation shader
            lightShaftsEffectShader = ToLoadAndUnload(new ImageEffectShader("LightShaftsEffect"));

            // Additive blending shader
            applyLightEffectShader            = ToLoadAndUnload(new ImageEffectShader("AdditiveLightEffect"));
            applyLightEffectShader.BlendState = new BlendStateDescription(Blend.One, Blend.One);

            minmaxVolumeEffectShader = new DynamicEffectInstance("VolumeMinMaxShader");
            minmaxVolumeEffectShader.Initialize(Context.Services);

            blur = ToLoadAndUnload(new GaussianBlur());

            // Need the shadow map renderer in order to render light shafts
            var meshRenderFeature = Context.RenderSystem.RenderFeatures.OfType <MeshRenderFeature>().FirstOrDefault();

            if (meshRenderFeature == null)
            {
                throw new InvalidOperationException("Missing mesh render feature");
            }

            var forwardLightingFeature = meshRenderFeature.RenderFeatures.OfType <ForwardLightingRenderFeature>().FirstOrDefault();

            if (forwardLightingFeature == null)
            {
                throw new InvalidOperationException("Missing forward lighting render feature");
            }

            shadowMapRenderer = forwardLightingFeature.ShadowMapRenderer;

            for (int i = 0; i < 2; ++i)
            {
                var minmaxPipelineState = new MutablePipelineState(Context.GraphicsDevice);
                minmaxPipelineState.State.SetDefaults();

                minmaxPipelineState.State.BlendState.RenderTarget0.BlendEnable           = true;
                minmaxPipelineState.State.BlendState.RenderTarget0.ColorSourceBlend      = Blend.One;
                minmaxPipelineState.State.BlendState.RenderTarget0.ColorDestinationBlend = Blend.One;
                minmaxPipelineState.State.BlendState.RenderTarget0.ColorBlendFunction    = i == 0 ? BlendFunction.Min : BlendFunction.Max;
                minmaxPipelineState.State.BlendState.RenderTarget0.ColorWriteChannels    = i == 0 ? ColorWriteChannels.Red : ColorWriteChannels.Green;

                minmaxPipelineState.State.DepthStencilState.DepthBufferEnable      = false;
                minmaxPipelineState.State.DepthStencilState.DepthBufferWriteEnable = false;
                minmaxPipelineState.State.RasterizerState.DepthClipEnable          = true;
                minmaxPipelineState.State.RasterizerState.CullMode = i == 0 ? CullMode.Back : CullMode.Front;

                minmaxPipelineStates[i] = minmaxPipelineState;
            }
        }
        protected override void InitializeCore()
        {
            base.InitializeCore();

            backgroundEffect       = new DynamicEffectInstance("BackgroundShader");
            skyboxBackgroundEffect = new DynamicEffectInstance("SkyboxShader");
            backgroundEffect.Initialize(Context.Services);
            skyboxBackgroundEffect.Initialize(Context.Services);
            spriteBatch = new SpriteBatch(RenderSystem.GraphicsDevice)
            {
                VirtualResolution = new Vector3(1)
            };
        }
        protected override void InitializeCore()
        {
            // Initalize the shader
            myCustomShader = new DynamicEffectInstance("MyCustomShader");
            myCustomShader.Initialize(Context.Services);

            // Create the pipeline state and set properties that won't change
            pipelineState = new MutablePipelineState(Context.GraphicsDevice);
            pipelineState.State.SetDefaults();
            pipelineState.State.InputElements            = MyRenderObject.VertexDeclaration.CreateInputElements();
            pipelineState.State.PrimitiveType            = MyRenderObject.PrimitiveType;
            pipelineState.State.BlendState               = BlendStates.Default;
            pipelineState.State.RasterizerState.CullMode = CullMode.None;
        }
Beispiel #10
0
        public ComputeEffectShader(RenderContext context)
            : base(context, null)
        {
            pipelineState = new MutablePipelineState(context.GraphicsDevice);

            // Setup the effect compiler
            EffectInstance = new DynamicEffectInstance("ComputeEffectShader", Parameters);
            EffectInstance.Initialize(context.Services);

            ThreadNumbers     = new Int3(1);
            ThreadGroupCounts = new Int3(1);

            SetDefaultParameters();
        }
        protected override void InitializeCore()
        {
            base.InitializeCore();

            // initialize shader
            shader = new DynamicEffectInstance("SinglePassWireframeShader");
            shader.Initialize(Context.Services);

            // create the pipeline state and set properties that won't change
            pipelineState = new MutablePipelineState(Context.GraphicsDevice);
            pipelineState.State.SetDefaults();
            pipelineState.State.InputElements            = VertexPositionNormalTexture.Layout.CreateInputElements();
            pipelineState.State.BlendState               = BlendStates.AlphaBlend;
            pipelineState.State.RasterizerState.CullMode = CullMode.None;
        }
Beispiel #12
0
        public CubemapFromTextureRenderer(IServiceRegistry services, RenderDrawContext renderDrawContext, Texture input, int outputSize, PixelFormat outputFormat)
            : base(renderDrawContext.GraphicsDevice, outputSize, outputFormat, false)
        {
            inputTexture = input;

            DrawContext = renderDrawContext;

            skyboxEffect = new DynamicEffectInstance("SkyboxShaderTexture");
            skyboxEffect.Initialize(services);

            spriteBatch = new SpriteBatch(renderDrawContext.GraphicsDevice)
            {
                VirtualResolution = new Vector3(1)
            };
        }
        public ComputeEffectShader(RenderContext context)
            : base(context, null)
        {
            pipelineState = new MutablePipelineState(context.GraphicsDevice);

            // Setup the effect compiler
            EffectInstance = new DynamicEffectInstance("ComputeEffectShader", Parameters);
            EffectInstance.Initialize(context.Services);

            // We give ComputeEffectShader a higher priority, since they are usually executed serially and blocking
            EffectInstance.EffectCompilerParameters.TaskPriority = -1;

            ThreadNumbers     = new Int3(1);
            ThreadGroupCounts = new Int3(1);

            SetDefaultParameters();
        }
        public ComputeEffectShader(RenderContext context)
            : base(context, null)
        {
            pipelineState = new MutablePipelineState(context.GraphicsDevice);

            // Setup the effect compiler
            EffectInstance = new DynamicEffectInstance("ComputeEffectShader", Parameters);
            EffectInstance.Initialize(context.Services);

            // We give ComputeEffectShader a higher priority, since they are usually executed serially and blocking
            EffectInstance.EffectCompilerParameters.TaskPriority = -1;

            ThreadNumbers = new Int3(1);
            ThreadGroupCounts = new Int3(1);

            SetDefaultParameters();
        }
        protected override void InitializeCore()
        {
            background2DEffect         = new DynamicEffectInstance("BackgroundShader");
            backgroundCubemapEffect    = new DynamicEffectInstance("BackgroundCubemapShader");
            skyboxTextureEffect        = new DynamicEffectInstance("SkyboxShaderTexture");
            skyboxTextureHorizonEffect = new DynamicEffectInstance("SkyboxShaderTextureHorizon");
            skyboxCubemapEffect        = new DynamicEffectInstance("SkyboxShaderCubemap");

            background2DEffect.Initialize(Context.Services);
            backgroundCubemapEffect.Initialize(Context.Services);
            skyboxTextureEffect.Initialize(Context.Services);
            skyboxCubemapEffect.Initialize(Context.Services);
            skyboxTextureHorizonEffect.Initialize(Context.Services);

            spriteBatch = new SpriteBatch(RenderSystem.GraphicsDevice)
            {
                VirtualResolution = new Vector3(1)
            };
        }
Beispiel #16
0
 public EffectNode(NodeContext nodeContext, EffectNodeDescription description) : base(nodeContext, description)
 {
     graphicsDevice = description.Factory.DeviceManager.GraphicsDevice;
     instance       = new DynamicEffectInstance(description.Name);
     instance.Initialize(description.Factory.ServiceRegistry);
     instance.UpdateEffect(graphicsDevice);
     parameters     = instance.Parameters;
     Inputs         = description.CreateNodeInputs(this, parameters);
     Outputs        = description.CreateNodeOutputs(this, parameters);
     perFrameParams = parameters.GetWellKnownParameters(WellKnownParameters.PerFrameMap).ToArray();
     perViewParams  = parameters.GetWellKnownParameters(WellKnownParameters.PerViewMap).ToArray();
     perDrawParams  = parameters.GetWellKnownParameters(WellKnownParameters.PerDrawMap).ToArray();
     if (perDrawParams.Length > 0)
     {
         worldPin = Inputs.OfType <ConvertedValueParameterPin <Matrix, SharpDX.Matrix> >().FirstOrDefault(p => p.Key == TransformationKeys.World);
     }
     if (Inputs.Length > 0)
     {
         customParameterSetterPin = Inputs[Inputs.Length - 1] as Pin <Action <ParameterCollection, RenderView, RenderDrawContext> >;
     }
 }
Beispiel #17
0
        IEnumerable <EffectPinDescription> GetInputs()
        {
            var effectName = IsCompute ? "ComputeEffectShader" : Name;

            using (var dummyInstance = new DynamicEffectInstance(effectName))
            {
                var parameters = dummyInstance.Parameters;
                if (IsCompute)
                {
                    parameters.Set(ComputeEffectShaderKeys.ComputeShaderName, Name);
                    parameters.Set(ComputeEffectShaderKeys.ThreadNumbers, new Int3(1));
                }
                dummyInstance.Initialize(Factory.ServiceRegistry);
                dummyInstance.UpdateEffect(Factory.DeviceManager.GraphicsDevice);

                var usedNames = new HashSet <string>();
                usedNames.Add(ParameterSetterInput.Name);
                if (IsCompute)
                {
                    usedNames.Add(ComputeDispatchCountInput.Name);
                    usedNames.Add(ComputeThreadNumbersInput.Name);
                    usedNames.Add(ComputeIterationCountInput.Name);
                    usedNames.Add(ComputeIterationParameterSetterInput.Name);
                    usedNames.Add(ComputeEnabledInput.Name);
                    // Thread numbers and thread group count pins
                    yield return(ComputeThreadNumbersInput);

                    yield return(ComputeDispatchCountInput);
                }

                // Permutation parameters
                foreach (var parameter in parameters.ParameterKeyInfos)
                {
                    var key = parameter.Key;
                    if (key == ComputeEffectShaderKeys.ComputeShaderName)
                    {
                        continue;
                    }
                    if (key == ComputeEffectShaderKeys.ThreadNumbers)
                    {
                        continue;
                    }
                    yield return(new ParameterPinDescription(usedNames, key, isPermutationKey: true));
                }

                // Resource and value parameters
                var byteCode    = dummyInstance.Effect.Bytecode;
                var layoutNames = byteCode.Reflection.ResourceBindings.Select(x => x.ResourceGroup ?? "Globals").Distinct().ToList();
                var needsWorld  = false;
                foreach (var parameter in parameters.Layout.LayoutParameterKeyInfos)
                {
                    var key  = parameter.Key;
                    var name = key.Name;

                    // Skip constant buffers
                    if (layoutNames.Contains(name))
                    {
                        continue;
                    }

                    // Skip compiler injected paddings
                    if (name.Contains("_padding_"))
                    {
                        continue;
                    }

                    // Skip well known parameters
                    if (WellKnownParameters.PerFrameMap.ContainsKey(name) || WellKnownParameters.PerViewMap.ContainsKey(name))
                    {
                        continue;
                    }

                    if (WellKnownParameters.PerDrawMap.ContainsKey(name))
                    {
                        // Expose World only - all other world dependent parameters we can compute on our own
                        needsWorld = true;
                        continue;
                    }

                    if (key == ComputeShaderBaseKeys.ThreadGroupCountGlobal)
                    {
                        continue; // Already handled
                    }
                    yield return(new ParameterPinDescription(usedNames, key, parameter.Count));
                }

                if (needsWorld)
                {
                    yield return(new ParameterPinDescription(usedNames, TransformationKeys.World));
                }

                yield return(ParameterSetterInput);

                if (IsCompute)
                {
                    yield return(ComputeIterationCountInput);

                    yield return(ComputeIterationParameterSetterInput);

                    yield return(ComputeEnabledInput);
                }
            }
        }
Beispiel #18
0
        /// <summary>
        /// Bake lightprobes into buffers compatible with <see cref="LightProbeRenderer"/>
        /// </summary>
        /// <param name="drawContext">The drawing context</param>
        private unsafe void BakeLightProbes(RenderContext context, RenderDrawContext drawContext)
        {
            Texture ibl = null;
            Buffer  tetrahedronProbeIndices = null;
            Buffer  tetrahedronMatrices     = null;
            Buffer  lightprobesCoefficients = null;
            var     renderView = context.RenderView;

            var lightProbesData = context.VisibilityGroup.Tags.Get(LightProbeRenderer.CurrentLightProbes);

            if (lightProbesData == null || lightProbesData.Tetrahedra.Count == 0)
            {
                // No lightprobes, we still set GPU resources (otherwise rendering might fetch invalid data)
                goto SetGPUResources;
            }

            // First time initialization
            if (bakeLightProbes == null)
            {
                bakeLightProbes = new DynamicEffectInstance("XenkoBakeLightProbeEffect");
                bakeLightProbes.Initialize(Services);

                bakeLightProbesPipeline = new MutablePipelineState(GraphicsDevice);
                bakeLightProbesPipeline.State.InputElements = LightProbeVertex.Layout.CreateInputElements();
                bakeLightProbesPipeline.State.PrimitiveType = PrimitiveType.TriangleList;
            }

            // Render IBL tetrahedra ID so that we can assign them per pixel
            //ibl = PushScopedResource(Context.Allocator.GetTemporaryTexture2D(drawContext.CommandList.DepthStencilBuffer.Width, drawContext.CommandList.DepthStencilBuffer.Height, PixelFormat.R16_UInt));
            ibl = PushScopedResource(Context.Allocator.GetTemporaryTexture2D(TextureDescription.New2D(drawContext.CommandList.DepthStencilBuffer.Width, drawContext.CommandList.DepthStencilBuffer.Height,
                                                                                                      1, PixelFormat.R16_UInt, TextureFlags.ShaderResource | TextureFlags.RenderTarget, 1, GraphicsResourceUsage.Default, actualMultisampleCount)));
            using (drawContext.PushRenderTargetsAndRestore())
            {
                drawContext.CommandList.Clear(ibl, Color4.Black);
                drawContext.CommandList.SetRenderTarget(drawContext.CommandList.DepthStencilBuffer, ibl);

                bakeLightProbes.UpdateEffect(GraphicsDevice);

                bakeLightProbesPipeline.State.RootSignature  = bakeLightProbes.RootSignature;
                bakeLightProbesPipeline.State.EffectBytecode = bakeLightProbes.Effect.Bytecode;
                bakeLightProbesPipeline.State.RasterizerState.DepthClipEnable = false;
                bakeLightProbesPipeline.State.DepthStencilState = new DepthStencilStateDescription(true, false)
                {
                    StencilEnable = true,
                    FrontFace     = new DepthStencilStencilOpDescription
                    {
                        StencilDepthBufferFail = StencilOperation.Keep,
                        StencilFail            = StencilOperation.Keep,
                        StencilPass            = StencilOperation.Increment,
                        StencilFunction        = CompareFunction.Equal,
                    },
                };
                //bakeLightProbesPipeline.State.RasterizerState.DepthClipEnable = false;
                bakeLightProbesPipeline.State.Output.CaptureState(drawContext.CommandList);
                bakeLightProbesPipeline.Update();

                drawContext.CommandList.SetPipelineState(bakeLightProbesPipeline.CurrentState);
                drawContext.CommandList.SetStencilReference(0);

                // Apply the effect
                bakeLightProbes.Parameters.Set(BakeLightProbeShaderKeys.MatrixTransform, ref renderView.ViewProjection);
                bakeLightProbes.Apply(drawContext.GraphicsContext);

                /*int tetrahedrawGridSize = 5;
                 * Vector3 tetrahedraMin = new Vector3(-12.0f);
                 * Vector3 tetrahedraMax = new Vector3(12.0f);
                 * var lightprobePositions = new Vector3[tetrahedrawGridSize*tetrahedrawGridSize*tetrahedrawGridSize];
                 *
                 * for (int i = 0; i < lightprobePositions.Length; ++i)
                 * {
                 *  lightprobePositions[i] = new Vector3(
                 *      MathUtil.Lerp(tetrahedraMin.X, tetrahedraMax.X, (float)(i/(tetrahedrawGridSize*tetrahedrawGridSize))/(tetrahedrawGridSize - 1)),
                 *      MathUtil.Lerp(tetrahedraMin.Y, tetrahedraMax.Y, (float)((i/tetrahedrawGridSize)%tetrahedrawGridSize)/(tetrahedrawGridSize - 1)),
                 *      MathUtil.Lerp(tetrahedraMin.Z, tetrahedraMax.Z, (float)(i%tetrahedrawGridSize)/(tetrahedrawGridSize - 1)));
                 * }
                 *
                 * var tetra = new BowyerWatsonTetrahedralization();
                 * var tetraResult = tetra.Compute(lightprobePositions);*/

                Matrix.Invert(ref renderView.View, out var viewInverse);

                var eye = new Vector3(viewInverse.M41, viewInverse.M42, viewInverse.M43);

                var tetraResult         = lightProbesData.Tetrahedra;
                var lightprobePositions = lightProbesData.Vertices;
                var lightprobeFaces     = lightProbesData.Faces;

                // We build a graph of tetrahedron connectivity from back to front, then do a topological sort on top of it
                var tetraDepth          = new TetrahedronSortKey[tetraResult.Count];
                var faceDirection       = new bool[lightprobeFaces.Count];
                var incomingEdges       = new byte[tetraResult.Count];
                var processQueue        = new Queue <int>();
                int processedTetrahedra = 0;

                for (int i = 0; i < lightprobeFaces.Count; ++i)
                {
                    var face = lightprobeFaces[i];

                    // Compute face orientations
                    var vertex0 = lightprobePositions[face.Vertices[0]];
                    Vector3.Subtract(ref vertex0, ref eye, out vertex0);
                    bool faceFrontFacing = Vector3.Dot(face.Normal, vertex0) >= 0.0f;
                    faceDirection[i] = faceFrontFacing;

                    // Only process edges that connect two tetrahedra (ignore boundaries for now)
                    if (face.BackTetrahedron != -1)
                    {
                        // Build list of incoming edges (back to front)
                        if (faceFrontFacing)
                        {
                            incomingEdges[face.FrontTetrahedron] |= (byte)(1 << face.FrontFace);
                        }
                        else
                        {
                            incomingEdges[face.BackTetrahedron] |= (byte)(1 << face.BackFace);
                        }
                    }
                }

                for (int i = 0; i < tetraResult.Count; ++i)
                {
                    // Tetrahedron without any incoming edges means they should be drawn first (graph nodes with no incoming edges for our topological sort)
                    if (incomingEdges[i] == 0)
                    {
                        processQueue.Enqueue(i);
                    }
                }

                // Perform topological sort
                while (processQueue.Count > 0)
                {
                    var tetrahedronIndex = processQueue.Dequeue();
                    tetraDepth[tetrahedronIndex] = new TetrahedronSortKey(tetrahedronIndex, processedTetrahedra++);
                    var tetrahedron = tetraResult[tetrahedronIndex];
                    //var frontFacingFaces = frontFacing[tetrahedronIndex];

                    // Process each outgoing face (edges in the graph)
                    for (int tetrahedronFace = 0; tetrahedronFace < 4; ++tetrahedronFace)
                    {
                        // Check if there is a neighbour
                        if (tetrahedron.Neighbours[tetrahedronFace] == -1)
                        {
                            continue;
                        }

                        var faceIndex     = tetrahedron.Faces[tetrahedronFace];
                        var realFaceIndex = faceIndex >= 0 ? faceIndex : ~faceIndex;

                        // Only process faces going back to front (outgoing edges)
                        if (faceDirection[realFaceIndex] == faceIndex >= 0)
                        {
                            continue;
                        }

                        var face = lightprobeFaces[realFaceIndex];

                        int   tetrahedronNeighbourIndex;
                        sbyte tetrahedronNeighbourFace;
                        if (faceIndex >= 0)
                        {
                            tetrahedronNeighbourIndex = face.BackTetrahedron;
                            tetrahedronNeighbourFace  = face.BackFace;
                        }
                        else
                        {
                            tetrahedronNeighbourIndex = face.FrontTetrahedron;
                            tetrahedronNeighbourFace  = face.FrontFace;
                        }

                        var neighbourTraversedFaces    = incomingEdges[tetrahedronNeighbourIndex];
                        var newNeighbourTraversedFaces = (byte)(neighbourTraversedFaces & ~(1 << tetrahedronNeighbourFace));

                        // Proceed only if something changed
                        if (newNeighbourTraversedFaces != neighbourTraversedFaces)
                        {
                            incomingEdges[tetrahedronNeighbourIndex] = newNeighbourTraversedFaces;
                            if (newNeighbourTraversedFaces == 0) // are all incoming edges already marked? If yes, go on
                            {
                                processQueue.Enqueue(tetrahedronNeighbourIndex);
                            }
                        }
                    }
                }

                Array.Sort(tetraDepth);

                // Draw shape
                tetrahedronMatrices     = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription(tetraResult.Count * 3 * sizeof(Vector4), BufferFlags.ShaderResource, GraphicsResourceUsage.Default), PixelFormat.R32G32B32A32_Float));
                tetrahedronProbeIndices = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription(tetraResult.Count * 4 * sizeof(int), BufferFlags.ShaderResource, GraphicsResourceUsage.Default), PixelFormat.R32G32B32A32_UInt));
                lightprobesCoefficients = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription(lightProbesData.Coefficients.Length * sizeof(Color3), BufferFlags.ShaderResource, GraphicsResourceUsage.Default), PixelFormat.R32G32B32_Float));

                var tetraInsideIndex = -1;

                fixed(Color3 *lightProbeCoefficients = lightProbesData.Coefficients)
                fixed(Vector4 * matrices  = lightProbesData.Matrices)
                fixed(Int4 * probeIndices = lightProbesData.LightProbeIndices)
                {
                    drawContext.CommandList.UpdateSubresource(lightprobesCoefficients, 0, new DataBox((IntPtr)lightProbeCoefficients, 0, 0));
                    drawContext.CommandList.UpdateSubresource(tetrahedronProbeIndices, 0, new DataBox((IntPtr)probeIndices, 0, 0));
                    drawContext.CommandList.UpdateSubresource(tetrahedronMatrices, 0, new DataBox((IntPtr)matrices, 0, 0));

                    // Find which probe we are currently in
                    // TODO: Optimize (use previous coherency info?)
                    for (int i = 0; i < tetraResult.Count; ++i)
                    {
                        // Get tetrahedra matrix
                        var tetrahedraMatrix = Matrix.Identity;
                        tetrahedraMatrix.Column1 = matrices[i * 3 + 0];
                        tetrahedraMatrix.Column2 = matrices[i * 3 + 1];
                        tetrahedraMatrix.Column3 = matrices[i * 3 + 2];

                        // Extract and zero-out position of 3rd vertex (we get the 3x3 matrix)
                        var vertex3 = tetrahedraMatrix.TranslationVector;
                        tetrahedraMatrix.TranslationVector = Vector3.Zero;

                        Vector3 tetraFactors = Vector3.TransformCoordinate(eye - vertex3, tetrahedraMatrix);
                        var     tetraFactorW = 1.0f - tetraFactors.X - tetraFactors.Y - tetraFactors.Z;
                        if (tetraFactors.X >= 0.0f && tetraFactors.X <= 1.0f &&
                            tetraFactors.Y >= 0.0f && tetraFactors.Y <= 1.0f &&
                            tetraFactors.Z >= 0.0f && tetraFactors.Z <= 1.0f &&
                            tetraFactorW >= 0.0f && tetraFactorW <= 1.0f)
                        {
                            tetraInsideIndex = i;
                            break;
                        }
                    }
                }

                // Fill vertex/index buffers
                var vertexBuffer = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription((tetraResult.Count * 4 + 3) * LightProbeVertex.Size, BufferFlags.VertexBuffer, GraphicsResourceUsage.Dynamic)));
                var indexBuffer  = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription(tetraResult.Count * 12 * sizeof(uint), BufferFlags.IndexBuffer, GraphicsResourceUsage.Dynamic)));

                var mappedVertexBuffer = drawContext.CommandList.MapSubresource(vertexBuffer, 0, MapMode.WriteDiscard);
                var vertices           = (LightProbeVertex *)mappedVertexBuffer.DataBox.DataPointer;
                // Upload sorted tetrahedron indices
                for (int i = 0; i < tetraResult.Count; ++i)
                {
                    var sortedIndex = tetraDepth[i].Index;
                    var tetrahedra  = tetraResult[sortedIndex];
                    for (int j = 0; j < 4; ++j)
                    {
                        vertices[i * 4 + j] = new LightProbeVertex(lightprobePositions[tetrahedra.Vertices[j]], (uint)sortedIndex);
                    }
                }
                // Full screen pass
                if (tetraInsideIndex != -1)
                {
                    vertices[tetraResult.Count * 4 + 0] = new LightProbeVertex(new Vector3(-1, 1, 0), (uint)tetraInsideIndex);
                    vertices[tetraResult.Count * 4 + 1] = new LightProbeVertex(new Vector3(3, 1, 0), (uint)tetraInsideIndex);
                    vertices[tetraResult.Count * 4 + 2] = new LightProbeVertex(new Vector3(-1, -3, 0), (uint)tetraInsideIndex);
                }
                drawContext.CommandList.UnmapSubresource(mappedVertexBuffer);

                var mappedIndexBuffer = drawContext.CommandList.MapSubresource(indexBuffer, 0, MapMode.WriteDiscard);
                var indices           = (int *)mappedIndexBuffer.DataBox.DataPointer;
                for (int i = 0; i < tetraResult.Count; ++i)
                {
                    indices[i * 12 + 0] = i * 4 + 0;
                    indices[i * 12 + 1] = i * 4 + 2;
                    indices[i * 12 + 2] = i * 4 + 1;

                    indices[i * 12 + 3] = i * 4 + 1;
                    indices[i * 12 + 4] = i * 4 + 2;
                    indices[i * 12 + 5] = i * 4 + 3;

                    indices[i * 12 + 6] = i * 4 + 3;
                    indices[i * 12 + 7] = i * 4 + 2;
                    indices[i * 12 + 8] = i * 4 + 0;

                    indices[i * 12 + 9]  = i * 4 + 3;
                    indices[i * 12 + 10] = i * 4 + 0;
                    indices[i * 12 + 11] = i * 4 + 1;
                }
                drawContext.CommandList.UnmapSubresource(mappedIndexBuffer);

                drawContext.CommandList.SetVertexBuffer(0, vertexBuffer, 0, LightProbeVertex.Size);
                drawContext.CommandList.SetIndexBuffer(indexBuffer, 0, true);

                // Draw until current tetrahedra
                drawContext.CommandList.DrawIndexed(tetraResult.Count * 12);

                // For now, drawing them one by one (easier to debug)
                //for (int i = 0; i < tetraResult.Count; ++i)
                //{
                //    context.CommandList.DrawIndexed(12, i * 12);
                //}

                // Draw current tetrahedron we are in as full screen (fill stencil holes)
                if (tetraInsideIndex != -1)
                {
                    bakeLightProbesPipeline.State.DepthStencilState.DepthBufferEnable = false;
                    bakeLightProbesPipeline.Update();

                    drawContext.CommandList.SetPipelineState(bakeLightProbesPipeline.CurrentState);

                    // Apply the effect
                    bakeLightProbes.Parameters.Set(BakeLightProbeShaderKeys.MatrixTransform, Matrix.Identity);
                    bakeLightProbes.Apply(drawContext.GraphicsContext);

                    drawContext.CommandList.Draw(3, tetraResult.Count * 4);
                }

                // TODO: Draw the tetrahedron we are in full screen
                // context.CommandList.Draw...
            }

            // Set LightProbes resources
SetGPUResources:
            foreach (var renderFeature in context.RenderSystem.RenderFeatures)
            {
                if (!(renderFeature is RootEffectRenderFeature))
                {
                    continue;
                }

                var logicalKey  = ((RootEffectRenderFeature)renderFeature).CreateViewLogicalGroup("LightProbes");
                var viewFeature = renderView.Features[renderFeature.Index];

                foreach (var viewLayout in viewFeature.Layouts)
                {
                    var resourceGroup = viewLayout.Entries[renderView.Index].Resources;

                    var logicalGroup = viewLayout.GetLogicalGroup(logicalKey);
                    if (logicalGroup.Hash == ObjectId.Empty)
                    {
                        continue;
                    }

                    resourceGroup.DescriptorSet.SetShaderResourceView(logicalGroup.DescriptorSlotStart, ibl);
                    resourceGroup.DescriptorSet.SetShaderResourceView(logicalGroup.DescriptorSlotStart + 1, tetrahedronProbeIndices);
                    resourceGroup.DescriptorSet.SetShaderResourceView(logicalGroup.DescriptorSlotStart + 2, tetrahedronMatrices);
                    resourceGroup.DescriptorSet.SetShaderResourceView(logicalGroup.DescriptorSlotStart + 3, lightprobesCoefficients);
                }
            }
        }