diff --git a/cmd/gapit/flags.go b/cmd/gapit/flags.go index 4674ce94b8..263931f3d2 100644 --- a/cmd/gapit/flags.go +++ b/cmd/gapit/flags.go @@ -155,12 +155,13 @@ type ( CommandFilterFlags } ReplaceResourceFlags struct { - Gapis GapisFlags - Gapir GapirFlags - Handle string `help:"required. handle of the resource to replace"` - ResourcePath string `help:"required. file path for the new resource"` - At int `help:"command index to replace the resource at"` - OutputTraceFile string `help:"file name for the updated trace"` + Gapis GapisFlags + Gapir GapirFlags + Handle string `help:"required. handle of the resource to replace"` + ResourcePath string `help:"file path for the new resource"` + At int `help:"command index to replace the resource(s) at"` + UpdateResourceBinary string `help:"shaders only. binary to run for every shader; consumes resource data from standard input and writes to standard output"` + OutputTraceFile string `help:"file name for the updated trace"` } StateFlags struct { Gapis GapisFlags diff --git a/cmd/gapit/replace_resource.go b/cmd/gapit/replace_resource.go index a9afec670b..10ddf202c3 100644 --- a/cmd/gapit/replace_resource.go +++ b/cmd/gapit/replace_resource.go @@ -18,14 +18,18 @@ import ( "context" "flag" "fmt" + "io" "io/ioutil" + "os/exec" "path/filepath" "strings" + "syscall" "github.com/google/gapid/core/app" "github.com/google/gapid/core/log" "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/service" + "github.com/google/gapid/gapis/service/path" ) type replaceResourceVerb struct{ ReplaceResourceFlags } @@ -50,13 +54,13 @@ func (verb *replaceResourceVerb) Run(ctx context.Context, flags flag.FlagSet) er return nil } - if verb.Handle == "" { - app.Usage(ctx, "-handle argument is required") + if verb.Handle == verb.UpdateResourceBinary { + app.Usage(ctx, "only one of -handle or -updateresourcebinary arguments is required") return nil } - if verb.ResourcePath == "" { - app.Usage(ctx, "-resourcepath argument is required") + if verb.Handle != "" && verb.ResourcePath == "" { + app.Usage(ctx, "-resourcepath argument is required if -handle is specified") return nil } @@ -90,36 +94,102 @@ func (verb *replaceResourceVerb) Run(ctx context.Context, flags flag.FlagSet) er verb.At = int(boxedCapture.(*service.Capture).NumCommands) - 1 } + var resourcePath *path.Any + var resourceData interface{} + for _, types := range resources.GetTypes() { if types.Type == api.ResourceType_ShaderResource { - var matchedResource *service.Resource - for _, v := range types.GetResources() { - if strings.Contains(v.GetHandle(), verb.Handle) { - if matchedResource != nil { - return fmt.Errorf("Multiple resources matched: %s, %s", matchedResource.GetHandle(), v.GetHandle()) + switch { + case verb.Handle != "": + matchedResource, err := resources.FindSingle(func(t api.ResourceType, r service.Resource) bool { + return strings.Contains(r.GetHandle(), verb.Handle) + }) + if err != nil { + return err + } + resourcePath = capture.Command(uint64(verb.At)).ResourceAfter(matchedResource.ID).Path() + newResourceBytes, err := ioutil.ReadFile(verb.ResourcePath) + if err != nil { + return log.Errf(ctx, err, "Could not read resource file %s", verb.ResourcePath) + } + resourceData = api.NewResourceData(&api.Shader{Type: api.ShaderType_Spirv, Source: string(newResourceBytes)}) + case verb.UpdateResourceBinary != "": + resources := types.GetResources() + ids := make([]*path.ID, len(resources)) + resourcesSource := make([]*api.ResourceData, len(resources)) + for i, v := range resources { + ids[i] = v.ID + resourcePath := capture.Command(uint64(verb.At)).ResourceAfter(v.ID) + rd, err := client.Get(ctx, resourcePath.Path()) + if err != nil { + log.Errf(ctx, err, "Could not get data for shader: %v", v) + return err + } + newData, err := verb.getNewResourceData(ctx, rd.(*api.ResourceData)) + if err != nil { + log.Errf(ctx, err, "Could not update the shader: %v", v) + return err } - matchedResource = v + resourcesSource[i] = api.NewResourceData(&api.Shader{Type: api.ShaderType_Spirv, Source: string(newData)}) } + resourceData = api.NewMultiResourceData(resourcesSource) + resourcePath = capture.Command(uint64(verb.At)).ResourcesAfter(ids).Path() } - resourcePath := capture.Command(uint64(verb.At)).ResourceAfter(matchedResource.ID) - newResourceBytes, err := ioutil.ReadFile(verb.ResourcePath) - if err != nil { - return log.Errf(ctx, err, "Could not read resource file %s", verb.ResourcePath) - } + } + } + newResourcePath, err := client.Set(ctx, resourcePath, resourceData) + if err != nil { + return log.Errf(ctx, err, "Could not update resource data: %v", resourcePath) + } + newCapture := path.FindCapture(newResourcePath.Node()) + newCaptureFilepath, err := filepath.Abs(verb.OutputTraceFile) + err = client.SaveCapture(ctx, newCapture, newCaptureFilepath) - newResourceData := api.NewResourceData(&api.Shader{Type: api.ShaderType_SpirvBinary, Source: string(newResourceBytes)}) - newResourcePath, err := client.Set(ctx, resourcePath.Path(), newResourceData) - if err != nil { - return log.Errf(ctx, err, "Could not update data for shader: %v", matchedResource) - } - newCapture := newResourcePath.GetResourceData().GetAfter().GetCapture() - newCaptureFilepath, err := filepath.Abs(verb.OutputTraceFile) - err = client.SaveCapture(ctx, newCapture, newCaptureFilepath) + log.I(ctx, "Capture written to: %v", newCaptureFilepath) + return nil +} + +// getNewResourceData runs the update resource binary on the old resource data +// and returns the newly generated resource data +func (verb *replaceResourceVerb) getNewResourceData(ctx context.Context, resourceData *api.ResourceData) (string, error) { + updateCmd := exec.Command(verb.UpdateResourceBinary) + stdin, err := updateCmd.StdinPipe() + if err != nil { + return "", log.Errf(ctx, err, "Failed to get stdinput pipe") + } + + stdout, err := updateCmd.StdoutPipe() + if err != nil { + return "", log.Errf(ctx, err, "Failed to get stdoutput pipe") + } + + stderr, err := updateCmd.StderrPipe() + if err != nil { + return "", log.Errf(ctx, err, "Failed to get stderr pipe") + } + + if err = updateCmd.Start(); err != nil { + return "", log.Errf(ctx, err, "Could not start the update resource command") + } + shaderSource := resourceData.GetShader().GetSource() + io.WriteString(stdin, shaderSource) + stdin.Close() + newResourceBytes, err := ioutil.ReadAll(stdout) + if err != nil { + return "", err + } + errorMsg, err := ioutil.ReadAll(stderr) + if err != nil { + return "", err + } - log.I(ctx, "Capture written to: %v", newCaptureFilepath) - return nil + if err = updateCmd.Wait(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return "", fmt.Errorf("Error generating new resource data; status %d; error message: %s", + exitError.Sys().(syscall.WaitStatus).ExitStatus(), errorMsg) } + return "", err } - return fmt.Errorf("Failed to find the resource with the handle %s", verb.Handle) + return string(newResourceBytes), err } diff --git a/gapis/api/gles/resources.go b/gapis/api/gles/resources.go index d76e862256..cd9a27b758 100644 --- a/gapis/api/gles/resources.go +++ b/gapis/api/gles/resources.go @@ -321,9 +321,9 @@ func (s Shaderʳ) SetResourceData( } resourceID := resourceIDs[s] - resource := resources.Find(s.ResourceType(ctx), resourceID) - if resource != nil { - return fmt.Errorf("Couldn't find resource") + resource, err := resources.Find(s.ResourceType(ctx), resourceID) + if err != nil { + return err } c, err := capture.ResolveFromPath(ctx, capturePath) diff --git a/gapis/api/resource.go b/gapis/api/resource.go index 12ff753ce3..5e7b680379 100644 --- a/gapis/api/resource.go +++ b/gapis/api/resource.go @@ -51,8 +51,8 @@ type Resource interface { // ResourceMeta represents resource with a state information obtained during building. type ResourceMeta struct { - Resource Resource // Resolved resource. - IDMap ResourceMap // Map for resolved resources to ids. + Resources []Resource // Resolved resource. + IDMap ResourceMap // Map for resolved resources to ids. } // ReplaceCallback is called from SetResourceData to propagate changes to current command stream. @@ -100,3 +100,7 @@ func NewResourceData(data interface{}) *ResourceData { panic(fmt.Errorf("%T is not a ResourceData type", data)) } } + +func NewMultiResourceData(resources []*ResourceData) *MultiResourceData { + return &MultiResourceData{Resources: resources} +} diff --git a/gapis/api/service.proto b/gapis/api/service.proto index aaf9e96a31..38d7c110a4 100644 --- a/gapis/api/service.proto +++ b/gapis/api/service.proto @@ -127,6 +127,11 @@ message ResourceData { } } +// MultiResourceData represents the state of resources at a single point in a capture +message MultiResourceData { + repeated ResourceData resources = 1; +} + // Texture represents a texture resource. message Texture { oneof type { diff --git a/gapis/api/vulkan/resources.go b/gapis/api/vulkan/resources.go index 1f55bc0672..a4b6e3dabe 100644 --- a/gapis/api/vulkan/resources.go +++ b/gapis/api/vulkan/resources.go @@ -765,9 +765,9 @@ func (shader ShaderModuleObjectʳ) SetResourceData( } resourceID := resourceIDs[shader] - resource := resources.Find(shader.ResourceType(ctx), resourceID) - if resource == nil { - return fmt.Errorf("Couldn't find resource") + resource, err := resources.Find(shader.ResourceType(ctx), resourceID) + if err != nil { + return err } c, err := capture.ResolveFromPath(ctx, at.Capture) diff --git a/gapis/resolve/resolvables.proto b/gapis/resolve/resolvables.proto index d5de54a794..95d2f21dc4 100644 --- a/gapis/resolve/resolvables.proto +++ b/gapis/resolve/resolvables.proto @@ -91,7 +91,7 @@ message AllResourceDataResolvable { } message ResourceMetaResolvable { - path.ID ID = 1; + repeated path.ID IDs = 1; path.Command after = 2; } diff --git a/gapis/resolve/resource_meta.go b/gapis/resolve/resource_meta.go index 978dd5f082..968d84c6e5 100644 --- a/gapis/resolve/resource_meta.go +++ b/gapis/resolve/resource_meta.go @@ -24,8 +24,8 @@ import ( ) // ResourceMeta returns the metadata for the specified resource. -func ResourceMeta(ctx context.Context, id *path.ID, after *path.Command) (*api.ResourceMeta, error) { - obj, err := database.Build(ctx, &ResourceMetaResolvable{ID: id, After: after}) +func ResourceMeta(ctx context.Context, ids []*path.ID, after *path.Command) (*api.ResourceMeta, error) { + obj, err := database.Build(ctx, &ResourceMetaResolvable{IDs: ids, After: after}) if err != nil { return nil, err } @@ -42,14 +42,18 @@ func (r *ResourceMetaResolvable) Resolve(ctx context.Context) (interface{}, erro if !ok { return nil, fmt.Errorf("Cannot resolve resources at command: %v", r.After) } - id := r.ID.ID() - val, ok := res.resources[id] - if !ok { - return nil, fmt.Errorf("Could not find resource %v", id) + ids := r.IDs + values := make([]api.Resource, len(ids)) + for i, id := range ids { + val, ok := res.resources[id.ID()] + if !ok { + return nil, fmt.Errorf("Could not find resource %v", id.ID()) + } + values[i] = val } result := &api.ResourceMeta{ - IDMap: res.resourceMap, - Resource: val, + IDMap: res.resourceMap, + Resources: values, } return result, nil } diff --git a/gapis/resolve/set.go b/gapis/resolve/set.go index 4410b15283..e2143b2e46 100644 --- a/gapis/resolve/set.go +++ b/gapis/resolve/set.go @@ -63,8 +63,8 @@ func change(ctx context.Context, p path.Node, val interface{}) (path.Node, error case *path.Report: return nil, fmt.Errorf("Reports are immutable") - case *path.ResourceData: - meta, err := ResourceMeta(ctx, p.ID, p.After) + case *path.MultiResourceData: + meta, err := ResourceMeta(ctx, p.IDs, p.After) if err != nil { return nil, err } @@ -85,12 +85,63 @@ func change(ctx context.Context, p path.Node, val interface{}) (path.Node, error cmds[where] = with.(api.Cmd) } + data, ok := val.(*api.MultiResourceData) + if !ok { + return nil, fmt.Errorf("Expected ResourceData, got %T", val) + } + + for i, resource := range meta.Resources { + if err := resource.SetResourceData(ctx, p.After, data.Resources[i], meta.IDMap, replaceCommands); err != nil { + return nil, err + } + } + + // Store the new command list + c, err := changeCommands(ctx, p.After.Capture, cmds) + if err != nil { + return nil, err + } + + return &path.MultiResourceData{ + IDs: p.IDs, // TODO: Shouldn't this change? + After: &path.Command{ + Capture: c, + Indices: p.After.Indices, + }, + }, nil + + case *path.ResourceData: + meta, err := ResourceMeta(ctx, []*path.ID{p.ID}, p.After) + if err != nil { + return nil, err + } + + cmdIdx := p.After.Indices[0] + // If we change resource data, subcommands do not affect this, so change + // the main command. + + oldCmds, err := NCmds(ctx, p.After.Capture, cmdIdx+1) + if err != nil { + return nil, err + } + + cmds := make([]api.Cmd, len(oldCmds)) + copy(cmds, oldCmds) + + replaceCommands := func(where uint64, with interface{}) { + cmds[where] = with.(api.Cmd) + } + data, ok := val.(*api.ResourceData) if !ok { return nil, fmt.Errorf("Expected ResourceData, got %T", val) } - if err := meta.Resource.SetResourceData(ctx, p.After, data, meta.IDMap, replaceCommands); err != nil { + if len(meta.Resources) != 1 { + return nil, fmt.Errorf("Expected a single resource, got %d", len(meta.Resources)) + } + + if err := meta.Resources[0].SetResourceData(ctx, p.After, data, meta.IDMap, replaceCommands); err != nil { return nil, err } diff --git a/gapis/service/path/path.go b/gapis/service/path/path.go index 2d29b6660e..3dfef54f3b 100644 --- a/gapis/service/path/path.go +++ b/gapis/service/path/path.go @@ -83,6 +83,7 @@ func (n *Metrics) Path() *Any { return &Any{Path: &Any_Metrics func (n *Parameter) Path() *Any { return &Any{Path: &Any_Parameter{n}} } func (n *Report) Path() *Any { return &Any{Path: &Any_Report{n}} } func (n *ResourceData) Path() *Any { return &Any{Path: &Any_ResourceData{n}} } +func (n *MultiResourceData) Path() *Any { return &Any{Path: &Any_MultiResourceData{n}} } func (n *Resources) Path() *Any { return &Any{Path: &Any_Resources{n}} } func (n *Result) Path() *Any { return &Any{Path: &Any_Result{n}} } func (n *Slice) Path() *Any { return &Any{Path: &Any_Slice{n}} } @@ -119,6 +120,7 @@ func (n Metrics) Parent() Node { return n.Command } func (n Parameter) Parent() Node { return n.Command } func (n Report) Parent() Node { return n.Capture } func (n ResourceData) Parent() Node { return n.After } +func (n MultiResourceData) Parent() Node { return n.After } func (n Resources) Parent() Node { return n.Capture } func (n Result) Parent() Node { return n.Command } func (n Slice) Parent() Node { return oneOfNode(n.Array) } @@ -150,6 +152,7 @@ func (n *Metrics) SetParent(p Node) { n.Command, _ = p.(*Comma func (n *Parameter) SetParent(p Node) { n.Command, _ = p.(*Command) } func (n *Report) SetParent(p Node) { n.Capture, _ = p.(*Capture) } func (n *ResourceData) SetParent(p Node) { n.After, _ = p.(*Command) } +func (n *MultiResourceData) SetParent(p Node) { n.After, _ = p.(*Command) } func (n *Resources) SetParent(p Node) { n.Capture, _ = p.(*Capture) } func (n *Result) SetParent(p Node) { n.Command, _ = p.(*Command) } func (n *State) SetParent(p Node) { n.After, _ = p.(*Command) } @@ -246,6 +249,11 @@ func (n ResourceData) Format(f fmt.State, c rune) { fmt.Fprintf(f, "%v.resource-data<%x>", n.Parent(), n.ID) } +// Format implements fmt.Formatter to print the version. +func (n MultiResourceData) Format(f fmt.State, c rune) { + fmt.Fprintf(f, "%v.resource-data<%x>", n.Parent(), n.IDs) +} + // Format implements fmt.Formatter to print the version. func (n Resources) Format(f fmt.State, c rune) { fmt.Fprintf(f, "%v.resources", n.Parent()) } @@ -602,6 +610,15 @@ func (n *Command) ResourceAfter(id *ID) *ResourceData { } } +// ResourcesAfter returns the path node to the resources with the given +// identifiers after this command. +func (n *Command) ResourcesAfter(ids []*ID) *MultiResourceData { + return &MultiResourceData{ + IDs: ids, + After: n, + } +} + // FramebufferObservation returns the path node to framebuffer observation // after this command. func (n *Command) FramebufferObservation() *FramebufferObservation { diff --git a/gapis/service/path/path.proto b/gapis/service/path/path.proto index 15d5b7c331..317471445e 100644 --- a/gapis/service/path/path.proto +++ b/gapis/service/path/path.proto @@ -49,18 +49,19 @@ message Any { Memory memory = 21; Mesh mesh = 22; Metrics metrics = 23; - Parameter parameter = 24; - Report report = 25; - ResourceData resource_data = 26; - Resources resources = 27; - Result result = 28; - Slice slice = 29; - State state = 30; - StateTree state_tree = 31; - StateTreeNode state_tree_node = 32; - StateTreeNodeForPath state_tree_node_for_path = 33; - Stats stats = 34; - Thumbnail thumbnail = 35; + MultiResourceData multi_resource_data = 24; + Parameter parameter = 25; + Report report = 26; + ResourceData resource_data = 27; + Resources resources = 28; + Result result = 29; + Slice slice = 30; + State state = 31; + StateTree state_tree = 32; + StateTreeNode state_tree_node = 33; + StateTreeNodeForPath state_tree_node_for_path = 34; + Stats stats = 35; + Thumbnail thumbnail = 36; } } @@ -378,6 +379,13 @@ message ResourceData { Command after = 2; } +// MultiResourceData is a path to a set of resource snapshots at a given point +// in a command stream. +message MultiResourceData { + Command after = 1; + repeated ID IDs = 2; +} + // Slice is a path to a subslice of a slice or array. message Slice { uint64 start = 1; diff --git a/gapis/service/path/validate.go b/gapis/service/path/validate.go index e0394f09b8..f011a510a9 100644 --- a/gapis/service/path/validate.go +++ b/gapis/service/path/validate.go @@ -249,6 +249,19 @@ func (n *ResourceData) Validate() error { ) } +// Validate checks the path is valid. +func (n *MultiResourceData) Validate() error { + if err := checkNotNilAndValidate(n, n.After, "after"); err != nil { + return err + } + for _, id := range n.IDs { + if err := checkIsValid(n, id, "id"); err != nil { + return err + } + } + return nil +} + // Validate checks the path is valid. func (n *Resources) Validate() error { return checkNotNilAndValidate(n, n.Capture, "capture") diff --git a/gapis/service/service.go b/gapis/service/service.go index 9058b1ec16..4f1c7b096f 100644 --- a/gapis/service/service.go +++ b/gapis/service/service.go @@ -243,6 +243,8 @@ func NewValue(v interface{}) *Value { return &Value{Val: &Value_ImageInfo{v}} case *device.Instance: return &Value{Val: &Value_Device{v}} + case *api.MultiResourceData: + return &Value{Val: &Value_MultiResourceData{v}} default: if v := box.NewValue(v); v != nil { @@ -280,17 +282,33 @@ func NewMemoryRanges(l memory.RangeList) []*MemoryRange { return out } -// Find looks up a resource by type and identifier. -func (r *Resources) Find(ty api.ResourceType, id id.ID) *Resource { +// FindAll returns all the resources that match the predicate f. +func (r *Resources) FindAll(f func(api.ResourceType, Resource) bool) []*Resource { + var resources []*Resource for _, t := range r.Types { - if t.Type == ty { - for _, r := range t.Resources { - if r.ID.ID() == id { - return r - } + for _, r := range t.Resources { + if f(t.Type, *r) { + resources = append(resources, r) } - break } } - return nil + return resources +} + +// FindSingle returns the single resource that matches the predicate f. +// If there are 0 or multiple resources found, FindSingle returns an error. +func (r *Resources) FindSingle(f func(api.ResourceType, Resource) bool) (*Resource, error) { + resources := r.FindAll(f) + if len(resources) != 1 { + return nil, fmt.Errorf("One resource expected, found %d", len(resources)) + } + return resources[0], nil +} + +// Find looks up a resource by type and identifier. +// Returns an error if 0 or multiple resources are found. +func (r *Resources) Find(ty api.ResourceType, id id.ID) (*Resource, error) { + return r.FindSingle(func(t api.ResourceType, r Resource) bool { + return t == ty && r.ID.ID() == id + }) } diff --git a/gapis/service/service.proto b/gapis/service/service.proto index 29525fc6d4..1fd092fe55 100644 --- a/gapis/service/service.proto +++ b/gapis/service/service.proto @@ -101,6 +101,7 @@ message Value { api.ResourceData resource_data = 31; api.Mesh mesh = 32; api.Metrics metrics = 33; + api.MultiResourceData multi_resource_data = 34; image.Info image_info = 40;