Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        private void DrawAddressSelectControls(PlottedGraph plot)
        {
            ImGui.Text("Address");
            ImGui.InputText("##AddressInput", ref _activeHighlights.AddrEntryText, 255);
            ImGui.SameLine();

            if (ImGui.Button("Add") ||
                ImGui.IsKeyPressed(ImGui.GetKeyIndex(ImGuiKey.Enter)) ||
                ImGui.IsKeyPressed(ImGui.GetKeyIndex(ImGuiKey.KeyPadEnter)))
            {
                string addrstring = _activeHighlights.AddrEntryText;
                addrstring = new string(addrstring.ToCharArray().Where(c => !char.IsWhiteSpace(c)).ToArray());

                if (addrstring.ToLower().StartsWith("0x"))
                {
                    addrstring = addrstring.Substring(2);
                }

                bool success = ulong.TryParse(addrstring, NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out ulong hexAddr);
                if (!success)
                {
                    success = ulong.TryParse(addrstring, NumberStyles.Integer, CultureInfo.CurrentCulture, out hexAddr);
                }

                if (success)
                {
                    _activeHighlights.AddrEntryText = "";
                    if (!_activeHighlights.SelectedAddresses.Contains(hexAddr))
                    {
                        plot.AddHighlightedAddress(hexAddr);
                        _activeHighlights.SelectedAddresses.Add(hexAddr);
                    }
                }
            }
        }
Esempio n. 3
0
 public void DrawExceptionSelectControls(PlottedGraph plot)
 {
     if (plot.HighlightedExceptionNodes.Any() && ImGui.Button("Clear"))
     {
         plot.RemoveHighlightedNodes(_activeHighlights.SelectedExceptionNodes, CONSTANTS.HighlightType.Exceptions);
         _activeHighlights.SelectedExceptionNodes.Clear();
     }
 }
Esempio n. 4
0
        /// 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:");
        }
Esempio n. 5
0
        private void DrawExceptionSelectBox(PlottedGraph plot)
        {
            uint[]? exceptionNodes = plot.InternalProtoGraph.GetExceptionNodes();
            if (exceptionNodes is null || exceptionNodes.Length == 0)
            {
                string caption = $"No exceptions recorded in thread ID {_ActiveGraph?.TID}";
                ImGuiUtils.DrawRegionCenteredText(caption);
                return;
            }

            string[] labels = exceptionNodes.Select(x => x.ToString()).ToArray();
            if (ImGui.BeginTable("##ExceptionsTable", 2, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail() - new Vector2(10, _activeHighlights.SelectedExceptionNodes.Any() ? 30 : 0)))
            {
                ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed, 160);
                ImGui.TableSetupColumn("Module");
                ImGui.TableSetupScrollFreeze(0, 1);
                ImGui.TableHeadersRow();

                foreach (uint nodeidx in exceptionNodes)
                {
                    NodeData?n = plot.InternalProtoGraph.GetNode(nodeidx);

                    if (n is not null)
                    {
                        ImGui.TableNextRow();
                        if (ImGui.TableNextColumn())
                        {
                            if (ImGui.Selectable($"0x{n.Address:X}", _activeHighlights.SelectedExceptionNodes.Contains(nodeidx), ImGuiSelectableFlags.SpanAllColumns))
                            {
                                if (_activeHighlights.SelectedExceptionNodes.Contains(nodeidx))
                                {
                                    _activeHighlights.SelectedExceptionNodes.Remove(nodeidx);
                                    plot.RemoveHighlightedNodes(new List <uint> {
                                        nodeidx
                                    }, CONSTANTS.HighlightType.Exceptions);
                                }
                                else
                                {
                                    _activeHighlights.SelectedExceptionNodes.Add(nodeidx);
                                    plot.AddHighlightedNodes(new List <uint> {
                                        nodeidx
                                    }, CONSTANTS.HighlightType.Exceptions);
                                }
                            }
                        }
                        if (ImGui.TableNextColumn())
                        {
                            ImGui.Text(System.IO.Path.GetFileName(plot.InternalProtoGraph.ProcessData.GetModulePath(n.GlobalModuleID)));
                        }
                    }
                }
                ImGui.EndTable();
            }
        }
Esempio n. 6
0
 private static void HandleMouseoverSym(PlottedGraph plot, moduleEntry module_modentry, symbolInfo syminfo)
 {
     module_modentry.symbols[syminfo.address] = syminfo;
     if (syminfo.hovered)
     {
         plot.AddHighlightedNodes(syminfo.threadNodes, CONSTANTS.HighlightType.Externals);
     }
     else
     {
         plot.RemoveHighlightedNodes(syminfo.threadNodes, CONSTANTS.HighlightType.Externals);
     }
 }
Esempio n. 7
0
        /// <summary>
        /// Set the highlight state of nodes in the attributes buffer so they can be animated/have their icon set
        /// </summary>
        /// <param name="cl">Thread specific Veldrid CommandList</param>
        /// <param name="graph">Graph with highlights to apply</param>
        /// <param name="attribsBuf">Attributes buffer to apply highlight data to</param>
        public static void ApplyHighlightAttributes(CommandList cl, PlottedGraph graph, DeviceBuffer attribsBuf)
        {
            graph.GetHighlightChanges(out List <uint> added, out List <uint> removed);

            if (added.Any() is true)
            {
                SetHighlightedNodes(cl, added, attribsBuf, CONSTANTS.HighlightType.Addresses);
            }

            if (removed.Any() is true)
            {
                UnsetHighlightedNodes(cl, removed, attribsBuf);
            }
        }
Esempio n. 8
0
        public void DrawAddressSelectBox(PlottedGraph plot)
        {
            if (ImGui.ListBox("##AddrListbox", ref selitem,
                              _activeHighlights.SelectedAddresses.Select(ad => $"0x{ad:X}").ToArray(),
                              _activeHighlights.SelectedAddresses.Count))
            {
                ulong address = _activeHighlights.SelectedAddresses[selitem];
                _activeHighlights.SelectedAddresses.RemoveAt(selitem);
                plot.HighlightedAddresses.Remove(address);
                List <uint> nodes = plot.InternalProtoGraph.ProcessData.GetNodesAtAddress(address, plot.TID);

                plot.LayoutState.Lock.EnterUpgradeableReadLock();
                plot.RemoveHighlightedNodes(nodes, CONSTANTS.HighlightType.Addresses);
                plot.LayoutState.Lock.ExitUpgradeableReadLock();
            }
        }
Esempio n. 9
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);
            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;
        }
Esempio n. 10
0
        private void DrawSymbolsSelectControls(PlottedGraph plot)
        {
            float height = 30;

            if (_activeHighlights.selectedHighlightTab == 0)
            {
                if (ImGui.BeginChild(ImGui.GetID("highlightSymsControls"), new Vector2(ImGui.GetContentRegionAvail().X, height)))
                {
                    ImGui.AlignTextToFramePadding();
                    ImGui.Text($"{_activeHighlights.SelectedSymbols.Count} highlighted symbols ({plot.HighlightedSymbolNodes.Count} nodes)");
                    ImGui.SameLine();
                    ImGui.Dummy(new Vector2(6, 10));
                    ImGui.SameLine();
                    if (ImGui.Button("Clear"))
                    {
                        foreach (var sym in _activeHighlights.SelectedSymbols)
                        {
                            symbolInfo symdat = _activeHighlights.displayedModules[sym.moduleID].symbols[sym.address];
                            symdat.selected = false;
                            _activeHighlights.displayedModules[sym.moduleID].symbols[sym.address] = symdat;
                        }

                        plot.LayoutState.Lock.EnterUpgradeableReadLock();
                        plot.RemoveHighlightedNodes(plot.HighlightedSymbolNodes, CONSTANTS.HighlightType.Externals);
                        plot.LayoutState.Lock.ExitUpgradeableReadLock();

                        _activeHighlights.SelectedSymbols.Clear();
                    }

                    /*
                     * ImGui.SameLine(ImGui.GetContentRegionAvail().X - 100);
                     * ImGui.PushStyleColor(ImGuiCol.Button, Themes.GetThemeColourUINT(Themes.eThemeColour.GraphBackground));
                     * ImGui.PushStyleColor(ImGuiCol.Text, WritableRgbaFloat.ToUint(Color.Cyan));
                     * if (ImGui.Button("Highlight Colour"))
                     * {
                     *  //todo: highlight colour picker
                     * }
                     * ImGui.PopStyleColor();
                     * ImGui.PopStyleColor();
                     */

                    ImGui.EndChild();
                }
            }
        }
Esempio n. 11
0
        private static void DrawPreviewZoomEnvelope(PlottedGraph plot, Vector2 subGraphPosition)
        {
            ImDrawListPtr imdp         = ImGui.GetWindowDrawList();
            float         previewBaseY = subGraphPosition.Y + EachGraphHeight;

            plot.GetPreviewVisibleRegion(new Vector2(EachGraphWidth, EachGraphHeight), PreviewProjection, out Vector2 TopLeft, out Vector2 BaseRight);

            float C1Y = previewBaseY - TopLeft.Y;
            float C2Y = previewBaseY - BaseRight.Y;

            bool verySmall = Math.Abs(C1Y - C2Y) < 20;
            uint colour    = verySmall ? Themes.GetThemeColourUINT(Themes.eThemeColour.Emphasis1) :
                             Themes.GetThemeColourUINT(Themes.eThemeColour.PreviewZoomEnvelope);

            float C1X = Math.Max(subGraphPosition.X + TopLeft.X, subGraphPosition.X);
            float C2X = Math.Min(subGraphPosition.X + BaseRight.X, subGraphPosition.X + EachGraphWidth - 1);

            C1Y = Math.Min(previewBaseY - 1, C1Y);
            C2Y = Math.Max(subGraphPosition.Y, C2Y);

            if (C1Y > subGraphPosition.Y && C1Y < previewBaseY && C2X > subGraphPosition.X)
            {
                imdp.AddLine(new Vector2(C1X, C1Y), new Vector2(C2X, C1Y), colour);
            }

            if (C2Y > subGraphPosition.Y && C2Y < previewBaseY)
            {
                imdp.AddLine(new Vector2(C2X, C2Y), new Vector2(C1X, C2Y), colour);
            }

            if (C2Y < previewBaseY && C1Y > subGraphPosition.Y)
            {
                C2Y = Math.Max(C2Y, subGraphPosition.Y);
                if (C2X > subGraphPosition.X && C2X < subGraphPosition.X + EachGraphWidth)
                {
                    imdp.AddLine(new Vector2(C2X, C1Y), new Vector2(C2X, C2Y), colour);
                }
                if (C1X > subGraphPosition.X && C1X < subGraphPosition.X + EachGraphWidth)
                {
                    imdp.AddLine(new Vector2(C1X, C2Y), new Vector2(C1X, C1Y), colour);
                }
            }
        }
Esempio n. 12
0
        private void update_rendering(PlottedGraph graph)
        {
            ProtoGraph protoGraph = graph.InternalProtoGraph;

            if (protoGraph == null || protoGraph.EdgeCount == 0)
            {
                return;
            }

            //if (graph.NodesDisplayData == null)// || !graph.setGraphBusy(true, 2))
            //	return;

            if (graph.ReplayState == PlottedGraph.REPLAY_STATE.Ended && protoGraph.Terminated)
            {
                graph.ResetAnimation();
            }

            //update the render if there are more verts/edges or graph is being resized

            graph.RenderGraph();

            if (!protoGraph.Terminated)
            {
                if (graph.IsAnimated)
                {
                    graph.ProcessLiveAnimationUpdates(out int doneCount);
                }
            }
            else
            {
                if (graph.InternalProtoGraph.TraceData.DiscardTraceData is false &&
                    (graph.ReplayState == PlottedGraph.REPLAY_STATE.Playing ||
                     graph._userSelectedAnimPosition != -1))
                {
                    if (--_nextReplayStep <= 0)
                    {
                        graph.ProcessReplayUpdates();
                        _nextReplayStep = _FramesBetweenAnimationUpdates;
                    }
                }
            }
        }
Esempio n. 13
0
        private void HandleSelectedSym(PlottedGraph plot, moduleEntry module_modentry, symbolInfo syminfo)
        {
            syminfo.selected = !syminfo.selected;
            module_modentry.symbols[syminfo.address] = syminfo;

            plot.LayoutState.Lock.EnterUpgradeableReadLock();
            plot.LayoutState.GetAttributes(plot.ActiveLayoutStyle, out float[]? attribsArray);


            if (syminfo.selected)
            {
                plot.AddHighlightedNodes(syminfo.threadNodes, CONSTANTS.HighlightType.Externals);
                _activeHighlights.SelectedSymbols.Add(syminfo);
            }
            else
            {
                plot.RemoveHighlightedNodes(syminfo.threadNodes, CONSTANTS.HighlightType.Externals);
                _activeHighlights.SelectedSymbols = _activeHighlights.SelectedSymbols.Where(s => s.address != syminfo.address).ToList();
            }
            plot.LayoutState.Lock.ExitUpgradeableReadLock();
        }
Esempio n. 14
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;
        }
Esempio n. 15
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);
        }
Esempio n. 16
0
        /// <summary>
        /// Used the velocity buffer to move the nodes in the positions buffer
        /// </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 unsafe void RenderPosition(ResourceSetDescription RSetDesc, CommandList cl, PlottedGraph plot, float delta)
        {
            _timer.Restart();
            cl.Begin();

            ResourceSet resourceSet = _gd.ResourceFactory.CreateResourceSet(RSetDesc);
            //Debug.Assert(!VeldridGraphBuffers.DetectNaN(_gd, positions));
            //Debug.Assert(!VeldridGraphBuffers.DetectNaN(_gd, velocities));

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

            PositionShaderParams parameters = new PositionShaderParams
            {
                delta     = delta,
                nodeCount = (uint)plot.RenderedNodeCount()
            };

            //Logging.WriteConsole($"RenderPosition Parambuffer Size is {(uint)Unsafe.SizeOf<PositionShaderParams>()}");

            cl.UpdateBuffer(_positionParamsBuffer, 0, parameters);
            cl.SetPipeline(_positionComputePipeline);
            cl.SetComputeResourceSet(0, resourceSet);
            cl.Dispatch((uint)Math.Ceiling(plot.LayoutState.PositionsVRAM1 !.SizeInBytes / (256.0 * sizeof(Vector4))), 1, 1);
            cl.End();
            _timer.Stop();
            PositionSetupTime = _timer.Elapsed.TotalMilliseconds;

            _timer.Restart();
            _gd !.SubmitCommands(cl);
            _gd !.WaitForIdle();
            _gd.DisposeWhenIdle(resourceSet);
            _timer.Stop();
            PositionTime = _timer.Elapsed.TotalMilliseconds;
        }
Esempio n. 17
0
 private void HandleClickedGraph(PlottedGraph plot) => clickedGraph = plot;
Esempio n. 18
0
        public void Draw(PlottedGraph LatestActiveGraph)
        {
            if (LatestActiveGraph == null)
            {
                return;
            }

            if (_ActiveGraph != LatestActiveGraph)
            {
                _ActiveGraph = LatestActiveGraph;
                ThreadHighlightSettings?foundHighlights;
                if (!graphSettings.TryGetValue(_ActiveGraph, out foundHighlights) || foundHighlights is null)
                {
                    foundHighlights = new ThreadHighlightSettings();
                    graphSettings.Add(_ActiveGraph, foundHighlights);
                }
                _activeHighlights = foundHighlights;
            }
            Vector2 Size = ImGui.GetWindowSize();

            Size.Y = ImGui.GetContentRegionAvail().Y;

            if (ImGui.BeginChild("#highlightControls", Size))
            {
                if (!PopoutHighlight && ImGui.Button("Popout"))
                {
                    ImGui.SetCursorPosX(ImGui.GetContentRegionAvail().X - 50);
                    PopoutHighlight          = true;
                    PopoutHighlightSkipFrame = true;
                }


                else
                {
                    ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags.AutoSelectNewTabs;
                    if (ImGui.BeginTabBar("Highlights Tab Bar", tab_bar_flags))
                    {
                        if (ImGui.BeginTabItem("Externals/Symbols"))
                        {
                            _activeHighlights.selectedHighlightTab = 0;
                            DrawSymbolsSelectBox(reserveSize: 40); //todo: unbadify this height choice
                            DrawSymbolsSelectControls(_ActiveGraph);
                            ImGui.EndTabItem();
                        }
                        if (ImGui.BeginTabItem("Addresses"))
                        {
                            _activeHighlights.selectedHighlightTab = 1;
                            DrawAddressSelectControls(_ActiveGraph);
                            if (_activeHighlights.SelectedAddresses.Any())
                            {
                                DrawAddressSelectBox(_ActiveGraph);
                            }
                            ImGui.EndTabItem();
                        }
                        if (ImGui.BeginTabItem("Exceptions"))
                        {
                            _activeHighlights.selectedHighlightTab = 2;
                            DrawExceptionSelectBox(_ActiveGraph);
                            DrawExceptionSelectControls(_ActiveGraph);
                            ImGui.EndTabItem();
                        }
                        ImGui.EndTabBar();
                    }
                }
                ImGui.EndChild();
            }
        }
Esempio n. 19
0
        private void DrawModSymTreeNodes(PlottedGraph plot)
        {
            string LowerFilterText = _activeHighlights.SymFilterText.ToLower();

            foreach (moduleEntry module_modentry in _activeHighlights.displayedModules.Values)
            {
                var  keyslist            = module_modentry.symbols.Keys.ToArray();
                bool hasFilterMatches    = false;
                bool moduleMatchesFilter = false;
                if (_activeHighlights.SymFilterText.Length == 0)
                {
                    hasFilterMatches = true;
                }
                else if (module_modentry.path.ToLower().Contains(LowerFilterText))
                {
                    moduleMatchesFilter = true;
                    hasFilterMatches    = true;
                }
                else
                {
                    foreach (ulong symaddr in keyslist)
                    {
                        symbolInfo syminfo = module_modentry.symbols[symaddr];
                        if (syminfo.name.ToLower().Contains(LowerFilterText))
                        {
                            hasFilterMatches = true;
                            break;
                        }
                    }
                }

                if (hasFilterMatches)
                {
                    if (ImGui.TreeNode($"{module_modentry.path}"))
                    {
                        float cursX = ImGui.GetCursorPosX() + 75;
                        foreach (ulong symaddr in keyslist)
                        {
                            symbolInfo syminfo = module_modentry.symbols[symaddr];

                            if (_activeHighlights.SymFilterText.Length > 0 &&
                                !moduleMatchesFilter &&
                                !syminfo.name.ToLower().Contains(_activeHighlights.SymFilterText.ToLower())
                                )
                            {
                                continue;
                            }

                            ImGui.SetCursorPosX(10);
                            ImGui.BeginGroup();
                            if (ImGui.Selectable($"{syminfo.name}", syminfo.selected))
                            {
                                HandleSelectedSym(plot, module_modentry, syminfo);
                            }
                            if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
                            {
                                ImGui.OpenPopup("HighlightColorPicker");
                            }
                            if (ImGui.BeginPopup("HighlightColorPicker"))
                            {
                                ImGui.PushStyleColor(ImGuiCol.Text, 0xffffffff);
                                ImGui.Text($"Configuring highlight colour for {syminfo.name} (0x{syminfo.address}:x)");
                                ImGuiColorEditFlags flags = ImGuiColorEditFlags.NoInputs;
                                flags |= ImGuiColorEditFlags.AlphaBar;
                                if (ImGui.ColorPicker4("Highlight Colour", ref _activeColorPick1, flags))
                                {
                                    foreach (uint node in syminfo.threadNodes)
                                    {
                                        plot.SetCustomHighlightColour((int)node, _activeColorPick1);
                                    }
                                }
                                ImGui.Text("Highlight active:");
                                ImGui.SameLine();
                                if (SmallWidgets.ToggleButton("NodeActiveHighlightToggle", syminfo.selected, "Node is highlighted"))
                                {
                                    HandleSelectedSym(plot, module_modentry, syminfo);
                                }
                                ImGui.PopStyleColor();
                                ImGui.EndPopup();
                            }

                            ImGui.SameLine(190);
                            ImGui.Text($"0x{syminfo.address:X}");
                            ImGui.SameLine(305);
                            ImGui.Text($"{syminfo.threadNodes.Count}");
                            ImGui.EndGroup();

                            if (!syminfo.selected)
                            {
                                if (ImGui.IsItemHovered(ImGuiHoveredFlags.None))
                                {
                                    if (syminfo.hovered == false)
                                    {
                                        syminfo.hovered = true;
                                        HandleMouseoverSym(plot, module_modentry, syminfo);
                                    }
                                }
                                else
                                {
                                    if (syminfo.hovered == true)
                                    {
                                        syminfo.hovered = false;
                                        HandleMouseoverSym(plot, module_modentry, syminfo);
                                    }
                                }
                            }
                        }

                        ImGui.TreePop();
                    }
                }
            }
        }
Esempio n. 20
0
        /// <summary>
        /// Draw a preview graph texture on the preview pane
        /// </summary>
        /// <param name="plot">The graph being drawn</param>
        /// <param name="xPadding">horizontal padding</param>
        /// <param name="captionHeight">height of the caption</param>
        /// <param name="captionBackgroundcolor">contrast background colour of the caption</param>
        /// <param name="canHover">output flag states if we can safely draw a mouseover tooltip</param>
        /// <param name="mainWidgetSize">Size of the maingraph widget, used for projecting the zoom envelope</param>
        /// <returns>The graph was clicked</returns>
        private bool DrawPreviewGraph(PlottedGraph plot, float xPadding, float captionHeight, uint captionBackgroundcolor, out bool canHover, Vector2 mainWidgetSize)
        {
            ImDrawListPtr imdp    = ImGui.GetWindowDrawList(); //draw on and clipped to this window
            bool          clicked = false;

            canHover = false;
            if (plot == null)
            {
                return(clicked);
            }

            int graphNodeCount = plot.GraphNodeCount();

            if (graphNodeCount == 0)
            {
                return(clicked);
            }

            plot.GetLatestTexture(out Texture previewTexture);
            if (previewTexture == null)
            {
                return(clicked);
            }

            bool isSelected = plot.TID == selectedGraphTID;

            canHover = true;

            //copy in the actual rendered graph
            ImGui.SetCursorPosY(ImGui.GetCursorPosY());
            Vector2 subGraphPosition = ImGui.GetCursorScreenPos() + new Vector2(xPadding, 0);

            IntPtr CPUframeBufferTextureId = _ImGuiController !.GetOrCreateImGuiBinding(_gd !.ResourceFactory, previewTexture, $"PreviewPlot{plot.TID}");

            imdp.AddImage(user_texture_id: CPUframeBufferTextureId,
                          p_min: subGraphPosition,
                          p_max: new Vector2(subGraphPosition.X + EachGraphWidth, subGraphPosition.Y + EachGraphHeight),
                          uv_min: new Vector2(0, 1),
                          uv_max: new Vector2(1, 0));

            float borderThickness     = Themes.GetThemeSize(Themes.eThemeSize.PreviewSelectedBorder);
            float halfBorderThickness = (float)Math.Floor(borderThickness / 2f);

            if (isSelected)
            {
                DrawPreviewZoomEnvelope(plot, subGraphPosition);

                //Draw the thicker selected graph border
                if (borderThickness > 0)
                {
                    imdp.AddRect(
                        p_min: new Vector2(subGraphPosition.X + halfBorderThickness, subGraphPosition.Y + halfBorderThickness),
                        p_max: new Vector2((subGraphPosition.X + EachGraphWidth - halfBorderThickness), subGraphPosition.Y + EachGraphHeight - halfBorderThickness),
                        col: GetGraphBorderColour(plot), 0, ImDrawFlags.None, borderThickness);
                }
            }

            //write the caption
            string Caption = $"TID:{plot.TID} {graphNodeCount} nodes {(isSelected ? "[Selected]" : "")}";

            ImGui.SetCursorPosX(ImGui.GetCursorPosX());
            Vector2 captionBGStart = subGraphPosition + new Vector2(borderThickness, borderThickness);
            Vector2 captionBGEnd   = new Vector2((captionBGStart.X + EachGraphWidth - borderThickness * 2), captionBGStart.Y + captionHeight);

            imdp.AddRectFilled(p_min: captionBGStart, p_max: captionBGEnd, col: captionBackgroundcolor);
            ImGui.PushStyleColor(ImGuiCol.Text, Themes.GetThemeColourUINT(Themes.eThemeColour.PreviewText));
            ImGui.SetCursorPosX(ImGui.GetCursorPosX() + CONSTANTS.UI.PREVIEW_PANE_X_PADDING + borderThickness + 1);
            ImGui.SetCursorPosY(ImGui.GetCursorPosY() + borderThickness);
            ImGui.Text(Caption);
            ImGui.PopStyleColor();
            ImGui.SetCursorPosX(ImGui.GetCursorPosX() + EachGraphWidth - 48);

            //live thread activity plot
            if (ActiveTrace is not null && !ActiveTrace.WasLoadedFromSave)
            {
                ImGui.SetCursorPosY(ImGui.GetCursorPosY() - captionHeight);

                float maxVal;
                float[]? invalues = null;
                if (plot.InternalProtoGraph.TraceReader != null)
                {
                    plot.InternalProtoGraph.TraceReader.RecentMessageRates(out invalues);
                }
                if (invalues == null || invalues.Length == 0)
                {
                    invalues = new List <float>()
                    {
                        0, 0, 0, 0, 0
                    }.ToArray();
                    maxVal = 100;
                }
                else
                {
                    maxVal = invalues.Max();
                }
                ImGui.PushStyleColor(ImGuiCol.FrameBg, captionBackgroundcolor);
                ImGui.PlotLines("", ref invalues[0], invalues.Length, 0, "", 0, maxVal, new Vector2(40, captionHeight));
                if (ImGui.IsItemHovered())
                {
                    canHover = false; //The PlotLines widget doesn't allow disabling the mouseover, so have to prevent our mousover to avoid a merged tooltip
                }
                ImGui.PopStyleColor();
            }


            //invisible button to detect graph click

            ImGui.SetCursorPos(new Vector2(1, ImGui.GetCursorPosY() - (float)(captionHeight)));
            if (ImGui.InvisibleButton("PrevGraphBtn" + plot.TID, new Vector2(EachGraphWidth, EachGraphHeight - 2)) || ImGui.IsItemActive())
            {
                clicked = true;
                if (isSelected)
                {
                    Vector2 clickPos    = ImGui.GetMousePos();
                    Vector2 clickOffset = clickPos - subGraphPosition;
                    clickOffset.Y = EachGraphHeight - clickOffset.Y;
                    plot.MoveCameraToPreviewClick(clickOffset, new Vector2(EachGraphWidth, EachGraphHeight),
                                                  mainGraphWidgetSize: mainWidgetSize, PreviewProjection);
                }
            }
            return(clicked);
        }
Esempio n. 21
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);
        }
Esempio n. 22
0
        /// <summary>
        /// Creates an array of metadata for basic blocks used for basic-block-centric graph layout
        /// item[0] = blockID
        /// item[1] = offsetFromCenter; number of nodes ahead the center node is
        /// item[2] = centerPseudoBlockTopID; top of the block this node is in
        /// item[3] = centerPseudoBlockBaseID; base of the block this node is in
        /// </summary>
        /// <param name="plot">The graph being plotted</param>
        /// <param name="blockData">Output description of basic block information for each node</param>
        /// <param name="blockMiddles">Output List of basic block middle nodes</param>
        private static bool CreateBlockMetadataBuf(PlottedGraph plot, out NODE_BLOCK_METADATA_COMPUTEBUFFER[] blockData, out int[] blockMiddles)
        {
            ProtoGraph graph = plot.InternalProtoGraph;

            List <int>[] nodeNeighboursArray = plot.GetNodeNeighboursArray();
            int          nodeCount           = nodeNeighboursArray.Length;

            NODE_BLOCK_METADATA_COMPUTEBUFFER[] blockDataInts = new NODE_BLOCK_METADATA_COMPUTEBUFFER[nodeCount];
            Dictionary <int, int>      blockMiddlesDict       = new Dictionary <int, int>();
            List <int>                 blockMiddleNodesList   = new List <int>();
            Dictionary <int, NodeData> exceptionBlocks        = new();

            /*
             * Step 1: Build a list of active blocks (ie: blocks which currently have instructions in,
             * as opposed to blocks which have been split into new ones by control flow
             */
            List <int>            activeBlockIDs                 = new();
            Dictionary <int, int> NodeBlockToBlockMetaIndex      = new();
            Dictionary <int, int> BlockMetaToBlockFirstLastIndex = new();

            for (var i = 0; i < nodeCount; i++)
            {
                NodeData n           = graph.NodeList[i];
                int      nodeBlockID = (int)n.BlockID;
                if (NodeBlockToBlockMetaIndex.TryGetValue(nodeBlockID, out int metaBlockID) is false || activeBlockIDs.Contains(metaBlockID) is false)
                {
                    metaBlockID = activeBlockIDs.Count;
                    NodeBlockToBlockMetaIndex[nodeBlockID]      = metaBlockID;
                    BlockMetaToBlockFirstLastIndex[metaBlockID] = nodeBlockID;
                    activeBlockIDs.Add(metaBlockID);

                    if (n.CausedException)
                    {
                        exceptionBlocks[metaBlockID] = n;
                    }
                }
            }


            //Step 2: Build the list of block center nodes that the block velocity shader will run over
            blockMiddleNodesList.Capacity = activeBlockIDs.Count;
            foreach (int blockIdx in activeBlockIDs)
            {
                if (blockIdx == -1)
                {
                    blockMiddleNodesList.Add(-1);
                }

                int originalBlockIndex = BlockMetaToBlockFirstLastIndex[blockIdx];

                if (originalBlockIndex < 0 || originalBlockIndex >= graph.BlocksFirstLastNodeList.Count)
                {
                    blockMiddlesDict[blockIdx] = -1; //instructions sent and not executed? why?
                    blockMiddleNodesList.Add((int)-1);

                    continue;
                }

                var firstIdx_LastIdx = graph.BlocksFirstLastNodeList[originalBlockIndex];
                if (firstIdx_LastIdx == null)
                {
                    continue;
                }

                if (firstIdx_LastIdx.Item1 == firstIdx_LastIdx.Item2)
                {
                    if (blockMiddleNodesList.Contains((int)firstIdx_LastIdx.Item1))
                    {
                        continue;
                    }

                    blockMiddlesDict[blockIdx] = (int)firstIdx_LastIdx.Item1; //1 node block, top/mid/base is the same
                    blockMiddleNodesList.Add((int)firstIdx_LastIdx.Item1);

                    //Debug.Assert(blockIdx == (blockMiddleNodesList.Count-1));
                }
                else
                {
                    var block = graph.ProcessData.BasicBlocksList[originalBlockIndex]?.Item2;
                    Debug.Assert(block is not null);
                    int midIdx    = (int)Math.Ceiling((block.Count - 1.0) / 2.0);
                    var middleIns = block[midIdx];
                    if (!middleIns.GetThreadVert(graph.ThreadID, out uint centerNodeID))
                    {
                        blockMiddlesDict[blockIdx] = -1; //instructions sent and not executed? why?
                        //Debug.Assert(false, $"Instruction 0x{middleIns.address:X} not found in thread {tid}");
                    }
                    else
                    {
                        //if (blockMiddleNodesList.Contains((int)centerNodeID))
                        //{
                        //    continue;
                        //}
                        blockMiddlesDict[blockIdx] = (int)centerNodeID;
                        blockMiddleNodesList.Add((int)centerNodeID);
                        //Debug.Assert(blockIdx == (blockMiddleNodesList.Count - 1));
                    }
                }
            }


            /*
             * Step 3: Build the block metadata buffer which allows the position and velocity shaders to process each
             * node in the context of the block it is in
             */
            int externals = 0;

            for (uint nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++)
            {
                NodeData?n = graph.GetNode(nodeIdx);
                Debug.Assert(n is not null);

                int blockID;
                int offsetFromCenter;
                Tuple <uint, uint>?FirstLastIdx;
                if (!n.IsExternal)
                {
                    if (n.BlockID >= graph.BlocksFirstLastNodeList.Count)
                    {
                        continue;
                    }

                    FirstLastIdx = graph.BlocksFirstLastNodeList[(int)n.BlockID]; //bug: this can happen before bflnl is filled
                    if (FirstLastIdx == null)
                    {
                        continue;
                    }

                    blockID = NodeBlockToBlockMetaIndex[(int)n.BlockID];
                    if (!blockMiddlesDict.ContainsKey(blockID))
                    {
                        continue;
                    }

                    var blockEntry = graph.ProcessData.BasicBlocksList[(int)n.BlockID];
                    Debug.Assert(blockEntry is not null);
                    int blockNodeCount = blockEntry.Item2.Count;
                    if (exceptionBlocks.TryGetValue(blockID, out NodeData? exceptionNode) && exceptionNode is not null)
                    {
                        for (int bIdx = 0; bIdx < blockNodeCount; bIdx++)
                        {
                            if (blockEntry.Item2[bIdx].Address == exceptionNode.Address)
                            {
                                blockNodeCount = bIdx + 1;
                                break;
                            }
                        }
                    }
                    int midIdx = (int)Math.Ceiling((blockNodeCount - 1.0) / 2.0);
                    offsetFromCenter = n.BlockIndex - midIdx;
                }
                else
                {
                    externals       += 1;
                    FirstLastIdx     = new Tuple <uint, uint>(n.Index, n.Index);
                    offsetFromCenter = 0;
                    blockMiddleNodesList.Add((int)n.Index);

                    //external nodes dont have a block id so just give them a unique one
                    //all that matters in the shader is it's unique
                    blockID = blockMiddleNodesList.Count;
                    blockMiddlesDict[blockID] = (int)n.Index;
                }


                int blockTopNodeIndex  = -1;
                int blockBaseNodeIndex = -1;
                if (offsetFromCenter is 0)
                {
                    if (graph.GetNode(FirstLastIdx.Item1)?.IncomingNeighboursSet.Count > 0)
                    {
                        blockTopNodeIndex = (int)FirstLastIdx.Item1;
                    }
                    else
                    {
                        //these are all back edges, which have 0 force

                        /*
                         * //the top of the block wasnt connected to anything
                         * //there might be a connection below though
                         * List<InstructionData>? blockInslist = graph.ProcessData.BasicBlocksList[(int)n.BlockID]?.Item2;
                         * if (blockInslist is not null)
                         * {
                         *  for (var i = 1; i < blockInslist.Count; i++)
                         *  {
                         *      uint n2Idx = (uint)(FirstLastIdx.Item1 + i);
                         *      NodeData? n2 = graph.GetNode(n2Idx);
                         *      if (n2 is not null && n2.IncomingNeighboursSet.Count > 0)
                         *      {
                         *          blockTopNodeIndex = (int)n2Idx;
                         *          break;
                         *      }
                         *  }
                         * }
                         */
                    }

                    if (graph.GetNode(FirstLastIdx.Item2)?.OutgoingNeighboursSet.Count > 0)
                    {
                        blockBaseNodeIndex = (int)FirstLastIdx.Item2;
                    }
                }

                blockDataInts[nodeIdx].MetaBlockIndex    = blockID;
                blockDataInts[nodeIdx].OffsetFromCenter  = offsetFromCenter;
                blockDataInts[nodeIdx].BlockTopEdgeList  = blockTopNodeIndex;
                blockDataInts[nodeIdx].BlockBaseEdgeList = blockBaseNodeIndex != blockTopNodeIndex ? blockBaseNodeIndex : -1;
                //Debug.Assert(offsetFromCenter is not 0 || n.IsExternal || (blockMiddleNodesList[blockID] == (int)nodeIdx));
            }

            blockMiddles = blockMiddleNodesList.ToArray();
            blockData    = blockDataInts;

            return(true);
        }
Esempio n. 23
0
        /// <summary>
        /// Update the node attributes compute VRAM buffer (alpha, node size, mouseover details)
        /// </summary>
        /// <param name="cl">Thread-specific CommandList</param>
        /// <param name="graph">ProtoGraph being drawn</param>
        /// <param name="inputAttributes">Attributes buffer being updated</param>
        /// <param name="resources">Shader resources ResourceSet</param>
        /// <param name="delta">Time-delta from the last update</param>
        /// <param name="mouseoverNodeID">Index of the node the mouse is over</param>
        /// <param name="useAnimAttribs">Flag to specify the graph is in animated-alpha mode</param>
        private unsafe void RenderNodeAttribs(CommandList cl, PlottedGraph graph, DeviceBuffer inputAttributes,
                                              ResourceSet resources, float delta, int mouseoverNodeID, bool useAnimAttribs)
        {
            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"RenderNodeAttribs  {this.EngineID}", Logging.LogFilterType.BulkDebugLogFile);
            }
            AttribShaderParams parms = new AttribShaderParams
            {
                delta         = delta,
                hoveredNodeID = mouseoverNodeID,
                nodeCount     = (uint)Math.Min(graph.RenderedNodeCount(), graph.LayoutState.AttributesVRAM1 !.SizeInBytes / 16),
                MinimumAlpha  = GlobalConfig.AnimatedFadeMinimumAlpha,
                hoverMode     = (mouseoverNodeID != -1) ? 1 : 0,
                isAnimated    = useAnimAttribs ? 1: 0
            };


            graph.GetActiveNodeIndexes(out List <uint> pulseNodes, out List <uint> lingerNodes, out uint[] deactivatedNodes);

            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"RenderNodeAttribs {this.EngineID} updating attribsbuf {inputAttributes.Name}", Logging.LogFilterType.BulkDebugLogFile);
            }

            cl.UpdateBuffer(_attribsParamsBuffer, 0, parms);

            float currentPulseAlpha = Math.Max(GlobalConfig.AnimatedFadeMinimumAlpha, GraphicsMaths.getPulseAlpha());

            //todo - merge contiguous regions to reduce command count
            float[] valArray = new float[3];
            foreach (uint idx in pulseNodes)
            {
                if (idx >= graph.RenderedNodeCount())
                {
                    break;
                }

                if (inputAttributes.SizeInBytes <= idx * 4 * sizeof(float) + (2 * sizeof(float)))
                {
                    break;
                }

                valArray[0] = GlobalConfig.NodeSize; //start big
                valArray[1] = 1.0f;                  //full alpha
                valArray[2] = 1.0f;                  //pulse
                fixed(float *dataPtr = valArray)
                {
                    Debug.Assert((idx * 4 * sizeof(float) + valArray.Length * sizeof(float)) < inputAttributes.SizeInBytes);
                    cl.UpdateBuffer(inputAttributes, idx * 4 * sizeof(float), (IntPtr)dataPtr, (uint)valArray.Length * sizeof(float));
                }
            }

            //make the active node pulse
            if (graph.IsAnimated)
            {
                uint activeNodeIdx = graph.LastAnimatedVert;
                if (!lingerNodes.Contains(activeNodeIdx))
                {
                    valArray[0] = currentPulseAlpha;
                    fixed(float *dataPtr = valArray)
                    {
                        uint nodeAlphaOffset = (activeNodeIdx * 4 * sizeof(float)) + (2 * sizeof(float));

                        if (nodeAlphaOffset + sizeof(float) <= inputAttributes.SizeInBytes)
                        {
                            cl.UpdateBuffer(inputAttributes, nodeAlphaOffset, (IntPtr)dataPtr, sizeof(float));
                        }
                    }
                }
            }

            foreach (uint idx in lingerNodes)
            {
                if (idx >= graph.RenderedNodeCount())
                {
                    break;
                }

                if (inputAttributes.SizeInBytes <= idx * 4 * sizeof(float) + (2 * sizeof(float)))
                {
                    break;
                }

                valArray[0] = 2.0f + currentPulseAlpha;
                fixed(float *dataPtr = valArray)
                {
                    Debug.Assert((idx * 4 * sizeof(float) + (2 * sizeof(float)) + sizeof(float)) < inputAttributes.SizeInBytes);
                    cl.UpdateBuffer(inputAttributes, idx * 4 * sizeof(float) + (2 * sizeof(float)), (IntPtr)dataPtr, sizeof(float));
                }
            }

            foreach (uint idx in deactivatedNodes)
            {
                if (idx >= graph.RenderedNodeCount())
                {
                    break;
                }

                if (inputAttributes.SizeInBytes <= idx * 4 * sizeof(float) + (2 * sizeof(float)))
                {
                    break;
                }

                valArray[0] = 0.8f;
                fixed(float *dataPtr = valArray)
                {
                    Debug.Assert((idx * 4 * sizeof(float) + (2 * sizeof(float)) + sizeof(float)) < inputAttributes.SizeInBytes);
                    cl.UpdateBuffer(inputAttributes, idx * 4 * sizeof(float) + (2 * sizeof(float)), (IntPtr)dataPtr, sizeof(float));
                }
            }

            if (graph.HighlightsChanged)
            {
                ApplyHighlightAttributes(cl, graph, inputAttributes);
            }

            cl.SetPipeline(_nodeAttribComputePipeline);
            cl.SetComputeResourceSet(0, resources);

            cl.Dispatch((uint)Math.Ceiling(inputAttributes.SizeInBytes / (256.0 * sizeof(Vector4))), 1, 1);
        }
Esempio n. 24
0
        /// <summary>
        /// Draw the preview graph widget
        /// </summary>
        public void DrawWidget(Vector2 mainWidgetSize)
        {
            bool         showToolTip      = false;
            PlottedGraph?latestHoverGraph = null;
            TraceRecord? activeTrace      = ActiveTrace;

            if (activeTrace == null)
            {
                return;
            }


            float captionHeight = ImGui.CalcTextSize("123456789").Y;

            DrawnPreviewGraphs = activeTrace.GetPlottedGraphs();
            List <int> indexes = GetGraphOrder(trace: activeTrace, graphs: DrawnPreviewGraphs);
            uint       captionBackgroundcolor = Themes.GetThemeColourUINT(Themes.eThemeColour.PreviewTextBackground);

            ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, CONSTANTS.UI.PREVIEW_PANE_Y_SEP));

            //Graph drawing loop
            if (ImGui.BeginTable("PrevGraphsTable", 1, ImGuiTableFlags.Borders, new Vector2(CONSTANTS.UI.PREVIEW_PANE_WIDTH, ImGui.GetContentRegionAvail().Y)))
            {
                foreach (int graphIdx in indexes)
                {
                    PlottedGraph plot     = DrawnPreviewGraphs[graphIdx];
                    float        xPadding = CONSTANTS.UI.PREVIEW_PANE_X_PADDING;
                    if (plot == null || plot.GraphNodeCount() == 0)
                    {
                        continue;
                    }

                    ImGui.TableNextRow();
                    ImGui.TableSetColumnIndex(0);

                    if (DrawPreviewGraph(plot, xPadding, captionHeight, captionBackgroundcolor, out bool canHover, mainWidgetSize))
                    {
                        var MainGraphs = plot.InternalProtoGraph.TraceData.GetPlottedGraphs();
                        HandleClickedGraph(MainGraphs[graphIdx]);
                    }

                    if (canHover && ImGui.IsItemHovered(ImGuiHoveredFlags.None) && !(ImGui.IsMouseDown(ImGuiMouseButton.Left)))
                    {
                        latestHoverGraph = plot;
                        showToolTip      = true;
                    }
                }
                ImGui.EndTable();
            }
            ImGui.PopStyleVar();


            ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(5, 5));
            ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5));
            ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(5, 5));
            ImGui.PushStyleColor(ImGuiCol.Border, 0x77999999);

            HoveredGraph = latestHoverGraph;
            bool showedCtx = HandlePreviewGraphContextMenu();

            bool veryRecentPopup = showedCtx || _lastCtxMenu.AddMilliseconds(250) > DateTime.Now;

            if (showToolTip && !veryRecentPopup && HoveredGraph is not null)
            {
                DrawGraphTooltip(HoveredGraph);
            }
            ImGui.PopStyleVar(3);
            ImGui.PopStyleColor();
        }
Esempio n. 25
0
        /// <summary>
        /// Iterates over the position of every node, translating it to a widget position
        /// Returns the offsets of the furthest nodes of the edges of the widget
        /// To fit the graph in the screen, each offset needs to be as small as possible above 0
        ///
        /// Acquires reader lock
        /// </summary>
        /// <param name="graphWidgetSize">Size of the rendering widget</param>
        /// <param name="graph">Graph being displayed in the widget</param>
        /// <param name="isPreview">True if preview widget, false if main</param>
        /// <param name="xoffsets">Furthest from the left and right sides of the widget</param>
        /// <param name="yoffsets">Furthest from the top and bottom of the widget</param>
        /// <param name="zoffsets">Furthest from in front of/behind the camera lens in the Z direction</param>
        /// <returns>true if a meaningful result was returned</returns>
        public static bool GetWidgetFitOffsets(Vector2 graphWidgetSize, PlottedGraph graph, bool isPreview,
                                               out Vector2 xoffsets, out Vector2 yoffsets, out Vector2 zoffsets)
        {
            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"GetWidgetFitOffsets Start {graph.TID} layout", Logging.LogFilterType.BulkDebugLogFile);
            }
            xoffsets = new Vector2(0, 0);
            yoffsets = new Vector2(0, 0);
            zoffsets = new Vector2(0, 0);
            float zoom = isPreview ? graph.CameraState.PreviewCameraZoom : graph.CameraState.MainCameraZoom;

            float aspectRatio = graphWidgetSize.X / graphWidgetSize.Y;

            Matrix4x4 translation = isPreview ? graph.CameraState.PreviewCameraTranslation : graph.CameraState.MainCameraTranslation;
            Matrix4x4 projection  = Matrix4x4.CreatePerspectiveFieldOfView(1.0f, aspectRatio, 1, 80000);
            Matrix4x4 worldView   = Matrix4x4.CreateFromAxisAngle(Vector3.UnitY, 0) * translation;

            Vector2 xlimits = new Vector2(float.MaxValue, float.MinValue);
            Vector2 ylimits = new Vector2(float.MaxValue, float.MinValue);
            Vector2 zlimits = new Vector2(float.MaxValue, float.MinValue);
            Vector2 ev = new Vector2(0, 0);
            Vector2 xmin = ev, xmax = ev, ymin = ev, ymax = ev;

            float[] positions = graph.LayoutState.DownloadVRAMPositions();

            bool result;

            if (positions.Length < 4)
            {
                result = false;
            }
            else
            {
                result = true;
                for (int idx = 0; idx < positions.Length; idx += 4)
                {
                    float guard = positions[idx + 3];
                    if (guard is not 1)
                    {
                        break;
                    }

                    float x = positions[idx];
                    float y = positions[idx + 1];
                    float z = positions[idx + 2];

                    Vector3 worldpos = new Vector3(x, y, z);

                    Vector2 ndcPos = GraphicsMaths.WorldToNDCPos(worldpos, worldView, projection);
                    if (ndcPos.X < xlimits.X)
                    {
                        xlimits = new Vector2(ndcPos.X, xlimits.Y); xmin = ndcPos;
                    }
                    if (ndcPos.X > xlimits.Y)
                    {
                        xlimits = new Vector2(xlimits.X, ndcPos.X); xmax = ndcPos;
                    }
                    if (ndcPos.Y < ylimits.X)
                    {
                        ylimits = new Vector2(ndcPos.Y, ylimits.Y); ymin = ndcPos;
                    }
                    if (ndcPos.Y > ylimits.Y)
                    {
                        ylimits = new Vector2(ylimits.X, ndcPos.Y); ymax = ndcPos;
                    }
                    if (worldpos.Z < zlimits.X)
                    {
                        zlimits = new Vector2(worldpos.Z, zlimits.Y);
                    }
                    if (worldpos.Z > zlimits.Y)
                    {
                        zlimits = new Vector2(zlimits.X, worldpos.Z);
                    }
                }

                Vector2 minxS = GraphicsMaths.NdcToScreenPos(xmin, graphWidgetSize);
                Vector2 maxxS = GraphicsMaths.NdcToScreenPos(xmax, graphWidgetSize);
                Vector2 minyS = GraphicsMaths.NdcToScreenPos(ymin, graphWidgetSize);
                Vector2 maxyS = GraphicsMaths.NdcToScreenPos(ymax, graphWidgetSize);

                xoffsets = new Vector2(minxS.X, graphWidgetSize.X - maxxS.X);
                yoffsets = new Vector2(minyS.Y, graphWidgetSize.Y - maxyS.Y);
                zoffsets = new Vector2(zlimits.X - zoom, zlimits.Y - zoom);
            }


            //Sometimes the position buffer is full of terrible data.
            //Seems to just be for the preview graph? Only happens at the start so must have gotten hold of uninitialised data
            if (zoffsets.X > 100000000000 || zoffsets.X < -100000000000)
            {
                if (isPreview)
                {
                    graph.CameraState.PreviewCameraZoom = -60000;
                }
                else
                {
                    graph.CameraState.MainCameraZoom = -60000;
                }
                return(false);
            }
            if (GlobalConfig.Settings.Logs.BulkLogging)
            {
                Logging.RecordLogEvent($"GetWidgetFitOffsets exit", Logging.LogFilterType.BulkDebugLogFile);
            }
            return(result);
        }
Esempio n. 26
0
        private void DrawGraphTooltip(PlottedGraph plot)
        {
            ImGui.SetNextWindowPos(ImGui.GetMousePos() + new Vector2(0, 20));

            ImGui.BeginTooltip();
            string runningState;

            //todo a 'blocked' option when i get around to detecting/displaying the blocked state
            if (plot.InternalProtoGraph.TraceData.TraceState == TraceRecord.ProcessState.eSuspended)
            {
                runningState = "Suspended";
            }
            else
            {
                if (plot.InternalProtoGraph.Terminated)
                {
                    runningState = "Terminated";
                }
                else
                {
                    runningState = "Running";
                }
            }

            if (_threadStartCache.ContainsKey(plot))
            {
                ImGui.Text(_threadStartCache[plot]);
            }
            else
            {
                if (plot.InternalProtoGraph.NodeCount > 0)
                {
                    ulong blockaddr = plot.InternalProtoGraph.NodeList[0].Address;
                    bool  found     = plot.InternalProtoGraph.ProcessData.FindContainingModule(blockaddr, out int?module);
                    if (found)
                    {
                        string path     = plot.InternalProtoGraph.ProcessData.GetModulePath(module !.Value);
                        string pathSnip = Path.GetFileName(path);
                        if (pathSnip.Length > 50)
                        {
                            pathSnip = pathSnip.Substring(pathSnip.Length - 50, pathSnip.Length);
                        }

                        string val = $"Start Address: {pathSnip}:0x{blockaddr:X}";
                        _threadStartCache[plot] = val;
                        ImGui.Text(val);
                    }
                    else
                    {
                        ImGui.Text("[No Module?]");
                    }
                }
            }

            ImGui.Text($"Graph TID: {plot.TID} [{runningState}]");
            ImGui.Text($"Graph PID: {plot.PID}");
            ImGui.Text($"Unique Instructions: {plot.InternalProtoGraph.NodeList.Count}");
            ImGui.Text($"Total Instructions: {plot.InternalProtoGraph.TotalInstructions}");
            ImGui.Text($"Animation Entries: {plot.InternalProtoGraph.UpdateCount}");
            ImGui.Text($"Exceptions: {plot.InternalProtoGraph.ExceptionCount}");


            ImGui.Separator();
            ImGui.PushStyleColor(ImGuiCol.Text, 0xffeeeeff);
            string ctxtiptext = "Right click for options";

            ImGui.SetCursorPosX((ImGui.GetContentRegionAvail().X / 2) - ImGui.CalcTextSize(ctxtiptext).X / 2);
            ImGui.Text(ctxtiptext);
            ImGui.PopStyleColor();
            ImGui.EndTooltip();
        }