diff --git a/gapic/src/main/com/google/gapid/models/ApiContext.java b/gapic/src/main/com/google/gapid/models/ApiContext.java index e9dfadfdc8..fa7d4baf4f 100644 --- a/gapic/src/main/com/google/gapid/models/ApiContext.java +++ b/gapic/src/main/com/google/gapid/models/ApiContext.java @@ -218,6 +218,20 @@ public int getPriority() { return context != null ? context.getPriority() : 0; } + public Path.State.Builder state(Path.State.Builder path) { + if (id != null) { + path.getContextBuilder().setData(id.getData()); + } + return path; + } + + public Path.StateTree.Builder stateTree(Path.StateTree.Builder path) { + if (id != null) { + path.getStateBuilder().getContextBuilder().setData(id.getData()); + } + return path; + } + public Path.Events.Builder events(Path.Events.Builder path) { path.getFilterBuilder().setContext(id); return path; diff --git a/gapic/src/main/com/google/gapid/models/ApiState.java b/gapic/src/main/com/google/gapid/models/ApiState.java index e6eb1bcf27..7cd898b549 100644 --- a/gapic/src/main/com/google/gapid/models/ApiState.java +++ b/gapic/src/main/com/google/gapid/models/ApiState.java @@ -17,6 +17,7 @@ import static com.google.gapid.rpc.UiErrorCallback.error; import static com.google.gapid.rpc.UiErrorCallback.success; +import static com.google.gapid.util.Paths.any; import static com.google.gapid.util.Paths.stateTree; import static com.google.gapid.widgets.Widgets.submitIfNotDisposed; import static java.util.logging.Level.SEVERE; @@ -53,16 +54,20 @@ public class ApiState private final ConstantSets constants; private final ObjectStore selection = ObjectStore.create(); + private final ApiContext contexts; public ApiState( - Shell shell, Client client, Follower follower, AtomStream atoms, ConstantSets constants) { + Shell shell, Client client, Follower follower, AtomStream atoms, ApiContext contexts, ConstantSets constants) { super(LOG, shell, client, Listener.class); this.constants = constants; + this.contexts = contexts; atoms.addListener(new AtomStream.Listener() { @Override public void onAtomsSelected(AtomIndex index) { - load(Paths.any(stateTree(index.getCommand())), false); + Path.StateTree path = stateTree(index.getCommand()); + path = contexts.getSelectedContext().stateTree(path.toBuilder()).build(); + load(any(path), false); } }); follower.addListener(new Follower.Listener() { diff --git a/gapic/src/main/com/google/gapid/models/Models.java b/gapic/src/main/com/google/gapid/models/Models.java index c1435eec46..dd0425d5f7 100644 --- a/gapic/src/main/com/google/gapid/models/Models.java +++ b/gapic/src/main/com/google/gapid/models/Models.java @@ -59,7 +59,7 @@ public static Models create(Shell shell, Settings settings, Client client) { Timeline timeline = new Timeline(shell, client, capture, contexts); AtomStream atoms = new AtomStream(shell, client, capture, contexts, constants); Resources resources = new Resources(shell, client, capture); - ApiState state = new ApiState(shell, client, follower, atoms, constants); + ApiState state = new ApiState(shell, client, follower, atoms, contexts, constants); Reports reports = new Reports(shell, client, capture, devices, contexts); Thumbnails thumbs = new Thumbnails(client, devices, capture); return new Models(settings, follower, capture, devices, atoms, contexts, timeline, resources, diff --git a/gapic/src/main/com/google/gapid/views/StateView.java b/gapic/src/main/com/google/gapid/views/StateView.java index 6a9cf7583d..b94a9c105a 100644 --- a/gapic/src/main/com/google/gapid/views/StateView.java +++ b/gapic/src/main/com/google/gapid/views/StateView.java @@ -353,7 +353,8 @@ private void updateSelectionState(boolean show) { models.state.getResolvedSelectedPath(), nodePath -> getTreePath(root, nodePath)).get(), selection -> { viewer.refresh(); - viewer.setSelection(new TreeSelection(selection), show); + viewer.setSelection((selection.getSegmentCount() == 0) ? + TreeSelection.EMPTY : new TreeSelection(selection), show); if (show) { viewer.setExpandedState(selection, true); } diff --git a/gapis/api/gles/gles.go b/gapis/api/gles/gles.go index 311d02de08..4ad5236423 100644 --- a/gapis/api/gles/gles.go +++ b/gapis/api/gles/gles.go @@ -23,6 +23,7 @@ import ( "github.com/google/gapid/core/log" "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/messages" + "github.com/google/gapid/gapis/resolve" "github.com/google/gapid/gapis/resolve/dependencygraph" "github.com/google/gapid/gapis/service/path" ) @@ -37,6 +38,33 @@ func (s *State) GetContext(thread uint64) *Context { return s.Contexts[thread] } +// Root returns the path to the root of the state to display. It can vary based +// on filtering mode. Returning nil, nil indicates there is no state to show at +// this point in the capture. +func (s *State) Root(ctx context.Context, p *path.State) (path.Node, error) { + if p.Context == nil || !p.Context.IsValid() { + return p, nil + } + c, err := resolve.Context(ctx, p.After.Capture.Context(p.Context)) + if err != nil { + return nil, err + } + for thread, context := range s.Contexts { + if c.ID() == context.ID() { + return s.contextRoot(p.After, thread), nil + } + } + return nil, nil +} + +func (s *State) contextRoot(p *path.Command, thread uint64) *path.MapIndex { + return path.NewField("Contexts", resolve.APIStateAfter(p, ID)).MapIndex(thread) +} + +func (s *State) objectsRoot(p *path.Command, thread uint64) *path.Field { + return s.contextRoot(p, thread).Field("Objects") +} + func (c *State) preMutate(ctx context.Context, s *api.GlobalState, cmd api.Cmd) error { c.CurrentContext = c.GetContext(cmd.Thread()) // TODO: Find better way to separate GL and EGL commands. diff --git a/gapis/api/gles/links.go b/gapis/api/gles/links.go index d80f90b5b9..f73b3a2f2a 100644 --- a/gapis/api/gles/links.go +++ b/gapis/api/gles/links.go @@ -40,10 +40,7 @@ func objects(ctx context.Context, p path.Node) (*path.Field, *Context, error) { if !ok { return nil, nil, nil } - return cmdPath.StateAfter(). - Field("Contexts"). - MapIndex(thread). - Field("Objects"), context, nil + return state.objectsRoot(cmdPath, thread), context, nil } return nil, nil, nil } @@ -67,11 +64,7 @@ func sharedObjects(ctx context.Context, p path.Node) (*path.Field, *Context, err if !ok { return nil, nil, nil } - return cmdPath.StateAfter(). - Field("Contexts"). - MapIndex(thread). - Field("Objects"). - Field("Shared"), context, nil + return state.objectsRoot(cmdPath, thread).Field("Shared"), context, nil } return nil, nil, nil } diff --git a/gapis/api/gvr/gvr.go b/gapis/api/gvr/gvr.go index 3885bc593a..4bd5d41cf6 100644 --- a/gapis/api/gvr/gvr.go +++ b/gapis/api/gvr/gvr.go @@ -29,6 +29,13 @@ import ( var _ = replay.QueryFramebufferAttachment(API{}) +// Root returns the path to the root of the state to display. It can vary based +// on filtering mode. Returning nil, nil indicates there is no state to show at +// this point in the capture. +func (s *State) Root(ctx context.Context, p *path.State) (path.Node, error) { + return nil, nil +} + func (c *State) preMutate(ctx context.Context, s *api.GlobalState, cmd api.Cmd) error { return nil } diff --git a/gapis/api/state.go b/gapis/api/state.go index 375f52e7d8..b558074755 100644 --- a/gapis/api/state.go +++ b/gapis/api/state.go @@ -28,6 +28,7 @@ import ( "github.com/google/gapid/gapis/database" "github.com/google/gapid/gapis/memory" "github.com/google/gapid/gapis/replay/value" + "github.com/google/gapid/gapis/service/path" "github.com/google/gapid/gapis/stringtable" ) @@ -64,6 +65,10 @@ type GlobalState struct { // State represents the graphics state for a single context. type State interface { + // Root returns the path to the root of the state to display. It can vary + // based on filtering mode. Returning nil, nil indicates there is no state + // to show at this point in the capture. + Root(ctx context.Context, p *path.State) (path.Node, error) } // NewStateWithEmptyAllocator returns a new, default-initialized State object, diff --git a/gapis/api/test/test.go b/gapis/api/test/test.go index 8c9e6420fb..cd8ae4e9d4 100644 --- a/gapis/api/test/test.go +++ b/gapis/api/test/test.go @@ -19,6 +19,7 @@ import ( "github.com/google/gapid/core/image" "github.com/google/gapid/gapis/api" + "github.com/google/gapid/gapis/service/path" ) type CustomState struct{} @@ -29,6 +30,13 @@ func (API) GetFramebufferAttachmentInfo(*api.GlobalState, uint64, api.Framebuffe func (API) Context(*api.GlobalState, uint64) api.Context { return nil } +// Root returns the path to the root of the state to display. It can vary based +// on filtering mode. Returning nil, nil indicates there is no state to show at +// this point in the capture. +func (s *State) Root(ctx context.Context, p *path.State) (path.Node, error) { + return nil, nil +} + func (c *State) preMutate(ctx context.Context, s *api.GlobalState, cmd api.Cmd) error { return nil } diff --git a/gapis/api/vulkan/vulkan.go b/gapis/api/vulkan/vulkan.go index 8321c13d67..b7a4580f3c 100644 --- a/gapis/api/vulkan/vulkan.go +++ b/gapis/api/vulkan/vulkan.go @@ -66,6 +66,13 @@ func (API) Context(s *api.GlobalState, thread uint64) api.Context { return VulkanContext{} } +// Root returns the path to the root of the state to display. It can vary based +// on filtering mode. Returning nil, nil indicates there is no state to show at +// this point in the capture. +func (*State) Root(ctx context.Context, p *path.State) (path.Node, error) { + return p, nil +} + func (c *State) preMutate(ctx context.Context, s *api.GlobalState, cmd api.Cmd) error { return nil } diff --git a/gapis/resolve/state.go b/gapis/resolve/state.go index e83536837b..988e36ebe5 100644 --- a/gapis/resolve/state.go +++ b/gapis/resolve/state.go @@ -16,7 +16,6 @@ package resolve import ( "context" - "fmt" "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/api/sync" @@ -38,12 +37,8 @@ func GlobalState(ctx context.Context, p *path.GlobalState) (*api.GlobalState, er } // State resolves the specific API state at a requested point in a capture. -func State(ctx context.Context, p *path.State) (api.State, error) { - obj, err := database.Build(ctx, &StateResolvable{p}) - if err != nil { - return nil, err - } - return obj.(api.State), nil +func State(ctx context.Context, p *path.State) (interface{}, error) { + return database.Build(ctx, &StateResolvable{p}) } // Resolve implements the database.Resolver interface. @@ -82,45 +77,60 @@ func (r *GlobalStateResolvable) Resolve(ctx context.Context) (interface{}, error // Resolve implements the database.Resolver interface. func (r *StateResolvable) Resolve(ctx context.Context) (interface{}, error) { ctx = capture.Put(ctx, r.Path.After.Capture) - cmdIdx := r.Path.After.Indices[0] - if len(r.Path.After.Indices) > 1 { - return nil, fmt.Errorf("Subcommands currently not supported for api state") // TODO: Subcommands - } - cmds, err := NCmds(ctx, r.Path.After.Capture, cmdIdx+1) + obj, _, _, err := state(ctx, r.Path) + return obj, err +} + +func state(ctx context.Context, p *path.State) (interface{}, path.Node, api.ID, error) { + cmd, err := Cmd(ctx, p.After) if err != nil { - return nil, err + return nil, nil, api.ID{}, err } - return apiState(ctx, cmds, r.Path) -} -func apiState(ctx context.Context, cmds []api.Cmd, p *path.State) (api.State, error) { - cmdIdx := p.After.Indices[0] - if len(p.After.Indices) > 1 { - return nil, fmt.Errorf("Subcommands currently not supported for api state") // TODO: Subcommands + a := cmd.API() + if a == nil { + return nil, nil, api.ID{}, &service.ErrDataUnavailable{Reason: messages.ErrStateUnavailable()} } - if count := uint64(len(cmds)); cmdIdx >= count { - return nil, errPathOOB(cmdIdx, "Index", 0, count-1, p) + + g, err := GlobalState(ctx, p.After.GlobalStateAfter()) + if err != nil { + return nil, nil, api.ID{}, err } - a := cmds[cmdIdx].API() - if a == nil { - return nil, &service.ErrDataUnavailable{Reason: messages.ErrStateUnavailable()} + + state := g.APIs[a.ID()] + if state == nil { + return nil, nil, api.ID{}, &service.ErrDataUnavailable{Reason: messages.ErrStateUnavailable()} } - s, err := capture.NewState(ctx) + + root, err := state.Root(ctx, p) if err != nil { - return nil, err + return nil, nil, api.ID{}, err + } + if root == nil { + return nil, nil, api.ID{}, &service.ErrDataUnavailable{Reason: messages.ErrStateUnavailable()} } - err = api.ForeachCmd(ctx, cmds[:cmdIdx+1], func(ctx context.Context, id api.CmdID, cmd api.Cmd) error { - cmd.Mutate(ctx, id, s, nil) - return nil + // Transform the State path node to a GlobalState node to prevent the + // object load recursing back into this function. + abs := path.Transform(root, func(n path.Node) path.Node { + switch n := n.(type) { + case *path.State: + return APIStateAfter(p.After, a.ID()) + default: + return n + } }) + + obj, err := Get(ctx, abs.Path()) if err != nil { - return nil, err + return nil, nil, api.ID{}, err } - res, found := s.APIs[a.ID()] - if !found { - return nil, &service.ErrDataUnavailable{Reason: messages.ErrStateUnavailable()} - } - return res, nil + return obj, abs, a.ID(), nil +} + +// APIStateAfter returns an absolute path to the API state after c. +func APIStateAfter(c *path.Command, a api.ID) path.Node { + p := &path.GlobalState{After: c} + return p.Field("APIs").MapIndex(a) } diff --git a/gapis/resolve/state_tree.go b/gapis/resolve/state_tree.go index a18ba5e76d..e593c65a7a 100644 --- a/gapis/resolve/state_tree.go +++ b/gapis/resolve/state_tree.go @@ -25,7 +25,6 @@ import ( "github.com/google/gapid/core/data/slice" "github.com/google/gapid/core/math/u64" "github.com/google/gapid/gapis/api" - "github.com/google/gapid/gapis/capture" "github.com/google/gapid/gapis/database" "github.com/google/gapid/gapis/memory" "github.com/google/gapid/gapis/service" @@ -45,10 +44,11 @@ func StateTree(ctx context.Context, c *path.StateTree) (*service.StateTree, erro } type stateTree struct { - state *api.GlobalState - root *stn - api *path.API - groupLimit uint64 + globalState *api.GlobalState + state interface{} + root *stn + api *path.API + groupLimit uint64 } // needsSubgrouping returns true if the child count exceeds the group limit and @@ -211,7 +211,7 @@ func (n *stn) buildChildren(ctx context.Context, tree *stateTree) { s, e := subgroupRange(tree.groupLimit, size, i) children = append(children, &stn{ name: fmt.Sprintf("[%d - %d]", n.subgroupOffset+s, n.subgroupOffset+e-1), - value: reflect.ValueOf(slice.ISlice(s, e, tree.state.MemoryLayout)), + value: reflect.ValueOf(slice.ISlice(s, e, tree.globalState.MemoryLayout)), path: path.NewSlice(s, e-1, n.path), isSubgroup: true, subgroupOffset: n.subgroupOffset + s, @@ -219,8 +219,8 @@ func (n *stn) buildChildren(ctx context.Context, tree *stateTree) { } } else { for i, c := uint64(0), slice.Count(); i < c; i++ { - ptr := slice.IIndex(i, tree.state.MemoryLayout) - el, err := memory.LoadPointer(ctx, ptr, tree.state.Memory, tree.state.MemoryLayout) + ptr := slice.IIndex(i, tree.globalState.MemoryLayout) + el, err := memory.LoadPointer(ctx, ptr, tree.globalState.Memory, tree.globalState.MemoryLayout) if err != nil { panic(err) } @@ -347,26 +347,22 @@ func stateValuePreview(v reflect.Value) (*box.Value, bool) { // Resolve builds and returns a *StateTree for the path.StateTreeNode. // Resolve implements the database.Resolver interface. func (r *StateTreeResolvable) Resolve(ctx context.Context) (interface{}, error) { - state, err := GlobalState(ctx, r.Path.After.GlobalStateAfter()) + globalState, err := GlobalState(ctx, r.Path.After.GlobalStateAfter()) if err != nil { return nil, err } - c, err := capture.ResolveFromPath(ctx, r.Path.After.Capture) + + rootObj, rootPath, apiID, err := state(ctx, r.Path) if err != nil { return nil, err } - cmdIdx := r.Path.After.Indices[0] - api := c.Commands[cmdIdx].API() - if api == nil { - return nil, fmt.Errorf("Command has no API") - } - apiState := state.APIs[api.ID()] - apiPath := &path.API{Id: path.NewID(id.ID(api.ID()))} + apiPath := &path.API{Id: path.NewID(id.ID(apiID))} + root := &stn{ name: "root", - value: deref(reflect.ValueOf(apiState)), - path: r.Path, + value: deref(reflect.ValueOf(rootObj)), + path: rootPath, } - return &stateTree{state, root, apiPath, uint64(r.ArrayGroupSize)}, nil + return &stateTree{globalState, rootObj, root, apiPath, uint64(r.ArrayGroupSize)}, nil } diff --git a/gapis/resolve/state_tree_test.go b/gapis/resolve/state_tree_test.go index b7916cbc04..0ae603fd83 100644 --- a/gapis/resolve/state_tree_test.go +++ b/gapis/resolve/state_tree_test.go @@ -165,12 +165,12 @@ func TestStateTreeNode(t *testing.T) { } ctx = capture.Put(ctx, c) rootPath := c.Command(0).StateAfter() - state, err := capture.NewState(ctx) + gs, err := capture.NewState(ctx) if err != nil { panic(err) } tree := &stateTree{ - state: state, + globalState: gs, root: &stn{ name: "root", value: reflect.ValueOf(testState), @@ -182,7 +182,7 @@ func TestStateTreeNode(t *testing.T) { root := &path.StateTreeNode{Indices: []uint64{}} // Write some data to 0x1000. - e := tree.state.MemoryEncoder(memory.ApplicationPool, memory.Range{Base: 0x1000, Size: 0x8000}) + e := gs.MemoryEncoder(memory.ApplicationPool, memory.Range{Base: 0x1000, Size: 0x8000}) for i := 0; i < 0x1000; i++ { e.I64(int64(i * 10)) } diff --git a/gapis/service/path/path.go b/gapis/service/path/path.go index 034993d833..77e603c04f 100644 --- a/gapis/service/path/path.go +++ b/gapis/service/path/path.go @@ -251,7 +251,9 @@ func (n Slice) Format(f fmt.State, c rune) { } // Format implements fmt.Formatter to print the version. -func (n State) Format(f fmt.State, c rune) { fmt.Fprintf(f, "%v.state-after", n.Parent()) } +func (n State) Format(f fmt.State, c rune) { + fmt.Fprintf(f, "%v.state", n.Parent(), n.Context) +} // Format implements fmt.Formatter to print the version. func (n StateTree) Format(f fmt.State, c rune) { fmt.Fprintf(f, "%v.tree", n.State) } diff --git a/gapis/service/path/path.proto b/gapis/service/path/path.proto index c7542171b7..5f0c5d47a9 100644 --- a/gapis/service/path/path.proto +++ b/gapis/service/path/path.proto @@ -369,6 +369,8 @@ message Slice { // State is a path to a subset of the GlobalState at a point in a capture. message State { Command after = 1; + // If non-nil, then the state is filtered to the specified context. + ID context = 2; } // GlobalState is an path node to the absolute global state after a specfied