public override void Compute(PlottedGraph plot, CommandList cl, bool flip, float delta)
        {
            GraphLayoutState       layout = plot.LayoutState;
            ResourceSetDescription velocity_rsrc_desc, pos_rsrc_desc;

            if (flip)
            {
                velocity_rsrc_desc = new ResourceSetDescription(_velocityShaderRsrcLayout,
                                                                _velocityParamsBuffer, layout.PositionsVRAM1, layout.VelocitiesVRAM1, layout.EdgeConnectionIndexes,
                                                                layout.EdgeConnections, layout.EdgeStrengths,
                                                                layout.VelocitiesVRAM2
                                                                );

                pos_rsrc_desc = new ResourceSetDescription(_positionShaderRsrcLayout,
                                                           _positionParamsBuffer, layout.PositionsVRAM1, layout.VelocitiesVRAM2,
                                                           layout.PositionsVRAM2);
            }
            else
            {
                velocity_rsrc_desc = new ResourceSetDescription(_velocityShaderRsrcLayout,
                                                                _velocityParamsBuffer, layout.PositionsVRAM2, layout.VelocitiesVRAM2, layout.EdgeConnectionIndexes,
                                                                layout.EdgeConnections, layout.EdgeStrengths,
                                                                layout.VelocitiesVRAM1
                                                                );

                pos_rsrc_desc = new ResourceSetDescription(_positionShaderRsrcLayout,
                                                           _positionParamsBuffer, layout.PositionsVRAM2, layout.VelocitiesVRAM1,
                                                           layout.PositionsVRAM1);
            }

            RenderVelocity(velocity_rsrc_desc, cl, plot, delta);
            RenderPosition(pos_rsrc_desc, cl, plot, delta);
        }
        /// Creates an array of metadata for basic blocks used for basic-block-centric graph layout
        public static unsafe void CreateBlockMetadataBuffer(PlottedGraph plot, GraphicsDevice gdevice)
        {
            GraphLayoutState layout = plot.LayoutState;

            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"CreateBlockDataBuffer  {plot.TID}", Logging.LogFilterType.BulkDebugLogFile);
            }

            GraphLayoutState.GPUBuffers VRAMBuffers = layout._VRAMBuffers;
            VeldridGraphBuffers.VRAMDispose(VRAMBuffers.BlockMetadata);
            VeldridGraphBuffers.VRAMDispose(VRAMBuffers.BlockMiddles);

            var textureSize = plot.EdgeTextureWidth();

            if (textureSize > 0)
            {
                CreateBlockMetadataBuf(plot, out NODE_BLOCK_METADATA_COMPUTEBUFFER[] blockdats, out int[] blockMiddles);

                VRAMBuffers.BlockMetadata = VeldridGraphBuffers.TrackedVRAMAlloc(gdevice,
                                                                                 (uint)blockdats.Length * NODE_BLOCK_METADATA_COMPUTEBUFFER.SizeInBytes,
                                                                                 BufferUsage.StructuredBufferReadOnly, sizeof(int), $"BlockMetadata_T{plot.TID}");

                VRAMBuffers.BlockMiddles = VeldridGraphBuffers.TrackedVRAMAlloc(gdevice,
                                                                                (uint)blockMiddles.Length * sizeof(int), BufferUsage.StructuredBufferReadOnly, sizeof(int), $"BlockMiddles_T{plot.TID}");

                VRAMBuffers.BlockCount = blockMiddles.Length;

                if (blockdats.Length == 0)
                {
                    return;
                }

                fixed(NODE_BLOCK_METADATA_COMPUTEBUFFER *datsPtr = blockdats)
                {
                    fixed(int *middlesPtr = blockMiddles)
                    {
                        CommandList cl = gdevice.ResourceFactory.CreateCommandList();

                        cl.Begin();
                        cl.UpdateBuffer(VRAMBuffers.BlockMetadata, 0, (IntPtr)datsPtr, (uint)blockdats.Length * NODE_BLOCK_METADATA_COMPUTEBUFFER.SizeInBytes);
                        cl.UpdateBuffer(VRAMBuffers.BlockMiddles, 0, (IntPtr)middlesPtr, (uint)blockMiddles.Length * sizeof(int));
                        cl.End();
                        gdevice.SubmitCommands(cl);
                        gdevice.WaitForIdle();
                        cl.Dispose();
                    }
                }
            }

            //Debug.Assert(!VeldridGraphBuffers.DetectNaN(_gd, newBuffer));

            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"CreateBlockDataBuffer  {plot.TID} complete", Logging.LogFilterType.BulkDebugLogFile);
            }
            //PrintBufferArray(textureArray, "Created data texture:");
        }
        /// <summary>
        /// Pass the graph plot through the velocity compute shader, to adjust the node velocity based on the positions of other nodes
        /// </summary>
        /// <param name="RSetDesc">Position shader resource set</param>
        /// <param name="cl">Commandlist to run the commands on</param>
        /// <param name="plot">PlottedGraph to compute</param>
        /// <param name="delta">A float representing how much time has passed since the last frame. Higher values => bigger movements</param>
        private void RenderVelocity(ResourceSetDescription RSetDesc, CommandList cl, PlottedGraph plot, float delta)
        {
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);

            _timer.Restart();
            cl.Begin();

            ResourceSet resourceSet = _gd.ResourceFactory.CreateResourceSet(RSetDesc);
            uint        nodeCount   = (uint)plot.RenderedNodeCount();
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocityBlocks  {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
            GraphLayoutState     layout     = plot.LayoutState;
            VelocityShaderParams parameters = new VelocityShaderParams
            {
                delta       = delta, //not used
                temperature = Math.Min(plot.Temperature, GlobalConfig.MaximumNodeTemperature),
                repulsionK  = GlobalConfig.RepulsionK,
                nodeCount   = nodeCount
            };

            Debug.Assert(nodeCount <= (layout.VelocitiesVRAM1 !.SizeInBytes / 16));
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID} submit", Logging.LogFilterType.BulkDebugLogFile);

            cl.UpdateBuffer(_velocityParamsBuffer, 0, parameters);
            cl.SetPipeline(_velocityComputePipeline);
            cl.SetComputeResourceSet(0, resourceSet);

            //16 == sizeof(Vector4)
            uint elemCount = layout.VelocitiesVRAM1 !.SizeInBytes / 16;
            uint grpSizeX  = (uint)Math.Ceiling(elemCount / 256.0);

            //Console.WriteLine($"VRAM Size: {layout.VelocitiesVRAM1!.SizeInBytes}bytes, WkX: {grpSizeX}, nodeCount: {nodeCount}, bufVel4Count: {layout.VelocitiesVRAM1!.SizeInBytes/16}");
            cl.Dispatch(grpSizeX, 1, 1);
            //_cl.Dispatch((uint)Math.Ceiling(layout.VelocitiesVRAM1!.SizeInBytes / (256.0 * 16)), 1, 1);
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID} done in {watch.ElapsedMilliseconds} MS", Logging.LogFilterType.BulkDebugLogFile);

            cl.End();
            _timer.Stop();
            VelocitySetupTime = _timer.Elapsed.TotalMilliseconds;

            _timer.Restart();
            _gd !.SubmitCommands(cl);
            _gd !.WaitForIdle();
            _gd.DisposeWhenIdle(resourceSet);

            _timer.Stop();
            VelocityTime = _timer.Elapsed.TotalMilliseconds;
        }
Beispiel #4
0
        /// <summary>
        /// Pass the graph plot through the velocity compute shader, to adjust the node velocity based on the positions of other nodes
        /// </summary>
        /// <param name="RSetDesc">Position shader resource set</param>
        /// <param name="cl">Commandlist to run the commands on</param>
        /// <param name="plot">PlottedGraph to compute</param>
        /// <param name="delta">A float representing how much time has passed since the last frame. Higher values => bigger movements</param>
        private void RenderVelocity(ResourceSetDescription RSetDesc, CommandList cl, PlottedGraph plot, float delta)
        {
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);

            _timer.Restart();
            cl.Begin();

            ResourceSet resourceSet = _gd.ResourceFactory.CreateResourceSet(RSetDesc);

            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocityBlocks  {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
            GraphLayoutState     layout     = plot.LayoutState;
            VelocityShaderParams parameters = new VelocityShaderParams
            {
                nodeCount    = (uint)plot.RenderedNodeCount(),
                speedDivisor = GlobalConfig.PresetSpeedDivisor
            };

            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID} submit", Logging.LogFilterType.BulkDebugLogFile);

            cl.UpdateBuffer(_velocityParamsBuffer, 0, parameters);
            cl.SetPipeline(_velocityComputePipeline);
            cl.SetComputeResourceSet(0, resourceSet);

            //16 == sizeof(Vector4)
            cl.Dispatch((uint)Math.Ceiling(layout.VelocitiesVRAM1 !.SizeInBytes / (256.0 * 16)), 1, 1);
            //if (GlobalConfig.Settings.Logs.BulkLogging) Logging.RecordLogEvent($"RenderVelocity  {this.EngineID} done in {watch.ElapsedMilliseconds} MS", Logging.LogFilterType.BulkDebugLogFile);

            cl.End();
            _timer.Stop();
            VelocitySetupTime = _timer.Elapsed.TotalMilliseconds;

            _timer.Restart();
            _gd !.SubmitCommands(cl);
            _gd !.WaitForIdle();
            _gd.DisposeWhenIdle(resourceSet);

            _timer.Stop();
            VelocityTime = _timer.Elapsed.TotalMilliseconds;
        }
Beispiel #5
0
        LayoutPipelines.LayoutPipeline?SelectPipeline(GraphLayoutState layout)
        {
            if (layout.ActivatingPreset)
            {
                return(this.PresetLayout);
            }
            switch (layout.Style)
            {
            case CONSTANTS.LayoutStyles.Style.ForceDirected3DBlocks:
                return(this.ForceBlocksLayout);

            case CONSTANTS.LayoutStyles.Style.ForceDirected3DNodes:
                return(this.ForceNodesLayout);

            case CONSTANTS.LayoutStyles.Style.CylinderLayout:
            case CONSTANTS.LayoutStyles.Style.Circle:
            case CONSTANTS.LayoutStyles.Style.Custom:
                return(this.PresetLayout);

            default:
                Logging.RecordError($"Layout '{layout.Style}' requested but not handled by SelectPipeline");
                return(null);
            }
        }
Beispiel #6
0
        void ComputeAttributes(bool flip, GraphLayoutState layout, CommandList cl, PlottedGraph graph, float delta, int mouseoverNodeID, bool isAnimated)
        {
            ResourceSetDescription attr_rsrc_desc;
            DeviceBuffer           inputAttributes;

            if (flip)
            {
                attr_rsrc_desc = new ResourceSetDescription(_nodeAttribComputeLayout, _attribsParamsBuffer, layout.AttributesVRAM1,
                                                            layout.EdgeConnectionIndexes, layout.EdgeConnections, layout.AttributesVRAM2);
                inputAttributes = layout.AttributesVRAM1 !;
            }
            else
            {
                attr_rsrc_desc = new ResourceSetDescription(_nodeAttribComputeLayout, _attribsParamsBuffer, layout.AttributesVRAM2,
                                                            layout.EdgeConnectionIndexes, layout.EdgeConnections, layout.AttributesVRAM1);
                inputAttributes = layout.AttributesVRAM2 !;
            }

            _attSetupTimer.Restart();
            ResourceSet attribComputeResourceSet = _factory !.CreateResourceSet(attr_rsrc_desc);

            cl.Begin();
            RenderNodeAttribs(cl, graph, inputAttributes, attribComputeResourceSet, delta, mouseoverNodeID, isAnimated);
            cl.End();
            _attSetupTimer.Stop();
            attributeSetupTime = _attSetupTimer.Elapsed.TotalMilliseconds;

            _attShaderTimer.Restart();
            _gd !.SubmitCommands(cl);
            _gd !.WaitForIdle();
            //should we be dispose/recreating these? probably not. todo
            _gd.DisposeWhenIdle(attribComputeResourceSet);
            _attShaderTimer.Stop();
            attributeTime = _attShaderTimer.Elapsed.TotalMilliseconds;
            //DebugPrintOutputFloatBuffer(layout.AttributesVRAM1!, "Atts1", 32);
        }
Beispiel #7
0
        /// <summary>
        /// Do the actual computation of graph layout and animation
        /// Uses the velocity shader to adjust the velocity based on relative positions
        /// Uses the position shader to move the nodes at the calculated velocity
        /// Adjusts the size/alpha of nodes based on the attribute buffer
        /// </summary>
        /// <param name="cl">Thread-specific command list</param>
        /// <param name="plot">Graph to perform computation on</param>
        /// <param name="mouseoverNodeID">The index of the node the users mouse is hovering over</param>
        /// <param name="isAnimated">If the graph should have animation attributes computed (ie: main graph with live/replay active)</param>
        /// <returns>The version ID associated with the produced graph layout computed</returns>
        public ulong Compute(CommandList cl, PlottedGraph plot, int mouseoverNodeID, bool isAnimated)
        {
            Debug.Assert(_gd is not null);

            if (plot.DrawnEdgesCount == 0 || !GlobalConfig.LayoutAllComputeEnabled) // || ErrorState)
            {
                return(plot.LayoutState.RenderVersion);
            }

            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"Compute start {EngineID} graph {plot.PID}:{plot.TID}", Logging.LogFilterType.BulkDebugLogFile);
            }

            int edgesCount = plot.DrawnEdgesCount;

            Debug.Assert(plot != null, "Layout engine called to compute without active graph");
            GraphLayoutState layout = plot.LayoutState;

            layout.Lock.EnterUpgradeableReadLock();
            _stepTimer.Restart();
            try
            {
                if (!layout.ActivatingPreset || layout.Initialised is false)
                {
                    plot.AddNewEdgesToLayoutBuffers(edgesCount);
                }

                var   now   = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
                float delta = Math.Min((now - plot.LastComputeTime) / 1000f, 1.0f); // safety cap on large deltas
                delta *= (layout.ActivatingPreset ? 7.5f : 1.0f);                   //without this the preset animation will 'bounce'

                plot.LastComputeTime = now;


                //todo set this on layout change
                bool isForceDirected = CONSTANTS.LayoutStyles.IsForceDirected(plot.ActiveLayoutStyle);

                bool forceComputationActive = layout.ActivatingPreset || (GlobalConfig.LayoutPositionsActive && plot.Temperature > 0 && isForceDirected);

                LayoutPipelines.LayoutPipeline?activePipeline = SelectPipeline(layout);
                if (activePipeline is null)
                {
                    ErrorState = true;
                    Logging.RecordError("Error selecting active layout - it's either invalid or the pipeline is uninitialised");
                    return(layout.RenderVersion);
                }

                bool flip = layout.flip();
                if (forceComputationActive)
                {
                    /*
                     * DebugPrintOutputFloatBuffer(layout.VelocitiesVRAM1!, "Vel1b4", 32);
                     * DebugPrintOutputFloatBuffer(layout.PositionsVRAM1!, "pos1b4", 32);
                     * DebugPrintOutputFloatBuffer(layout.PositionsVRAM2!, "pos2b4", 32);
                     * DebugPrintOutputFloatBuffer(layout.VelocitiesVRAM2!, "vel2b4", 32);
                     */

                    if (GlobalConfig.Settings.Logs.BulkLogging)
                    {
                        Logging.RecordLogEvent($"Layout {activePipeline.Name} computation starting in engine {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
                    }

                    //Actual computation happens here
                    activePipeline.Compute(plot, cl, flip, delta);

                    if (GlobalConfig.Settings.Logs.BulkLogging)
                    {
                        Logging.RecordLogEvent($"Layout {activePipeline.Name} computation finished in engine {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
                    }

                    layout.IncrementVersion();

                    if (plot.OPT_LOCK_TEMPERATURE is false)
                    {
                        plot.Temperature *= CONSTANTS.Layout_Constants.TemperatureStepMultiplier;
                        if (plot.Temperature <= CONSTANTS.Layout_Constants.MinimumTemperature)
                        {
                            plot.Temperature = 0;
                        }
                    }
                }

                if (rgatUI.ResponsiveKeyHeld || plot.FurthestNodeDimension == 0)
                {
                    // todo - don't iterate over every node every frame!
                    // not sure whether to make this timer based or do it in the shader
                    // it looks pretty bad doing it every 10 frames
                    // for now just do it every 3 frames
                    if ((forceComputationActive && (layout.RenderVersion % 3) == 0) || plot.FurthestNodeDimension == 0)
                    {
                        if (layout.PositionsVRAM1 is not null && (plot.ComputeBufferNodeCount * 4 * sizeof(float)) <= layout.PositionsVRAM1.SizeInBytes)
                        {
                            float highPosition = FindHighXYZ(layout.PositionsVRAM1 !, plot.ComputeBufferNodeCount, out int furthestNodeIdx);
                            if (furthestNodeIdx != -1)
                            {
                                plot.SetFurthestNodeDimension(furthestNodeIdx, highPosition);
                            }
                        }
                    }
                }

                if (GlobalConfig.LayoutAttribsActive)
                {
                    if (GlobalConfig.Settings.Logs.BulkLogging)
                    {
                        Logging.RecordLogEvent($"Attribute computation starting in engine {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
                    }
                    ComputeAttributes(flip, layout, cl, plot, delta, mouseoverNodeID, isAnimated);
                    if (GlobalConfig.Settings.Logs.BulkLogging)
                    {
                        Logging.RecordLogEvent($"Attribute computation finished in engine {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
                    }
                }

                //If activating a preset, find the fasted node. If below a threshold, move the positions to their targets
                //This avoids preset snapping taking too long at low speeds
                int steps = layout.IncrementPresetSteps();
                if (layout.ActivatingPreset && steps > 10) //todo look at this again, should it be done after compute?
                {
                    if (layout.VelocitiesVRAM1 is not null && (plot.ComputeBufferNodeCount * 4 * sizeof(float)) <= layout.VelocitiesVRAM1.SizeInBytes)
                    {
                        //when the nodes are near their targets, instead of bouncing around while coming to a stop, just snap them into position
                        float fastest = FindHighXYZ(layout.VelocitiesVRAM1, plot.ComputeBufferNodeCount, out int _);
                        if (fastest < 1 || steps > 20)
                        {
                            if (GlobalConfig.BulkLog)
                            {
                                Logging.RecordLogEvent("Preset done", graph: plot.InternalProtoGraph, filter: Logging.LogFilterType.BulkDebugLogFile);
                            }
                            layout.CompleteLayoutChange();
                        }
                    }
                }

                if (GlobalConfig.LayoutPositionsActive)
                {
                    _stepTimer.Stop();
                    plot.RecordComputeTime(stepMSTotal: _stepTimer.Elapsed.TotalMilliseconds,
                                           positionSetupTime: activePipeline.PositionSetupTime, positionShaderTime: activePipeline.PositionTime,
                                           velocitySetupTime: activePipeline.VelocitySetupTime, velocityShaderTime: activePipeline.VelocityTime,
                                           attributeSetupTime: attributeSetupTime, attributeShaderTime: attributeTime);
                    activePipeline.ResetTimers();
                    attributeSetupTime = 0;
                    attributeTime      = 0;
                }
            }
            catch (Exception e)
            {
                Logging.RecordException($"Error during layout compute: {e.Message}", e, plot.InternalProtoGraph);
                ErrorState = true;
            }
            finally
            {
                _stepTimer.Stop();
                layout.Lock.ExitUpgradeableReadLock();
            }


            if (_stepTimer.ElapsedMilliseconds > 100)
            {
                Logging.RecordLogEvent($"Compute step took {_stepTimer.ElapsedMilliseconds}ms", Logging.LogFilterType.Debug);
            }

            //DebugPrintOutputIntBuffer(layout.BlockMiddles!, "Middles", 100);
            //DebugPrintOutputFloatBuffer(layout.VelocitiesVRAM1!, "Vel1", 140);
            //DebugPrintOutputFloatBuffer(layout.PositionsVRAM1!, "pos1", 140);
            //DebugPrintOutputFloatBuffer(layout.PositionsVRAM2!, "pos2", 32);
            //DebugPrintOutputFloatBuffer(layout.AttributesVRAM, "Atts2", 32);

            lock (_lock)
            {
                _lastComputeMS.Add(_stepTimer.Elapsed.TotalMilliseconds);
                if (_lastComputeMS.Count > GlobalConfig.StatisticsTimeAvgWindow)
                {
                    _lastComputeMS = _lastComputeMS.TakeLast(GlobalConfig.StatisticsTimeAvgWindow).ToList();
                }
                AverageComputeTime = _lastComputeMS.Average();
            }

            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"Compute end {EngineID} graph  {plot.PID}:{plot.TID}", Logging.LogFilterType.BulkDebugLogFile);
            }

            return(layout.RenderVersion);
        }