blob: b206889620af1045e7ec927474144b34df683d6f [file] [log] [blame]
// 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 (
"bytes"
"fmt"
"image"
"net"
"os"
"os/exec"
"time"
"android.googlesource.com/platform/tools/gpu/atexit"
"android.googlesource.com/platform/tools/gpu/atom"
"android.googlesource.com/platform/tools/gpu/binary/cyclic"
"android.googlesource.com/platform/tools/gpu/binary/vle"
"android.googlesource.com/platform/tools/gpu/_experimental/client/schema"
"android.googlesource.com/platform/tools/gpu/log"
"android.googlesource.com/platform/tools/gpu/memory"
"android.googlesource.com/platform/tools/gpu/service"
"github.com/google/gxui"
)
const mtu = 1024
var (
rpcResourceRetryDelay = time.Millisecond * 250
)
type ApplicationContext struct {
Config
theme gxui.Theme
logger *log.Splitter
rpc service.RPC
captureID service.CaptureId
capture service.Capture
dropDownOverlay gxui.BubbleOverlay
toolTipOverlay gxui.BubbleOverlay
toolTipController *gxui.ToolTipController
onAtomSelected gxui.Event
onObjectSelected gxui.Event
onAddressSelected gxui.Event
onColorBufferUpdate gxui.Event
onDepthBufferUpdate gxui.Event
onRequestReplay gxui.Event
onWireframeChanged gxui.Event
onDeviceSelected gxui.Event
onAtomsUpdated gxui.Event
onHierarchyUpdated gxui.Event
onStateUpdated gxui.Event
onTimingInfoUpdated gxui.Event
schema service.Schema
atoms []schema.Atom
hierarchy atom.Group
state schema.Struct
selectedAtomID atom.ID
selectedAddress memory.Pointer
selectedObject interface{}
selectedDevice service.DeviceId
wireframe bool
colorBuffer gxui.Texture
depthBuffer gxui.Texture
timingInfo service.TimingInfo
timingPerCommand map[uint64]uint64
}
func connectServer(config Config) (net.Conn, error) {
conn, err := net.Dial("tcp", config.Gapis)
if err == nil {
return conn, nil
}
// connection failed, was it localhost?
host, _, err2 := net.SplitHostPort(config.Gapis)
if host != "localhost" {
return nil, err
}
if err2 != nil {
return nil, err2
}
// try to run the server ourselves
null, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
if err != nil {
return nil, err
}
path, err := exec.LookPath("gapis")
if err != nil {
return nil, err
}
args := []string{path,
"--rpc", config.Gapis,
"--data", config.DataPath,
}
proc, err := os.StartProcess(path, args, &os.ProcAttr{Files: []*os.File{null, null, null}})
if err != nil {
return nil, err
}
// We are running a server, shut it down when we are done
atexit.Register(func() {
proc.Kill()
proc.Wait()
}, time.Second)
// and try to connect to it (for a while)
for i := 0; i < 10; i++ {
conn, err = net.Dial("tcp", config.Gapis)
if err == nil {
return conn, nil
}
time.Sleep(100 * time.Millisecond)
}
return nil, fmt.Errorf("Failed to spawn %v in time", path)
}
func CreateApplicationContext(theme gxui.Theme, config Config) (*ApplicationContext, error) {
dropDownOverlay := theme.CreateBubbleOverlay()
toolTipOverlay := theme.CreateBubbleOverlay()
rpcSocket, err := connectServer(config)
if err != nil {
return nil, err
}
rpc := service.CreateClient(rpcSocket, rpcSocket, mtu)
appCtx := &ApplicationContext{
Config: config,
theme: theme,
logger: &log.Splitter{},
rpc: rpc,
dropDownOverlay: dropDownOverlay,
toolTipOverlay: toolTipOverlay,
toolTipController: gxui.CreateToolTipController(toolTipOverlay, theme.Driver()),
onAtomSelected: gxui.CreateEvent(func() {}),
onObjectSelected: gxui.CreateEvent(func() {}),
onAddressSelected: gxui.CreateEvent(func() {}),
onColorBufferUpdate: gxui.CreateEvent(func() {}),
onDepthBufferUpdate: gxui.CreateEvent(func() {}),
onRequestReplay: gxui.CreateEvent(func() {}),
onWireframeChanged: gxui.CreateEvent(func() {}),
onDeviceSelected: gxui.CreateEvent(func() {}),
onAtomsUpdated: gxui.CreateEvent(func() {}),
onHierarchyUpdated: gxui.CreateEvent(func() {}),
onStateUpdated: gxui.CreateEvent(func() {}),
onTimingInfoUpdated: gxui.CreateEvent(func() {}),
selectedAtomID: InvalidAtomID,
}
return appCtx, nil
}
func (c *ApplicationContext) Run(f func()) {
c.theme.Driver().Call(f)
}
func (c *ApplicationContext) SelectAtom(id atom.ID) {
if c.selectedAtomID != id {
c.logger.Infof("SelectAtom(%v)", id)
c.selectedAtomID = id
c.onAtomSelected.Fire()
}
}
func (c *ApplicationContext) SelectAddress(address memory.Pointer) {
if c.selectedAddress != address {
c.logger.Infof("SelectAddress(%v)", address)
c.selectedAddress = address
c.onAddressSelected.Fire()
}
}
func (c *ApplicationContext) SelectObject(object interface{}) {
if c.selectedObject != object {
c.logger.Infof("SelectObject(%v)", object)
c.selectedObject = object
c.onObjectSelected.Fire()
}
}
func (c *ApplicationContext) SelectDevice(device service.DeviceId) {
if c.selectedDevice != device {
c.logger.Infof("SelectDevice(%v)", device)
c.selectedDevice = device
c.onDeviceSelected.Fire()
}
}
func (c *ApplicationContext) SetWireframe(value bool) {
if c.wireframe != value {
c.logger.Infof("SetWireframe(%v)", value)
c.wireframe = value
c.onWireframeChanged.Fire()
}
}
func (c *ApplicationContext) LoadCapture(captureID service.CaptureId, resetSelected bool) {
if captureID == c.captureID {
return
}
logger := c.logger.Fork().Enter("LoadCapture")
logger.Infof("(capture: %v)", captureID)
go func() {
var err error
capture, err := c.rpc.ResolveCapture(logger, captureID)
if err != nil {
logger.Errorf("Error resolving capture: %v", err)
return
}
atoms, err := c.rpc.ResolveAtomStream(logger, capture.Atoms)
if err != nil {
logger.Errorf("Error resolving capture: %v", err)
return
}
s, err := c.rpc.ResolveSchema(logger, capture.Schema)
if err != nil {
logger.Errorf("Error resolving capture: %v", err)
return
}
c.Run(func() {
atoms, err := schema.DecodeAtoms(atoms, s)
if err != nil {
panic(err)
}
c.captureID = captureID
c.capture = capture
c.schema = s
c.atoms = atoms
if resetSelected {
c.selectedAtomID = InvalidAtomID
c.selectedAddress = 0
c.selectedObject = nil
}
c.onAtomsUpdated.Fire()
c.RequestReplay()
logger.Infof("Capture '%s' loaded: %d atoms", c.capture.GetName(), len(atoms))
})
}()
}
func (c *ApplicationContext) LoadHierarchy() {
captureID := c.captureID
logger := c.logger.Fork().Enter("LoadHierarchy")
logger.Infof("(capture: %v)", captureID)
go func() {
hierarchy, err := c.rpc.GetHierarchy(logger, captureID)
if err != nil {
return
}
root, err := c.rpc.ResolveHierarchy(logger, hierarchy)
if err != nil {
return
}
c.Run(func() {
c.hierarchy = atom.Group{}
root.Root.Unpack(&c.hierarchy)
c.onHierarchyUpdated.Fire()
logger.Infof("Hierarchy loaded")
})
}()
}
func (c *ApplicationContext) LoadState() {
/*
captureID := c.captureID
after := c.selectedAtomID
logger := c.logger.Fork().Enter("LoadState")
logger.Errorf("(capture: %v, after: %v)", captureID, after)
go func() {
id, err := c.rpc.GetState(logger, captureID, uint64(after))
if err != nil {
return
}
bin, err := c.rpc.ResolveBinary(logger, id)
if err != nil {
return
}
c.Run(func() {
_ = bin
c.state = schema.Struct{Fields: []schema.Field{
schema.Field{Info: &service.FieldInfo{Name: "FIXME b/19835606"}},
}}
state, err := schema.ReadType(c.schema.State, binary.IntvDecoder(bytes.NewBuffer(bin.Data)))
if err != nil {
panic(err)
}
c.state = state.(schema.Struct)
c.onStateUpdated.Fire()
})
}()
*/
}
func (c *ApplicationContext) RequestReplay() {
c.onRequestReplay.Fire()
}
type ImageCallback func(gxui.Texture)
func isClosed(c <-chan struct{}) bool {
select {
case <-c:
return true
default:
return false
}
}
func (c *ApplicationContext) RequestThumbnail(after atom.ID, maxWidth, maxHeight uint32, callback ImageCallback) chan<- struct{} {
logger := c.logger.Fork().Enter("RequestThumbnail")
logger.Infof("(device: %v, after: %v, max size: %dx%d)", c.selectedDevice, after, maxWidth, maxHeight)
cancel := make(chan struct{})
device := c.selectedDevice
captureID := c.captureID
settings := service.RenderSettings{
MaxWidth: maxWidth,
MaxHeight: maxHeight,
Wireframe: false,
}
if !device.Valid() {
logger.Warningf("No device selected")
return nil
}
apiID := c.atoms[after].Info.Api
go func() {
imageID, err := c.rpc.GetFramebufferColor(logger, device, captureID, apiID, uint64(after), settings)
if err != nil {
return
}
if isClosed(cancel) {
logger.Infof("Request cancelled")
return
}
imageInfo, err := c.rpc.ResolveImageInfo(logger, imageID)
if err != nil {
return
}
if isClosed(cancel) {
logger.Infof("Request cancelled")
return
}
logger.Infof("Image info resolved")
imageData, err := c.rpc.ResolveBinary(logger, imageInfo.Data)
if err != nil {
return
}
if isClosed(cancel) {
logger.Infof("Request cancelled")
return
}
logger.Infof("Image %dx%d resolved", imageInfo.Width, imageInfo.Height)
if imageInfo.Width > 0 && imageInfo.Height > 0 {
img := image.NewRGBA(image.Rect(0, 0, int(imageInfo.Width), int(imageInfo.Height)))
img.Pix = []byte(imageData.Data)
c.Run(func() {
tex := c.theme.Driver().CreateTexture(img, 1)
tex.SetFlipY(true)
callback(tex)
})
}
}()
return cancel
}
type MemoryCallback func(service.MemoryInfo)
func (c *ApplicationContext) RequestMemory(after atom.ID, base memory.Pointer, size uint64, callback MemoryCallback) chan<- struct{} {
logger := c.logger.Fork().Enter("RequestMemory")
logger.Infof("(after: %v, base: 0x%x, size: 0x%x)", after, base, size)
cancel := make(chan struct{})
captureID := c.captureID
if c.captureID.Valid() {
go func() {
rng := service.MemoryRange{Base: uint64(base), Size: size}
id, err := c.rpc.GetMemoryInfo(logger, captureID, uint64(after), rng)
if err != nil {
return
}
if isClosed(cancel) {
logger.Infof("Request cancelled")
return
}
info, err := c.rpc.ResolveMemoryInfo(logger, id)
if err != nil {
return
}
if isClosed(cancel) {
logger.Infof("Request cancelled")
return
}
c.Run(func() {
callback(info)
})
}()
}
return cancel
}
func (c *ApplicationContext) ReplaceAtom(a schema.Atom, id atom.ID) {
buf := &bytes.Buffer{}
enc := cyclic.Encoder(vle.Writer(buf))
err := a.Pack(enc)
if err != nil {
panic(err)
}
b := service.Binary{
Data: buf.Bytes(),
}
capture, err := c.rpc.ReplaceAtom(c.logger, c.captureID, uint64(id), a.Info.Type, b)
if err != nil {
panic(err)
}
c.LoadCapture(capture, false)
}
func (c *ApplicationContext) Theme() gxui.Theme { return c.theme }
func (c *ApplicationContext) Logger() *log.Splitter { return c.logger }
func (c *ApplicationContext) Rpc() service.RPC { return c.rpc }
func (c *ApplicationContext) DropDownOverlay() gxui.BubbleOverlay { return c.dropDownOverlay }
func (c *ApplicationContext) ToolTipOverlay() gxui.BubbleOverlay { return c.toolTipOverlay }
func (c *ApplicationContext) ToolTipController() *gxui.ToolTipController { return c.toolTipController }
func (c *ApplicationContext) Schema() service.Schema { return c.schema }
func (c *ApplicationContext) Atoms() []schema.Atom { return c.atoms }
func (c *ApplicationContext) Hierarchy() atom.Group { return c.hierarchy }
func (c *ApplicationContext) State() schema.Struct { return c.state }
func (c *ApplicationContext) SelectedAtomID() atom.ID { return c.selectedAtomID }
func (c *ApplicationContext) SelectedAddress() memory.Pointer { return c.selectedAddress }
func (c *ApplicationContext) SelectedObject() interface{} { return c.selectedObject }
func (c *ApplicationContext) SelectedDevice() service.DeviceId { return c.selectedDevice }
func (c *ApplicationContext) Wireframe() bool { return c.wireframe }
func (c *ApplicationContext) ColorBuffer() gxui.Texture { return c.colorBuffer }
func (c *ApplicationContext) DepthBuffer() gxui.Texture { return c.depthBuffer }
func (c *ApplicationContext) CaptureID() service.CaptureId { return c.captureID }
func (c *ApplicationContext) Capture() service.Capture { return c.capture }
func (c *ApplicationContext) OnAtomSelected(f func()) gxui.EventSubscription {
return c.onAtomSelected.Listen(f)
}
func (c *ApplicationContext) OnObjectSelected(f func()) gxui.EventSubscription {
return c.onObjectSelected.Listen(f)
}
func (c *ApplicationContext) OnAddressSelected(f func()) gxui.EventSubscription {
return c.onAddressSelected.Listen(f)
}
func (c *ApplicationContext) OnColorBufferUpdate(f func()) gxui.EventSubscription {
return c.onColorBufferUpdate.Listen(f)
}
func (c *ApplicationContext) OnDepthBufferUpdate(f func()) gxui.EventSubscription {
return c.onDepthBufferUpdate.Listen(f)
}
func (c *ApplicationContext) OnRequestReplay(f func()) gxui.EventSubscription {
return c.onRequestReplay.Listen(f)
}
func (c *ApplicationContext) OnWireframeChanged(f func()) gxui.EventSubscription {
return c.onWireframeChanged.Listen(f)
}
func (c *ApplicationContext) OnDeviceSelected(f func()) gxui.EventSubscription {
return c.onDeviceSelected.Listen(f)
}
func (c *ApplicationContext) OnAtomsUpdated(f func()) gxui.EventSubscription {
return c.onAtomsUpdated.Listen(f)
}
func (c *ApplicationContext) OnHierarchyUpdated(f func()) gxui.EventSubscription {
return c.onHierarchyUpdated.Listen(f)
}
func (c *ApplicationContext) OnStateUpdated(f func()) gxui.EventSubscription {
return c.onStateUpdated.Listen(f)
}
func (c *ApplicationContext) OnTimingInfoUpdated(f func()) gxui.EventSubscription {
return c.onTimingInfoUpdated.Listen(f)
}