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; }
/// <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; }
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); } }
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); }
/// <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); }