| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package client |
| |
| import ( |
| "fmt" |
| "strconv" |
| |
| "android.googlesource.com/platform/tools/gpu/_experimental/client/schema" |
| "android.googlesource.com/platform/tools/gpu/atom" |
| "android.googlesource.com/platform/tools/gpu/memory" |
| "android.googlesource.com/platform/tools/gpu/service" |
| "github.com/google/gxui" |
| "github.com/google/gxui/math" |
| ) |
| |
| const kCommandAdapterItemHeight = 18 |
| |
| func createEnumList(t gxui.Theme, appCtx *ApplicationContext, values interface{}, selected gxui.AdapterItem, active bool, onChange func(item gxui.AdapterItem)) gxui.DropDownList { |
| a := gxui.CreateDefaultAdapter() |
| a.SetItems(values) |
| a.SetSizeAsLargest(t) |
| a.SetStyleLabel(func(t gxui.Theme, l gxui.Label) { |
| if active { |
| l.SetColor(CONSTANT_COLOR) |
| } else { |
| l.SetColor(INACTIVE_COLOR) |
| } |
| }) |
| l := t.CreateDropDownList() |
| l.SetAdapter(a) |
| l.SetBubbleOverlay(appCtx.DropDownOverlay()) |
| l.Select(selected) |
| l.OnSelectionChanged(onChange) |
| return l |
| } |
| |
| func createTextbox(t gxui.Theme, appCtx *ApplicationContext, value interface{}, active bool, onChange func(s string) bool) gxui.TextBox { |
| tb := t.CreateTextBox() |
| tb.SetMargin(math.Spacing{}) |
| tb.SetPadding(math.Spacing{}) |
| tb.SetText(fmt.Sprintf("%v", value)) |
| b := &gxui.TextBlock{Runes: []rune(tb.Text())} |
| tb.SetDesiredWidth(tb.Font().Measure(b).W + 4) |
| tb.OnTextChanged(func([]gxui.TextBoxEdit) { |
| text := tb.Text() |
| b := &gxui.TextBlock{Runes: []rune(text)} |
| tb.SetDesiredWidth(tb.Font().Measure(b).W + 4) |
| if onChange(text) { |
| tb.SetTextColor(CONSTANT_COLOR) |
| } else { |
| tb.SetTextColor(gxui.Red) |
| } |
| }) |
| tb.OnLostFocus(func() { |
| text := fmt.Sprintf("%v", value) |
| if text != tb.Text() { |
| tb.SetText(text) |
| } |
| }) |
| if active { |
| tb.SetTextColor(CONSTANT_COLOR) |
| } else { |
| tb.SetTextColor(INACTIVE_COLOR) |
| } |
| return tb |
| } |
| |
| func createAtomControls(t gxui.Theme, appCtx *ApplicationContext, id atom.ID) gxui.Control { |
| a := appCtx.Atoms()[id] |
| active := true |
| |
| ll := t.CreateLinearLayout() |
| ll.SetDirection(gxui.LeftToRight) |
| ll.AddChild(CreateLabel(t, fmt.Sprintf("%.6d ", id), LINE_NUMBER_COLOR, active)) |
| |
| switch { |
| case a.Info.IsCommand: |
| if active { |
| //appCtx.OnTimingInfoUpdated(func() { |
| timeLbl := t.CreateLabel() |
| milliseconds := float64(appCtx.timingPerCommand[uint64(id)]) / 1000000. |
| timeLbl.SetText(fmt.Sprintf("%6.3f ms ", milliseconds)) |
| if milliseconds >= 1. { |
| timeLbl.SetColor(gxui.ColorFromHex(0xFFFC19 + 0xFF<<24)) |
| } |
| if milliseconds >= 5. { |
| timeLbl.SetColor(gxui.ColorFromHex(0xD21212 + 0xFF<<24)) |
| } |
| ll.AddChild(timeLbl) |
| //}) |
| } |
| |
| nameLbl := CreateLabel(t, a.Info.Name, COMMAND_COLOR, active) |
| ll.AddChild(nameLbl) |
| |
| ll.AddChild(CreateLabel(t, "(", CODE_COLOR, active)) |
| for i, p := range a.Info.Parameters { |
| argIdx := i |
| v := a.Arguments[argIdx] |
| if argIdx > 0 { |
| ll.AddChild(CreateLabel(t, ", ", CODE_COLOR, active)) |
| } |
| var c gxui.Control |
| switch p.Type.GetKind() { |
| case service.TypeKindPointer: |
| b := t.CreateButton() |
| b.SetMargin(math.Spacing{}) |
| //b.SetPadding(math.Spacing{}) |
| b.AddChild(CreateLabel(t, fmt.Sprintf("0x%x", v), CONSTANT_COLOR, active)) |
| b.OnClick(func(gxui.MouseEvent) { |
| appCtx.SelectAddress(v.(memory.Pointer)) |
| }) |
| c = b |
| case service.TypeKindEnum: |
| enum := p.Type.(*service.EnumInfo) |
| entries := schema.AllEnumEntries(enum) |
| value := v.(schema.EnumValue) |
| selected := service.EnumEntry{Name: value.String(), Value: value.Value} |
| l := createEnumList(t, appCtx, entries, selected, active, func(item gxui.AdapterItem) { |
| entry := item.(service.EnumEntry) |
| a.Arguments[argIdx] = schema.EnumValue{Type: value.Type, Value: entry.Value} |
| appCtx.ReplaceAtom(a, id) |
| }) |
| c = l |
| case service.TypeKindBool: |
| c = createEnumList(t, appCtx, []bool{false, true}, v, active, func(v gxui.AdapterItem) { |
| a.Arguments[argIdx] = v |
| appCtx.ReplaceAtom(a, id) |
| }) |
| case service.TypeKindS32: |
| c = createTextbox(t, appCtx, v, active, func(s string) bool { |
| if i, err := strconv.ParseInt(s, 0, 32); err == nil { |
| a.Arguments[argIdx] = int32(i) |
| appCtx.ReplaceAtom(a, id) |
| return true |
| } else { |
| return false |
| } |
| }) |
| case service.TypeKindS64: |
| c = createTextbox(t, appCtx, v, active, func(s string) bool { |
| if i, err := strconv.ParseInt(s, 0, 64); err == nil { |
| a.Arguments[argIdx] = int64(i) |
| appCtx.ReplaceAtom(a, id) |
| return true |
| } else { |
| return false |
| } |
| }) |
| case service.TypeKindU32: |
| c = createTextbox(t, appCtx, v, active, func(s string) bool { |
| if i, err := strconv.ParseUint(s, 0, 32); err == nil { |
| a.Arguments[argIdx] = uint32(i) |
| appCtx.ReplaceAtom(a, id) |
| return true |
| } else { |
| return false |
| } |
| }) |
| case service.TypeKindU64: |
| c = createTextbox(t, appCtx, v, active, func(s string) bool { |
| if i, err := strconv.ParseUint(s, 0, 64); err == nil { |
| a.Arguments[argIdx] = uint64(i) |
| appCtx.ReplaceAtom(a, id) |
| return true |
| } else { |
| return false |
| } |
| }) |
| case service.TypeKindF32: |
| c = createTextbox(t, appCtx, v, active, func(s string) bool { |
| if f, err := strconv.ParseFloat(s, 32); err == nil { |
| a.Arguments[argIdx] = float32(f) |
| appCtx.ReplaceAtom(a, id) |
| return true |
| } else { |
| return false |
| } |
| }) |
| default: |
| c = CreateLabel(t, fmt.Sprintf("%v", v), CONSTANT_COLOR, active) |
| } |
| |
| ll.AddChild(c) |
| |
| pName := p.Name |
| appCtx.ToolTipController().AddToolTip(c, 0.7, func(math.Point) gxui.Control { |
| l := t.CreateLabel() |
| l.SetText(pName) |
| return l |
| }) |
| } |
| |
| ll.AddChild(CreateLabel(t, ")", CODE_COLOR, active)) |
| |
| /* TODO: Return value |
| res := ty.ReturnValue() |
| if res != nil { |
| ll.AddChild(CreateLabel(t, fmt.Sprintf(" → %v", res.Value()), CONSTANT_COLOR, active)) |
| } |
| */ |
| } |
| return ll |
| } |
| |
| func createAtomGroupControls(t gxui.Theme, appCtx *ApplicationContext, g atom.Group) gxui.Control { |
| layout := t.CreateLinearLayout() |
| layout.SetDirection(gxui.LeftToRight) |
| |
| img := t.CreateImage() |
| img.SetExplicitSize(math.Size{W: kCommandAdapterItemHeight, H: kCommandAdapterItemHeight}) |
| img.SetAspectMode(gxui.AspectCorrectLetterbox) |
| layout.AddChild(img) |
| |
| atomID := g.Range.Last() |
| atom := appCtx.Atoms()[atomID] |
| if atom.Info.IsDrawCall || atom.Info.IsEndOfFrame { |
| var cancel chan<- struct{} |
| cancelThumbnail := func() { |
| if cancel != nil { |
| close(cancel) |
| cancel = nil |
| } |
| } |
| requestThumbnail := func() { |
| cancelThumbnail() |
| cancel = appCtx.RequestThumbnail(atomID, kFilmStripAdapterItemWidth, kFilmStripAdapterItemHeight, func(tex gxui.Texture) { |
| img.SetTexture(tex) |
| appCtx.ToolTipController().AddToolTip(img, 0.7, func(math.Point) gxui.Control { |
| large := t.CreateImage() |
| large.SetTexture(tex) |
| large.SetAspectMode(gxui.AspectCorrectLetterbox) |
| return large |
| }) |
| }) |
| } |
| |
| var subscription gxui.EventSubscription |
| img.OnAttach(func() { |
| requestThumbnail() |
| subscription = appCtx.OnDeviceSelected(requestThumbnail) |
| }) |
| img.OnDetach(func() { |
| cancelThumbnail() |
| subscription.Unlisten() |
| }) |
| } |
| |
| label := t.CreateLabel() |
| label.SetText(g.Name) |
| layout.AddChild(label) |
| |
| return layout |
| } |
| |
| type cmdNode interface { |
| atomRange() atom.Range |
| } |
| |
| type observationsItem struct { |
| atomID atom.ID |
| index int |
| } |
| |
| func (i observationsItem) atomRange() atom.Range { |
| return atom.Range{Start: i.atomID, End: i.atomID + 1} |
| } |
| |
| type hierarchyItem atom.Range |
| |
| func (i hierarchyItem) atomRange() atom.Range { |
| return atom.Range(i) |
| } |
| |
| type observationsNode struct { |
| appCtx *ApplicationContext |
| atomID atom.ID |
| } |
| |
| func (n observationsNode) Count() int { |
| atom := n.appCtx.Atoms()[n.atomID] |
| return len(atom.Observations.Reads) + len(atom.Observations.Writes) |
| } |
| |
| func (n observationsNode) NodeAt(index int) gxui.TreeNode { |
| return nil |
| } |
| |
| func (n observationsNode) ItemAt(index int) gxui.AdapterItem { |
| return observationsItem{n.atomID, index} |
| } |
| |
| func (n observationsNode) ItemIndex(item gxui.AdapterItem) int { |
| return item.(observationsItem).index |
| } |
| |
| func (n observationsNode) Create(theme gxui.Theme, index int) gxui.Control { |
| atom := n.appCtx.Atoms()[n.atomID] |
| var r memory.Range |
| var c gxui.Color |
| if index < len(atom.Observations.Reads) { |
| r = atom.Observations.Reads[index].Range |
| c = gxui.Green |
| } else { |
| index -= len(atom.Observations.Reads) |
| r = atom.Observations.Writes[index].Range |
| c = gxui.Red |
| } |
| |
| b := theme.CreateButton() |
| b.SetMargin(math.Spacing{}) |
| b.AddChild(CreateLabel(theme, r.String(), c, true)) |
| b.OnClick(func(gxui.MouseEvent) { n.appCtx.SelectAddress(r.Base) }) |
| return b |
| } |
| |
| type hierarchyNode struct { |
| appCtx *ApplicationContext |
| group atom.Group |
| depth uint |
| } |
| |
| func (n hierarchyNode) Count() int { |
| return int(n.group.Count()) |
| } |
| |
| func (n hierarchyNode) NodeAt(index int) gxui.TreeNode { |
| if id, subgroup := n.group.Index(uint64(index)); subgroup != nil { |
| return &hierarchyNode{ |
| appCtx: n.appCtx, |
| group: *subgroup, |
| depth: n.depth + 1, |
| } |
| } else { |
| return observationsNode{n.appCtx, id} |
| } |
| } |
| |
| func (n hierarchyNode) ItemAt(index int) gxui.AdapterItem { |
| if id, group := n.group.Index(uint64(index)); group == nil { |
| return hierarchyItem{Start: id, End: id + 1} |
| } else { |
| return hierarchyItem(group.Range) |
| } |
| } |
| |
| func (n hierarchyNode) ItemIndex(item gxui.AdapterItem) int { |
| return int(n.group.IndexOf(item.(cmdNode).atomRange().Start)) |
| } |
| |
| func (n hierarchyNode) Create(theme gxui.Theme, index int) gxui.Control { |
| id, subgroup := n.group.Index(uint64(index)) |
| if subgroup != nil { |
| return createAtomGroupControls(theme, n.appCtx, *subgroup) |
| } else { |
| return createAtomControls(theme, n.appCtx, id) |
| } |
| } |
| |
| type CommandAdapter struct { |
| gxui.AdapterBase |
| hierarchyNode |
| appCtx *ApplicationContext |
| } |
| |
| func CreateCommandAdapter(appCtx *ApplicationContext) *CommandAdapter { |
| a := &CommandAdapter{ |
| hierarchyNode: hierarchyNode{appCtx: appCtx}, |
| } |
| return a |
| } |
| |
| func (a *CommandAdapter) SetRoot(root atom.Group) { |
| a.group = root |
| a.DataReplaced() |
| } |
| |
| func (a CommandAdapter) AtomRange(item gxui.AdapterItem) atom.Range { |
| if item == nil { |
| return atom.Range{} |
| } |
| return item.(cmdNode).atomRange() |
| } |
| |
| func (a CommandAdapter) Item(id atom.ID) gxui.AdapterItem { |
| return hierarchyItem{Start: id, End: id + 1} |
| } |
| |
| // gxui.TreeAdapter compliance |
| func (a CommandAdapter) Size(theme gxui.Theme) math.Size { |
| return math.Size{W: math.MaxSize.W, H: kCommandAdapterItemHeight} |
| } |