mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
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()
1189 lines
36 KiB
Go
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()
|
|
}
|
|
}
|