| // Copyright (C) 2016 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 gfxapi |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "fmt" |
| "io" |
| "reflect" |
| "unicode" |
| |
| "android.googlesource.com/platform/tools/gpu/api/snippets" |
| "android.googlesource.com/platform/tools/gpu/framework/binary" |
| "android.googlesource.com/platform/tools/gpu/framework/binary/cyclic" |
| "android.googlesource.com/platform/tools/gpu/framework/binary/registry" |
| "android.googlesource.com/platform/tools/gpu/framework/binary/vle" |
| "android.googlesource.com/platform/tools/gpu/gapid/service/path" |
| ) |
| |
| // GlobalSnippets is kindred grouped table of snippets with paths that apply |
| // to the global state. It is used to provide snippets for the API state |
| // object and ultimately the state view in the UI. |
| type GlobalSnippets []snippets.KindredSnippets |
| |
| // loadSnippets decodes the input reader and populates the receiver with |
| // the decoded snippet objects. Return non-nil if there was an error reading |
| // or decoding. The reader is a stream of Variant of type |
| // snippets.KindredSnippets. |
| func (g *GlobalSnippets) loadSnippets(in io.Reader) error { |
| d := cyclic.Decoder(vle.Reader(in)) |
| for { |
| obj := d.Variant() |
| if d.Error() != nil { |
| if d.Error() == io.EOF { |
| return nil |
| } |
| return d.Error() |
| } |
| snips, ok := obj.(snippets.KindredSnippets) |
| if !ok { |
| return fmt.Errorf("Expected snippets.KindredSnippets got %T", obj) |
| } |
| *g = append(*g, snips) |
| } |
| } |
| |
| // AddStateSnippetsFromReader decodes the input reader and adds snippet objects |
| // to the specified entity which should be the schema entity for the API |
| // state object associated with the snippets. Returns an error if there |
| // was an error reading or decoding. |
| func AddStateSnippetsFromReader(stateEntity *binary.Entity, reader io.Reader) error { |
| var g GlobalSnippets |
| if err := g.loadSnippets(reader); err != nil { |
| return err |
| } |
| for _, snip := range g { |
| stateEntity.Metadata = append(stateEntity.Metadata, snip) |
| } |
| return nil |
| } |
| |
| // AddStateSnippetsFromBase64String decodes the specified string as base64 |
| // encoded binary decoder input. Decoded snippet objects are added to the |
| // specified entity which should be the schema entity for the API state object |
| // associated with the snippets. Returns a non-nil error if there was an |
| // error decoding base64 or binary decoding. Base64 encoding is used so that |
| // the binary encoded string can be embedded directly in the library using |
| // the "embed" tool. |
| func AddStateSnippetsFromBase64String(stateEntity *binary.Entity, b64 string) error { |
| buf, err := base64.StdEncoding.DecodeString(b64) |
| if err != nil { |
| return err |
| } |
| return AddStateSnippetsFromReader(stateEntity, bytes.NewBuffer(buf)) |
| } |
| |
| type seenStack []reflect.Type |
| |
| func (seen seenStack) contains(t reflect.Type) bool { |
| for _, tt := range seen { |
| if t == tt { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // Generate snippets for CanFollow this information is not captured in the |
| // API file, but only in the Go code by the interface path.Linker. |
| // Consequently we generate these snippets at startup using reflection. |
| |
| // addCanFollow adds CanFollow snippets for the type 't' and its children. |
| func addCanFollow(pth snippets.Pathway, t reflect.Type, snips *[]binary.Object, seen seenStack) { |
| if seen.contains(t) { |
| // Avoid cycles |
| return |
| } |
| seen = append(seen, t) |
| |
| if t.Kind() == reflect.Ptr { |
| // Note pointer types in Go are a consequence of reference types in the |
| // API language, so we just dereference them. |
| addCanFollow(pth, t.Elem(), snips, seen) |
| return |
| } |
| |
| if t.Kind() != reflect.Struct && pth == nil { |
| panic(fmt.Errorf("non-struct type %v at top-level", t.Name)) |
| } |
| |
| linker := reflect.TypeOf((*path.Linker)(nil)).Elem() |
| if t.Implements(linker) { |
| *snips = append(*snips, &snippets.CanFollow{Path: pth}) |
| } |
| |
| // Visit the children. |
| switch t.Kind() { |
| case reflect.Struct: |
| addCanFollowStruct(pth, t, snips, seen, false) |
| case reflect.Array, reflect.Slice: |
| addCanFollow(snippets.Elem(pth), t.Elem(), snips, seen) |
| case reflect.Map: |
| addCanFollow(snippets.Key(pth), t.Key(), snips, seen) |
| addCanFollow(snippets.Elem(pth), t.Elem(), snips, seen) |
| } |
| } |
| |
| // addCanFollowStruct adds CanFollow snippets to a struct and all its children. |
| func addCanFollowStruct(path snippets.Pathway, t reflect.Type, snips *[]binary.Object, seen seenStack, fixCase bool) { |
| for i := 0; i < t.NumField(); i++ { |
| f := t.Field(i) |
| if len(f.PkgPath) != 0 { |
| // Non-empty package path means this field is unexported, ignore. |
| continue |
| } |
| if i == 0 && f.Anonymous && f.Type.Kind() == reflect.Struct && |
| f.Type.NumField() == 0 { |
| // Look for tags on the binary.Generate field |
| typeName := f.Tag.Get("display") |
| globals := f.Tag.Get("globals") |
| if globals != "" { |
| if fixCase { |
| // Fix case only applies to parameters, not globals |
| return |
| } |
| if globals != snippets.GlobalsTypename { |
| panic(fmt.Errorf("Type %v is tagged globals:\"%s\". Does not match with \"%s\"", t, globals, snippets.GlobalsTypename)) |
| } |
| typeName = globals |
| } |
| if path == nil { |
| if typeName == "" { |
| return |
| } |
| path = snippets.Relative(typeName) |
| } |
| continue |
| } |
| addCanFollow(field(path, f.Name, fixCase), f.Type, snips, seen) |
| } |
| } |
| |
| func addCanFollowAtom(t reflect.Type, snips *[]binary.Object) { |
| addCanFollowStruct(nil, t, snips, seenStack{t}, true) |
| } |
| |
| // AddCanFollowState add CanFollow snippets for the global API state object. |
| func AddCanFollowState(t reflect.Type, snips *[]binary.Object) { |
| addCanFollowStruct(nil, t, snips, seenStack{t}, false) |
| } |
| |
| func lowerCaseFirstCharacter(str string) string { |
| a := []rune(str) |
| a[0] = unicode.ToLower(a[0]) |
| return string(a) |
| } |
| |
| func field(path snippets.Pathway, name string, fixcase bool) snippets.Pathway { |
| if fixcase { |
| // See b/27585620 |
| name = lowerCaseFirstCharacter(name) |
| } |
| return snippets.Field(path, name) |
| } |
| |
| func init() { |
| linker := reflect.TypeOf((*path.Linker)(nil)).Elem() |
| |
| linkableMetaFactory := func(t reflect.Type, e *binary.Entity) { |
| defer func() { |
| if r := recover(); r != nil { |
| panic(fmt.Errorf("panic during %v: %v", t.Name, r)) |
| } |
| }() |
| // Add CanFollow snippets for all the atoms |
| if t.Implements(linker) { |
| // Ideally we would use atom.Atom here, but that creates a cycle. |
| return |
| } |
| // Add CanFollow snippets to the parameters of the atom |
| addCanFollowAtom(t, &e.Metadata) |
| } |
| registry.Factories.AddMetaFactory(linkableMetaFactory) |
| } |