Gopher2600/gui/sdlimgui/win_coproc_profiling.go
JetSetIlly 9a99ff7a3e fixed searching for PAL60 in filename and PAL60 sizing
added PAL60 specification. even though it isn't a real specification it
is more convenient and provides better user feedback

moved FrameInfo into frameinfo package, now called Current as in
frameinfo.Current

clarified storage of requested specification by the television:

how the television is probed for the specification has changed, in
particular the current spec is retreived via the GetFrameInfo()
function. in fact, this was how most other packages did it but there
also existed a GetSpecID() which was uncessary

GetReqSpecID() and GetCreationSpecID() removed, replaced with
GetResetSpecID() and IsAutoSpec()

simplified SetSpec(). removed the force argument

removed reset option for vcs.AttachCartridge()
2025-02-23 17:39:08 +00:00

1189 lines
36 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package sdlimgui
import (
"fmt"
"sync/atomic"
"github.com/inkyblackness/imgui-go/v4"
"github.com/jetsetilly/gopher2600/coprocessor"
"github.com/jetsetilly/gopher2600/coprocessor/developer/dwarf"
"github.com/jetsetilly/gopher2600/coprocessor/developer/profiling"
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/gui/fonts"
"github.com/jetsetilly/gopher2600/hardware/television/frameinfo"
"github.com/jetsetilly/gopher2600/hardware/television/specification"
)
const winCoProcProfilingID = "Coprocessor Profiling"
const winCoProcProfilingMenu = "Profiling"
type winCoProcProfiling struct {
debuggerWin
img *SdlImgui
// which category of execution to focus on
focus profiling.Focus
focusComboDim imgui.Vec2
// whether to present performance figures as raw counts or as percentages
percentileFigures bool
// for the function tab, show cumulative values rather than flat values
cumulative bool
// show the number of times the function has been called in the current frame
cyclesPerCall bool
// whether the sort criteria as specified in the window has changed (ie.
// focus has changed)
windowSortSpecDirty bool
// the currently selected tab. used to dirty the sort flag when a new tab
// is selected
tabSelected string
// whether to included unexecuted entries (functions or lines) in the list
hideUnusedEntries bool
// scale load statistics in function filters to the function level (as
// opposed to the program level)
functionTabScale bool
// height of all options
optionsHeight float32
// function tab is newly opened/changed. this means that the stats should
// be resorted
functionTabSelect string
// FrameTrigger interface used to synchronise reset event with the TV
tv coProcProfilingTV
}
// coProceProfilingTV is used to synchronise the "Reset Statistics" button with the television.
type coProcProfilingTV struct {
img *SdlImgui
schedule atomic.Value
}
func (tv *coProcProfilingTV) initialise(img *SdlImgui) {
// initialise TV Frame Trigger
tv.img = img
tv.schedule.Store(false)
tv.img.dbg.PushFunction(func() {
tv.img.dbg.TV().AddFrameTrigger(tv)
})
}
func (tv *coProcProfilingTV) scheduleReset(set bool) {
tv.schedule.Store(set)
}
// NewFrame implements the television.FrameTrigger interface.
func (tv *coProcProfilingTV) NewFrame(_ frameinfo.Current) error {
// this code is running in the emulator goroutine and NOT the GUI goroutine
if tv.schedule.Load().(bool) {
tv.schedule.Store(false)
tv.img.dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
src.ResetProfiling()
})
}
return nil
}
func newWinCoProcProfiling(img *SdlImgui) (window, error) {
win := &winCoProcProfiling{
img: img,
focus: profiling.FocusAll,
percentileFigures: true,
}
win.tv.initialise(img)
return win, nil
}
func (win *winCoProcProfiling) init() {
win.focusComboDim = imguiGetFrameDim("", profiling.FocusOptions...)
}
func (win *winCoProcProfiling) id() string {
return winCoProcProfilingID
}
func (win *winCoProcProfiling) debuggerDraw() bool {
if !win.debuggerOpen {
return false
}
// do not open window if there is no coprocessor available
coproc := win.img.cache.VCS.Mem.Cart.GetCoProc()
if coproc == nil {
return false
}
imgui.SetNextWindowPosV(imgui.Vec2{X: 858, Y: 319}, imgui.ConditionFirstUseEver, imgui.Vec2{X: 0, Y: 0})
imgui.SetNextWindowSizeV(imgui.Vec2{X: 641, Y: 517}, imgui.ConditionFirstUseEver)
win.img.setReasonableWindowConstraints()
title := fmt.Sprintf("%s %s", coproc.ProcessorID(), winCoProcProfilingID)
if imgui.BeginV(win.debuggerID(title), &win.debuggerOpen, imgui.WindowFlagsNone) {
win.draw(coproc)
}
win.debuggerGeom.update()
imgui.End()
return true
}
func (win *winCoProcProfiling) draw(coproc coprocessor.CartCoProc) {
// safely iterate over top execution information
win.img.dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
if src == nil {
imgui.Text("No source files available")
return
}
if len(src.Filenames) == 0 {
imgui.Text("No source files available")
return
}
// ExecutionProfileChanged is used to decide whether to sort
// statistics. make sure we set it to false by the end of the draw()
// function
if src.ProfilingDirty {
defer func() {
src.ProfilingDirty = false
}()
}
imgui.BeginChildV("##coprocProfilingMain", imgui.Vec2{X: 0, Y: imguiRemainingWinHeight() - win.optionsHeight}, false, 0)
imgui.BeginTabBarV("##coprocProfilingFunctions", imgui.TabBarFlagsAutoSelectNewTabs)
functionTab := "Functions"
if imgui.BeginTabItemV(functionTab, nil, imgui.TabItemFlagsNone) {
if win.tabSelected != functionTab {
win.tabSelected = functionTab
win.windowSortSpecDirty = true
}
win.drawFunctions(src)
imgui.EndTabItem()
}
sourceLineTab := "Source Lines"
if imgui.BeginTabItemV(sourceLineTab, nil, imgui.TabItemFlagsNone) {
if win.tabSelected != sourceLineTab {
win.tabSelected = sourceLineTab
win.windowSortSpecDirty = true
}
win.drawSourceLines(src)
imgui.EndTabItem()
}
for _, ff := range src.FunctionFilters {
flgs := imgui.TabItemFlagsNone
open := true
if ff.FunctionName == win.functionTabSelect {
flgs |= imgui.TabItemFlagsSetSelected
}
funcFilterTab := fmt.Sprintf("%c %s", fonts.MagnifyingGlass, ff.FunctionName)
if imgui.BeginTabItemV(funcFilterTab, &open, flgs) {
if win.tabSelected != funcFilterTab {
win.tabSelected = funcFilterTab
win.windowSortSpecDirty = true
}
win.drawFunctionFilter(src, ff)
imgui.EndTabItem()
}
if !open {
src.DropFunctionFilter(ff.FunctionName)
}
}
win.functionTabSelect = ""
imgui.EndTabBar()
imgui.EndChild()
// options toolbar at foot of window
win.optionsHeight = imguiMeasureHeight(func() {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
imguiLabel("Focus")
imgui.PushItemWidth(win.focusComboDim.X + imgui.FrameHeight())
if imgui.BeginCombo("##focus", win.focus.String()) {
if imgui.Selectable(profiling.FocusAll.String()) {
win.focus = profiling.FocusAll
win.windowSortSpecDirty = true
}
if imgui.Selectable(profiling.FocusVBLANK.String()) {
win.focus = profiling.FocusVBLANK
win.windowSortSpecDirty = true
}
if imgui.Selectable(profiling.FocusScreen.String()) {
win.focus = profiling.FocusScreen
win.windowSortSpecDirty = true
}
if imgui.Selectable(profiling.FocusOverscan.String()) {
win.focus = profiling.FocusOverscan
win.windowSortSpecDirty = true
}
imgui.EndCombo()
}
// reset statistics
imgui.SameLineV(0, 15)
if win.img.dbg.State() == govern.Paused {
if win.tv.schedule.Load().(bool) {
if imgui.Button(fmt.Sprintf("%c Reset Now", fonts.Trash)) {
win.tv.scheduleReset(false)
src.ResetProfiling()
}
imgui.SameLineV(0, 15)
imgui.BeginGroup()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.Cancel)
imgui.Text(string(fonts.Cancel))
imgui.PopStyleColor()
imgui.SameLineV(0, 5)
imgui.Text("Statistics will be reset for the next TV frame")
imgui.EndGroup()
if imgui.IsItemClicked() {
win.tv.scheduleReset(false)
}
} else {
if imgui.Button(fmt.Sprintf("%c Reset Statistics", fonts.Trash)) {
win.tv.scheduleReset(true)
}
}
} else {
if imgui.Button(fmt.Sprintf("%c Reset Statistics", fonts.Trash)) {
win.tv.scheduleReset(true)
}
}
imgui.Spacing()
imgui.Checkbox("Hide Unexecuted Items", &win.hideUnusedEntries)
if win.tabSelected == functionTab {
imgui.SameLineV(0, 15)
if imgui.Checkbox("Percentile Figures", &win.percentileFigures) {
win.windowSortSpecDirty = true
}
drawDisabled(win.cyclesPerCall, func() {
imgui.SameLineV(0, 15)
if imgui.Checkbox("Cumulative Figures", &win.cumulative) {
win.windowSortSpecDirty = true
}
})
imgui.SameLineV(0, 15)
if imgui.Checkbox("Cycles/Call", &win.cyclesPerCall) {
win.windowSortSpecDirty = true
}
}
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
if src.Optimised {
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.Warning)
imgui.Text(string(fonts.Warning))
imgui.PopStyleColor()
win.img.imguiTooltipSimple(`Source compiled with optimisation. Some figures may be misleading`)
imgui.SameLineV(0, 15)
}
win.drawFrameStats(coproc)
})
})
}
func (win *winCoProcProfiling) drawFrameStats(coproc coprocessor.CartCoProc) {
accumulate := func(s coprocessor.CoProcSynchronisation) int {
switch s {
case coprocessor.CoProcIdle:
case coprocessor.CoProcNOPFeed:
return 1
case coprocessor.CoProcStrongARMFeed:
case coprocessor.CoProcParallel:
return 1
}
return 0
}
win.img.screen.crit.section.Lock()
defer win.img.screen.crit.section.Unlock()
// decide what to focus on
var focus string
var focusClocks float32
switch win.focus {
case profiling.FocusAll:
focusClocks = float32(win.img.screen.crit.frameInfo.TotalClocks())
focus = "TV Frame"
case profiling.FocusScreen:
focusClocks = float32(win.img.screen.crit.frameInfo.ScreenClocks())
focus = "Screen"
case profiling.FocusVBLANK:
focusClocks = float32(win.img.screen.crit.frameInfo.VBLANKClocks())
focus = "VBLANK"
case profiling.FocusOverscan:
focusClocks = float32(win.img.screen.crit.frameInfo.OverscanClocks())
focus = "Overscan"
}
// frame statistics are taken from reflection information
var clockCount float32
for i, r := range win.img.screen.crit.reflection {
sl := i / specification.ClksScanline
switch win.focus {
case profiling.FocusAll:
clockCount += float32(accumulate(r.CoProcSync))
case profiling.FocusScreen:
if sl >= win.img.screen.crit.frameInfo.VisibleTop && sl <= win.img.screen.crit.frameInfo.VisibleBottom {
clockCount += float32(accumulate(r.CoProcSync))
}
case profiling.FocusVBLANK:
if sl < win.img.screen.crit.frameInfo.VisibleTop {
clockCount += float32(accumulate(r.CoProcSync))
}
case profiling.FocusOverscan:
if sl > win.img.screen.crit.frameInfo.VisibleBottom {
clockCount += float32(accumulate(r.CoProcSync))
}
}
}
if clockCount > 0 {
imgui.Text(fmt.Sprintf("%s activity in most recent %s:", coproc.ProcessorID(), focus))
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLoad)
imgui.Text(fmt.Sprintf("%.02f%%", clockCount/focusClocks*100))
imgui.PopStyleColor()
} else if win.focus == profiling.FocusAll {
imgui.Text(fmt.Sprintf("No %s activity in the most recent frame", coproc.ProcessorID()))
} else {
imgui.Text(fmt.Sprintf("No %s activity during %s", coproc.ProcessorID(), focus))
}
}
func (win *winCoProcProfiling) drawFunctions(src *dwarf.Source) {
imgui.Spacing()
if src == nil || len(src.SortedFunctions.Functions) == 0 {
imgui.Text("No performance profile")
return
}
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
if !src.Cycles.VBLANK.HasExecuted() {
imgui.Text("No functions have been executed during VBLANK yet")
return
}
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
if !src.Cycles.Screen.HasExecuted() {
imgui.Text("No functions have been executed during the visible screen yet")
return
}
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
if !src.Cycles.Overscan.HasExecuted() {
imgui.Text("No functions have been executed during Overscan yet")
return
}
} else {
if win.hideUnusedEntries && !src.Cycles.Overall.HasExecuted() {
imgui.Text("No functions have been executed yet")
return
}
}
const numColumns = 7
flgs := imgui.TableFlagsScrollY
flgs |= imgui.TableFlagsSizingStretchProp
flgs |= imgui.TableFlagsSortable
flgs |= imgui.TableFlagsNoHostExtendX
flgs |= imgui.TableFlagsResizable
flgs |= imgui.TableFlagsHideable
// the layout of the table changes significantly if the numberOfCalls option
// is set. it is good to indicate that this is a different table in this
// case because it means the sort column and column sizing will change along
// with the option
title := "##coprocProfilingTableFunctions"
imgui.BeginTableV(title, numColumns, flgs, imgui.Vec2{}, 0.0)
// first column is a dummy column so that Selectable (span all columns) works correctly
width := imgui.ContentRegionAvail().X
imgui.TableSetupColumnV("File", imgui.TableColumnFlagsPreferSortDescending, width*0.275, 0)
imgui.TableSetupColumnV("Line", imgui.TableColumnFlagsNoSort, width*0.05, 1)
imgui.TableSetupColumnV("Function", imgui.TableColumnFlagsPreferSortDescending, width*0.320, 2)
imgui.TableSetupColumnV("Frame", imgui.TableColumnFlagsNoSortAscending|imgui.TableColumnFlagsDefaultSort, width*0.1, 3)
imgui.TableSetupColumnV("Avg", imgui.TableColumnFlagsNoSortAscending, width*0.1, 4)
imgui.TableSetupColumnV("Max", imgui.TableColumnFlagsNoSortAscending, width*0.1, 5)
imgui.TableSetupColumnV(string(fonts.CoProcKernel), imgui.TableColumnFlagsNoSort, width*0.05, 6)
imgui.TableSetupScrollFreeze(0, 1)
imgui.TableHeadersRow()
win.sort(src, func(sort imgui.TableSortSpecs) {
for _, s := range sort.Specs() {
switch s.ColumnUserID {
case 0:
src.SortedFunctions.Sort(dwarf.SortFunctionsFile, false,
false, s.SortDirection == imgui.SortDirectionAscending, win.focus)
case 2:
src.SortedFunctions.Sort(dwarf.SortFunctionsName, win.cumulative,
false, s.SortDirection == imgui.SortDirectionAscending, win.focus)
case 3:
if win.cyclesPerCall {
src.SortedFunctions.Sort(dwarf.SortFunctionsFrameCyclesPerCall, false,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
} else {
src.SortedFunctions.Sort(dwarf.SortFunctionsFrameCycles, win.cumulative,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
}
case 4:
if win.cyclesPerCall {
src.SortedFunctions.Sort(dwarf.SortFunctionsAverageCyclesPerCall, false,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
} else {
src.SortedFunctions.Sort(dwarf.SortFunctionsAverageCycles, win.cumulative,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
}
case 5:
if win.cyclesPerCall {
src.SortedFunctions.Sort(dwarf.SortFunctionsMaxCyclesPerCall, false,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
} else {
src.SortedFunctions.Sort(dwarf.SortFunctionsMaxCycles, win.cumulative,
win.percentileFigures, s.SortDirection != imgui.SortDirectionAscending, win.focus)
}
}
}
sort.ClearSpecsDirty()
})
for _, fn := range src.SortedFunctions.Functions {
if fn.Kernel&win.focus != win.focus {
continue
}
// using flat stats even if cumulative option is set - it amounts to
// the same thing in this case
if win.hideUnusedEntries && !fn.Cycles.Overall.HasExecuted() {
continue
}
// is the function is a stub or not. some facilities are not available
// without source
isStub := fn.IsStub()
// select which stats to focus on
var cyclesGroup profiling.Cycles
if win.cumulative {
cyclesGroup = fn.CumulativeCycles
} else {
cyclesGroup = fn.Cycles
}
var cycles profiling.CyclesScope
var calls profiling.CallsScope
var cyclesPerCall profiling.CyclesPerCallScope
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
cycles = cyclesGroup.VBLANK
calls = fn.NumCalls.VBLANK
cyclesPerCall = fn.CyclesPerCall.VBLANK
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
cycles = cyclesGroup.Screen
calls = fn.NumCalls.Screen
cyclesPerCall = fn.CyclesPerCall.Screen
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
cycles = cyclesGroup.Overscan
calls = fn.NumCalls.Overscan
cyclesPerCall = fn.CyclesPerCall.Overscan
} else {
cycles = cyclesGroup.Overall
calls = fn.NumCalls.Overall
cyclesPerCall = fn.CyclesPerCall.Overall
}
imgui.TableNextRow()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorHeaderHovered, win.img.cols.CoProcSourceHoverLine)
imgui.PushStyleColor(imgui.StyleColorHeaderActive, win.img.cols.CoProcSourceHoverLine)
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceFilename)
if isStub {
imgui.SelectableV("-", false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{X: 0, Y: 0})
} else {
imgui.SelectableV(fn.DeclLine.File.ShortFilename, false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{X: 0, Y: 0})
}
imgui.PopStyleColorV(3)
// whether to show the optimisation warning for a function
optimisedWarning := win.cumulative && fn.OptimisedCallStack
// tooltip
win.tooltip(fn.Cycles.Overall.CyclesProgram, fn, fn.DeclLine, false)
if optimisedWarning {
win.img.imguiTooltip(func() {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.Warning)
imgui.Text(string(fonts.Warning))
imgui.PopStyleColor()
imgui.SameLineV(0, 5)
imgui.Text("This function has been called as part of a call stack that could")
imgui.Text("not be discerned accurately. The figures will be inaccurate.")
}, true)
}
// open/select function filter on click
if imgui.IsItemClicked() && !isStub {
win.windowSortSpecDirty = true
src.AddFunctionFilter(fn.Name)
win.functionTabSelect = fn.Name
}
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLineNumber)
if isStub {
imgui.Text("-")
} else {
imgui.Text(fmt.Sprintf("%d", fn.DeclLine.LineNumber))
}
imgui.PopStyleColor()
imgui.TableNextColumn()
if optimisedWarning {
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.Warning)
imgui.Text(string(fonts.Warning))
imgui.PopStyleColor()
imgui.SameLineV(0, 5)
imgui.Text(fn.Name)
} else {
imgui.Text(fn.Name)
}
_ = calls
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLoad)
if win.cyclesPerCall {
if cyclesPerCall.FrameValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", cyclesPerCall.FrameLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", cyclesPerCall.FrameCount))
}
} else {
imgui.Text("-")
}
} else {
if cycles.CyclesProgram.FrameValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", cycles.CyclesProgram.FrameLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", cycles.CyclesProgram.FrameCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceAvgLoad)
if win.cyclesPerCall {
if cyclesPerCall.AverageValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%0.02f", cyclesPerCall.AverageLoad))
} else {
imgui.Text(fmt.Sprintf("%0.0f", cyclesPerCall.AverageCount))
}
} else {
imgui.Text("-")
}
} else {
if cycles.CyclesProgram.AverageValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", cycles.CyclesProgram.AverageLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", cycles.CyclesProgram.AverageCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceMaxLoad)
if win.cyclesPerCall {
if cyclesPerCall.MaxValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", cyclesPerCall.MaxLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", cyclesPerCall.MaxCount))
}
} else {
imgui.Text("-")
}
} else {
if cycles.CyclesProgram.MaxValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", cycles.CyclesProgram.MaxLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", cycles.CyclesProgram.MaxCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
if fn.Kernel&profiling.FocusVBLANK == profiling.FocusVBLANK {
imgui.Text("V")
imgui.SameLineV(0, 1)
}
if fn.Kernel&profiling.FocusScreen == profiling.FocusScreen {
imgui.Text("S")
imgui.SameLineV(0, 1)
}
if fn.Kernel&profiling.FocusOverscan == profiling.FocusOverscan {
imgui.Text("O")
imgui.SameLineV(0, 1)
}
}
imgui.EndTable()
}
func (win *winCoProcProfiling) drawSourceLines(src *dwarf.Source) {
imgui.Spacing()
if src == nil || len(src.SortedLines.Lines) == 0 {
imgui.Text("No performance profile")
return
}
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
if !src.Cycles.VBLANK.HasExecuted() {
imgui.Text("No lines have been executed during VBLANK yet")
return
}
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
if !src.Cycles.Screen.HasExecuted() {
imgui.Text("No lines have been executed during the visible screen yet")
return
}
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
if !src.Cycles.Overscan.HasExecuted() {
imgui.Text("No lines have been executed during Overscan yet")
return
}
} else {
if win.hideUnusedEntries && !src.Cycles.Overall.HasExecuted() {
imgui.Text("No lines have been executed yet")
return
}
}
const numColumns = 7
flgs := imgui.TableFlagsScrollY
flgs |= imgui.TableFlagsSizingStretchProp
flgs |= imgui.TableFlagsSortable
flgs |= imgui.TableFlagsResizable
flgs |= imgui.TableFlagsHideable
imgui.BeginTableV("##coprocProfilingTableSourceLines", numColumns, flgs, imgui.Vec2{}, 0.0)
// first column is a dummy column so that Selectable (span all columns) works correctly
width := imgui.ContentRegionAvail().X
imgui.TableSetupColumnV("Function", imgui.TableColumnFlagsPreferSortDescending, width*0.20, 0)
imgui.TableSetupColumnV("Line", imgui.TableColumnFlagsNoSort, width*0.05, 1)
imgui.TableSetupColumnV("Content", imgui.TableColumnFlagsNoSort, width*0.30, 2)
imgui.TableSetupColumnV("Frame", imgui.TableColumnFlagsNoSortAscending|imgui.TableColumnFlagsDefaultSort, width*0.07, 3)
imgui.TableSetupColumnV("Avg", imgui.TableColumnFlagsNoSortAscending, width*0.07, 4)
imgui.TableSetupColumnV("Max", imgui.TableColumnFlagsNoSortAscending, width*0.07, 5)
imgui.TableSetupColumnV(string(fonts.CoProcKernel), imgui.TableColumnFlagsNoSort, width*0.05, 6)
imgui.TableSetupScrollFreeze(0, 1)
imgui.TableHeadersRow()
win.sort(src, func(sort imgui.TableSortSpecs) {
for _, s := range sort.Specs() {
switch s.ColumnUserID {
case 0:
src.SortedLines.Sort(dwarf.SortLinesFunction, true,
false, s.SortDirection == imgui.SortDirectionAscending, win.focus)
case 3:
src.SortedLines.Sort(dwarf.SortLinesFrameCycles, true,
win.percentileFigures, true, win.focus)
case 4:
src.SortedLines.Sort(dwarf.SortLinesAverageCycles, true,
win.percentileFigures, true, win.focus)
case 5:
src.SortedLines.Sort(dwarf.SortLinesMaxCycles, true,
win.percentileFigures, true, win.focus)
}
}
sort.ClearSpecsDirty()
})
for _, ln := range src.SortedLines.Lines {
if ln.Kernel&win.focus != win.focus {
continue
}
if win.hideUnusedEntries && !ln.Cycles.Overall.HasExecuted() {
continue
}
// is the line is a stub or not. some facilities are not available
// without source
isStub := ln.IsStub()
// select which stats to focus on
var stats profiling.CyclesScope
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
stats = ln.Cycles.VBLANK
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
stats = ln.Cycles.Screen
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
stats = ln.Cycles.Overscan
} else {
stats = ln.Cycles.Overall
}
imgui.TableNextRow()
imgui.TableNextColumn()
// selectable across entire width of table
imgui.PushStyleColor(imgui.StyleColorHeaderHovered, win.img.cols.CoProcSourceHoverLine)
imgui.PushStyleColor(imgui.StyleColorHeaderActive, win.img.cols.CoProcSourceHoverLine)
imgui.SelectableV(ln.Function.Name, false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{X: 0, Y: 0})
imgui.PopStyleColorV(2)
// tooltip
if isStub {
win.img.imguiTooltipSimple(fmt.Sprintf("This entry represent all lines of code in %s", ln.Function.Name))
} else {
win.tooltip(ln.Cycles.Overall.CyclesProgram, ln.Function, ln, true)
}
// open source window on click
if !isStub && imgui.IsItemClicked() {
srcWin := win.img.wm.debuggerWindows[winCoProcSourceID].(*winCoProcSource)
srcWin.gotoSourceLine(ln)
}
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLineNumber)
if isStub {
imgui.Text("-")
} else {
imgui.Text(fmt.Sprintf("%d", ln.LineNumber))
}
imgui.PopStyleColor()
imgui.TableNextColumn()
win.img.drawSourceLine(ln, true)
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLoad)
if stats.CyclesProgram.FrameValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.FrameLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.FrameCount))
}
} else {
imgui.Text("-")
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceAvgLoad)
if stats.CyclesProgram.AverageValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.AverageLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.AverageCount))
}
} else {
imgui.Text("-")
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceMaxLoad)
if stats.CyclesProgram.MaxValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.MaxLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.MaxCount))
}
} else {
imgui.Text("-")
}
imgui.PopStyleColor()
imgui.TableNextColumn()
if ln.Kernel&profiling.FocusVBLANK == profiling.FocusVBLANK {
imgui.Text("V")
imgui.SameLineV(0, 1)
}
if ln.Kernel&profiling.FocusScreen == profiling.FocusScreen {
imgui.Text("S")
imgui.SameLineV(0, 1)
}
if ln.Kernel&profiling.FocusOverscan == profiling.FocusOverscan {
imgui.Text("O")
imgui.SameLineV(0, 1)
}
}
imgui.EndTable()
}
func (win *winCoProcProfiling) drawFunctionFilter(src *dwarf.Source, functionFilter *dwarf.FunctionFilter) {
imgui.Spacing()
if len(functionFilter.Lines.Lines) == 0 {
imgui.Text(fmt.Sprintf("%s contains no executable lines", functionFilter.FunctionName))
return
}
// validity check is done on function filter stats - drawFunctions() and
// drawSourceLines() equivalents of this block perform the validity check
// on the source level statistics
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
if !functionFilter.Function.Cycles.VBLANK.HasExecuted() {
imgui.Text("This function has not been executed during VBLANK yet")
return
}
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
if !functionFilter.Function.Cycles.Screen.HasExecuted() {
imgui.Text("This function has not been executed during the visible screen yet")
return
}
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
if !functionFilter.Function.Cycles.Overscan.HasExecuted() {
imgui.Text("This function has not been executed during Overscan yet")
return
}
} else {
if win.hideUnusedEntries && !src.Cycles.Overall.HasExecuted() {
imgui.Text("Ths function has not been executed yet")
return
}
}
// function summary in relation to the program
imgui.AlignTextToFramePadding()
switch win.focus {
case profiling.FocusVBLANK:
imgui.Text(fmt.Sprintf("Function accounted for %.02f%% of ARM time in the VBLANK last frame", functionFilter.Function.Cycles.VBLANK.CyclesProgram.FrameLoad))
case profiling.FocusScreen:
imgui.Text(fmt.Sprintf("Function accounted for %.02f%% of ARM time in the Visible Screen last frame", functionFilter.Function.Cycles.Screen.CyclesProgram.FrameLoad))
case profiling.FocusOverscan:
imgui.Text(fmt.Sprintf("Function accounted for %.02f%% of ARM time in the Overscan last frame", functionFilter.Function.Cycles.Overscan.CyclesProgram.FrameLoad))
default:
imgui.Text(fmt.Sprintf("Function accounted for %.02f%% of ARM time last frame", functionFilter.Function.Cycles.Overall.CyclesProgram.FrameLoad))
}
imgui.SameLineV(0, 15)
if imgui.Checkbox("Scale Statistics", &win.functionTabScale) {
win.windowSortSpecDirty = true
}
win.img.imguiTooltipSimple(`When selected the % values in the table are
scaled so they are relative to the function rather
thean to the program as a whole.`)
imgui.Spacing()
// table of function lines
const numColumns = 6
flgs := imgui.TableFlagsScrollY
flgs |= imgui.TableFlagsSizingStretchProp
flgs |= imgui.TableFlagsSortable
flgs |= imgui.TableFlagsNoHostExtendX
flgs |= imgui.TableFlagsResizable
flgs |= imgui.TableFlagsHideable
imgui.BeginTableV("##coprocProfilingTableFunctionFilter", numColumns, flgs, imgui.Vec2{}, 0.0)
// first column is a dummy column so that Selectable (span all columns) works correctly
width := imgui.ContentRegionAvail().X
imgui.TableSetupColumnV("Line", imgui.TableColumnFlagsPreferSortDescending, width*0.05, 0)
imgui.TableSetupColumnV("Source", imgui.TableColumnFlagsNoSort, width*0.55, 1)
imgui.TableSetupColumnV("Frame", imgui.TableColumnFlagsNoSortAscending|imgui.TableColumnFlagsDefaultSort, width*0.1, 2)
imgui.TableSetupColumnV("Avg", imgui.TableColumnFlagsNoSortAscending, width*0.1, 3)
imgui.TableSetupColumnV("Max", imgui.TableColumnFlagsNoSortAscending, width*0.1, 4)
imgui.TableSetupColumnV(string(fonts.CoProcKernel), imgui.TableColumnFlagsNoSort, width*0.05, 6)
imgui.TableSetupScrollFreeze(0, 1)
imgui.TableHeadersRow()
win.sort(src, func(sort imgui.TableSortSpecs) {
for _, s := range sort.Specs() {
switch s.ColumnUserID {
case 0:
functionFilter.Lines.Sort(dwarf.SortLinesNumber, !win.functionTabScale,
win.percentileFigures, s.SortDirection == imgui.SortDirectionAscending, win.focus)
case 2:
functionFilter.Lines.Sort(dwarf.SortLinesFrameCycles, !win.functionTabScale, win.percentileFigures,
true, win.focus)
case 3:
functionFilter.Lines.Sort(dwarf.SortLinesAverageCycles, !win.functionTabScale,
win.percentileFigures, true, win.focus)
case 4:
functionFilter.Lines.Sort(dwarf.SortLinesMaxCycles, !win.functionTabScale,
win.percentileFigures, true, win.focus)
}
}
})
for _, ln := range functionFilter.Lines.Lines {
if ln.Kernel&win.focus != win.focus {
continue
}
if win.hideUnusedEntries && !ln.Cycles.Overall.HasExecuted() {
continue
}
if ln.IsStub() {
continue
}
// select which stats to focus on
var stats profiling.CyclesScope
if win.focus&profiling.FocusVBLANK == profiling.FocusVBLANK {
stats = ln.Cycles.VBLANK
} else if win.focus&profiling.FocusScreen == profiling.FocusScreen {
stats = ln.Cycles.Screen
} else if win.focus&profiling.FocusOverscan == profiling.FocusOverscan {
stats = ln.Cycles.Overscan
} else {
stats = ln.Cycles.Overall
}
imgui.TableNextRow()
imgui.TableNextColumn()
// selectable across entire width of table
imgui.PushStyleColor(imgui.StyleColorHeaderHovered, win.img.cols.CoProcSourceHoverLine)
imgui.PushStyleColor(imgui.StyleColorHeaderActive, win.img.cols.CoProcSourceHoverLine)
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLineNumber)
imgui.SelectableV(fmt.Sprintf("%d", ln.LineNumber), false, imgui.SelectableFlagsSpanAllColumns, imgui.Vec2{X: 0, Y: 0})
imgui.PopStyleColorV(3)
// source on tooltip
if win.functionTabScale {
win.tooltip(ln.Cycles.Overall.CyclesFunction, ln.Function, ln, true)
} else {
win.tooltip(ln.Cycles.Overall.CyclesProgram, ln.Function, ln, true)
}
// open source window on click
if imgui.IsItemClicked() {
srcWin := win.img.wm.debuggerWindows[winCoProcSourceID].(*winCoProcSource)
srcWin.gotoSourceLine(ln)
}
imgui.TableNextColumn()
win.img.drawSourceLine(ln, true)
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceLoad)
if win.functionTabScale {
if stats.CyclesFunction.FrameValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesFunction.FrameLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesFunction.FrameCount))
}
} else {
imgui.Text("-")
}
} else {
if stats.CyclesProgram.FrameValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.FrameLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.FrameCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceAvgLoad)
if win.functionTabScale {
if stats.CyclesFunction.AverageValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesFunction.AverageLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesFunction.AverageCount))
}
} else {
imgui.Text("-")
}
} else {
if stats.CyclesProgram.AverageValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.AverageLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.AverageCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.CoProcSourceMaxLoad)
if win.functionTabScale {
if stats.CyclesFunction.MaxValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesFunction.MaxLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesFunction.MaxCount))
}
} else {
imgui.Text("-")
}
} else {
if stats.CyclesProgram.MaxValid {
if win.percentileFigures {
imgui.Text(fmt.Sprintf("%.02f", stats.CyclesProgram.MaxLoad))
} else {
imgui.Text(fmt.Sprintf("%.0f", stats.CyclesProgram.MaxCount))
}
} else {
imgui.Text("-")
}
}
imgui.PopStyleColor()
imgui.TableNextColumn()
if ln.Kernel&profiling.FocusVBLANK == profiling.FocusVBLANK {
imgui.Text("V")
imgui.SameLineV(0, 1)
}
if ln.Kernel&profiling.FocusScreen == profiling.FocusScreen {
imgui.Text("S")
imgui.SameLineV(0, 1)
}
if ln.Kernel&profiling.FocusOverscan == profiling.FocusOverscan {
imgui.Text("O")
imgui.SameLineV(0, 1)
}
}
imgui.EndTable()
}
// single tooltip for all the different contexts in the performance window
//
// the fn argument should never be nil. the ln argument can be nil if the
// function is a stub function
//
// showDisasm should be false if the context doesn't require disassembly detail
func (win *winCoProcProfiling) tooltip(scope profiling.CycleFigures,
fn *dwarf.SourceFunction, ln *dwarf.SourceLine,
showDisasm bool) {
win.img.imguiTooltip(func() {
if fn.IsStub() {
if fn.Name == dwarf.DriverFunctionName {
imgui.Text("Instructions that are executed")
imgui.Text("outside of the ROM and in the driver")
} else {
imgui.Text("Function has no source code")
}
} else {
win.img.drawFilenameAndLineNumber(ln.File.Filename, ln.LineNumber, -1)
}
if fn.Kernel != profiling.FocusAll {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
imgui.Text(fmt.Sprintf("%c Executed in: ", fonts.CoProcKernel))
if fn.Kernel&profiling.FocusVBLANK == profiling.FocusVBLANK {
imgui.Text(" VBLANK")
}
if fn.Kernel&profiling.FocusScreen == profiling.FocusScreen {
imgui.Text(" Visible Screen")
}
if fn.Kernel&profiling.FocusOverscan == profiling.FocusOverscan {
imgui.Text(" Overscan")
}
}
if showDisasm && len(ln.Instruction) > 0 {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
win.img.drawDisasmForCoProc(ln.Instruction, ln, false, false, 0, shortDisasmWindow)
}
}, true)
}
// helpfunction to sort profiling data according to current spec
func (win *winCoProcProfiling) sort(src *dwarf.Source, f func(imgui.TableSortSpecs)) {
sort := imgui.TableGetSortSpecs()
if src.ProfilingDirty || sort.SpecsDirty() || win.windowSortSpecDirty {
win.windowSortSpecDirty = false
f(sort)
sort.ClearSpecsDirty()
}
}