It is an error to use a gl.Context concurrently. (Each Context contains a state machine, so there is no way this can work.) Up until now only the GL driver could report such a misuse, and it generally does a poor job of it. Our command buffer adds some small oppertunity for the race detector to help, but it makes it harder to debug other kinds of driver crashes, so it is worth disabling in debug mode. To make it easy, compiling with -tags gldebug now inserts an explicit check that there are no other active GL calls outstanding. Adding something like: go func() { for { glctx.GetInteger(gl.ALPHA_BITS) } }() to x/mobile/example/basic now reliably crashes when compiled with -tags gldebug, providing a stack trace that includes both misbehaving goroutines. Change-Id: I3d85d94220bca2a15eaf2742f13b44db1f3428bf Reviewed-on: https://go-review.googlesource.com/15180 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
373 lines
7.8 KiB
Go
373 lines
7.8 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build ignore
|
|
|
|
// The gendebug program takes gl.go and generates a version of it
|
|
// where each function includes tracing code that writes its arguments
|
|
// to the standard log.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/printer"
|
|
"go/token"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
var outfile = flag.String("o", "", "result will be written to the file instead of stdout.")
|
|
|
|
var fset = new(token.FileSet)
|
|
|
|
func typeString(t ast.Expr) string {
|
|
buf := new(bytes.Buffer)
|
|
printer.Fprint(buf, fset, t)
|
|
return buf.String()
|
|
}
|
|
|
|
func typePrinter(t string) string {
|
|
switch t {
|
|
case "[]float32", "[]byte":
|
|
return "len(%d)"
|
|
}
|
|
return "%v"
|
|
}
|
|
|
|
func typePrinterArg(t, name string) string {
|
|
switch t {
|
|
case "[]float32", "[]byte":
|
|
return "len(" + name + ")"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func die(err error) {
|
|
fmt.Fprintf(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
f, err := parser.ParseFile(fset, "consts.go", nil, parser.ParseComments)
|
|
if err != nil {
|
|
die(err)
|
|
}
|
|
entries := enum(f)
|
|
|
|
f, err = parser.ParseFile(fset, "gl.go", nil, parser.ParseComments)
|
|
if err != nil {
|
|
die(err)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
fmt.Fprint(buf, preamble)
|
|
|
|
fmt.Fprintf(buf, "func (v Enum) String() string {\n")
|
|
fmt.Fprintf(buf, "\tswitch v {\n")
|
|
for _, e := range dedup(entries) {
|
|
fmt.Fprintf(buf, "\tcase 0x%x: return %q\n", e.value, e.name)
|
|
}
|
|
fmt.Fprintf(buf, "\t%s\n", `default: return fmt.Sprintf("gl.Enum(0x%x)", uint32(v))`)
|
|
fmt.Fprintf(buf, "\t}\n")
|
|
fmt.Fprintf(buf, "}\n\n")
|
|
|
|
for _, d := range f.Decls {
|
|
// Before:
|
|
// func (ctx *context) StencilMask(mask uint32) {
|
|
// C.glStencilMask(C.GLuint(mask))
|
|
// }
|
|
//
|
|
// After:
|
|
// func (ctx *context) StencilMask(mask uint32) {
|
|
// defer func() {
|
|
// errstr := ctx.errDrain()
|
|
// log.Printf("gl.StencilMask(%v) %v", mask, errstr)
|
|
// }()
|
|
// C.glStencilMask(C.GLuint(mask))
|
|
// }
|
|
fn, ok := d.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if fn.Recv == nil || fn.Recv.List[0].Names[0].Name != "ctx" {
|
|
continue
|
|
}
|
|
|
|
var (
|
|
params []string
|
|
paramTypes []string
|
|
results []string
|
|
resultTypes []string
|
|
)
|
|
|
|
// Print function signature.
|
|
fmt.Fprintf(buf, "func (ctx *context) %s(", fn.Name.Name)
|
|
for i, p := range fn.Type.Params.List {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
ty := typeString(p.Type)
|
|
for i, n := range p.Names {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
fmt.Fprintf(buf, "%s ", n.Name)
|
|
params = append(params, n.Name)
|
|
paramTypes = append(paramTypes, ty)
|
|
}
|
|
fmt.Fprint(buf, ty)
|
|
}
|
|
fmt.Fprintf(buf, ") (")
|
|
if fn.Type.Results != nil {
|
|
for i, r := range fn.Type.Results.List {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
ty := typeString(r.Type)
|
|
if len(r.Names) == 0 {
|
|
name := fmt.Sprintf("r%d", i)
|
|
fmt.Fprintf(buf, "%s ", name)
|
|
results = append(results, name)
|
|
resultTypes = append(resultTypes, ty)
|
|
}
|
|
for i, n := range r.Names {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
fmt.Fprintf(buf, "%s ", n.Name)
|
|
results = append(results, n.Name)
|
|
resultTypes = append(resultTypes, ty)
|
|
}
|
|
fmt.Fprint(buf, ty)
|
|
}
|
|
}
|
|
fmt.Fprintf(buf, ") {\n")
|
|
|
|
// gl.GetError is used by errDrain, which will be made part of
|
|
// all functions. So do not apply it to gl.GetError to avoid
|
|
// infinite recursion.
|
|
skip := fn.Name.Name == "GetError"
|
|
|
|
if !skip {
|
|
// Insert a defer block for tracing.
|
|
fmt.Fprintf(buf, "defer func() {\n")
|
|
fmt.Fprintf(buf, "\terrstr := ctx.errDrain()\n")
|
|
switch fn.Name.Name {
|
|
case "GetUniformLocation", "GetAttribLocation":
|
|
fmt.Fprintf(buf, "\tr0.name = name\n")
|
|
}
|
|
fmt.Fprintf(buf, "\tlog.Printf(\"gl.%s(", fn.Name.Name)
|
|
for i, p := range paramTypes {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
fmt.Fprint(buf, typePrinter(p))
|
|
}
|
|
fmt.Fprintf(buf, ") ")
|
|
if len(resultTypes) > 1 {
|
|
fmt.Fprint(buf, "(")
|
|
}
|
|
for i, r := range resultTypes {
|
|
if i > 0 {
|
|
fmt.Fprint(buf, ", ")
|
|
}
|
|
fmt.Fprint(buf, typePrinter(r))
|
|
}
|
|
if len(resultTypes) > 1 {
|
|
fmt.Fprint(buf, ") ")
|
|
}
|
|
fmt.Fprintf(buf, "%%v\"")
|
|
for i, p := range paramTypes {
|
|
fmt.Fprintf(buf, ", %s", typePrinterArg(p, params[i]))
|
|
}
|
|
for i, r := range resultTypes {
|
|
fmt.Fprintf(buf, ", %s", typePrinterArg(r, results[i]))
|
|
}
|
|
fmt.Fprintf(buf, ", errstr)\n")
|
|
fmt.Fprintf(buf, "}()\n")
|
|
}
|
|
|
|
// Print original body of function.
|
|
for _, s := range fn.Body.List {
|
|
if c := enqueueCall(s); c != nil {
|
|
c.Fun.(*ast.SelectorExpr).Sel.Name = "enqueueDebug"
|
|
setEnqueueBlocking(c)
|
|
}
|
|
printer.Fprint(buf, fset, s)
|
|
fmt.Fprintf(buf, "\n")
|
|
}
|
|
fmt.Fprintf(buf, "}\n\n")
|
|
}
|
|
|
|
b, err := format.Source(buf.Bytes())
|
|
if err != nil {
|
|
os.Stdout.Write(buf.Bytes())
|
|
die(err)
|
|
}
|
|
|
|
if *outfile == "" {
|
|
os.Stdout.Write(b)
|
|
return
|
|
}
|
|
if err := ioutil.WriteFile(*outfile, b, 0666); err != nil {
|
|
die(err)
|
|
}
|
|
}
|
|
|
|
func enqueueCall(stmt ast.Stmt) *ast.CallExpr {
|
|
exprStmt, ok := stmt.(*ast.ExprStmt)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
call, ok := exprStmt.X.(*ast.CallExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
fun, ok := call.Fun.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if fun.Sel.Name != "enqueue" {
|
|
return nil
|
|
}
|
|
return call
|
|
}
|
|
|
|
func setEnqueueBlocking(c *ast.CallExpr) {
|
|
lit := c.Args[0].(*ast.CompositeLit)
|
|
for _, elt := range lit.Elts {
|
|
kv := elt.(*ast.KeyValueExpr)
|
|
if kv.Key.(*ast.Ident).Name == "blocking" {
|
|
kv.Value = &ast.Ident{Name: "true"}
|
|
return
|
|
}
|
|
}
|
|
lit.Elts = append(lit.Elts, &ast.KeyValueExpr{
|
|
Key: &ast.Ident{
|
|
NamePos: lit.Rbrace,
|
|
Name: "blocking",
|
|
},
|
|
Value: &ast.Ident{Name: "true"},
|
|
})
|
|
}
|
|
|
|
const preamble = `// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Generated from gl.go using go generate. DO NOT EDIT.
|
|
// See doc.go for details.
|
|
|
|
// +build linux darwin
|
|
// +build gldebug
|
|
|
|
package gl
|
|
|
|
// #include "work.h"
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
func (ctx *context) errDrain() string {
|
|
var errs []Enum
|
|
for {
|
|
e := ctx.GetError()
|
|
if e == 0 {
|
|
break
|
|
}
|
|
errs = append(errs, e)
|
|
}
|
|
if len(errs) > 0 {
|
|
return fmt.Sprintf(" error: %v", errs)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (ctx *context) enqueueDebug(c call) C.uintptr_t {
|
|
numCalls := atomic.AddInt32(&ctx.debug, 1)
|
|
if numCalls > 1 {
|
|
panic("concurrent calls made to the same GL context")
|
|
}
|
|
defer func() {
|
|
if atomic.AddInt32(&ctx.debug, -1) > 0 {
|
|
select {} // block so you see us in the panic
|
|
}
|
|
}()
|
|
|
|
return ctx.enqueue(c)
|
|
}
|
|
|
|
`
|
|
|
|
type entry struct {
|
|
name string
|
|
value int
|
|
}
|
|
|
|
// enum builds a list of all GL constants that make up the gl.Enum type.
|
|
func enum(f *ast.File) []entry {
|
|
var entries []entry
|
|
for _, d := range f.Decls {
|
|
gendecl, ok := d.(*ast.GenDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if gendecl.Tok != token.CONST {
|
|
continue
|
|
}
|
|
for _, s := range gendecl.Specs {
|
|
v, ok := s.(*ast.ValueSpec)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if len(v.Names) != 1 || len(v.Values) != 1 {
|
|
continue
|
|
}
|
|
val, err := strconv.ParseInt(v.Values[0].(*ast.BasicLit).Value, 0, 32)
|
|
if err != nil {
|
|
log.Fatalf("enum %s: %v", v.Names[0].Name, err)
|
|
}
|
|
entries = append(entries, entry{v.Names[0].Name, int(val)})
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func dedup(entries []entry) []entry {
|
|
// Find all duplicates. Use "%d" as the name of any value with duplicates.
|
|
seen := make(map[int]int)
|
|
for _, e := range entries {
|
|
seen[e.value]++
|
|
}
|
|
var dedup []entry
|
|
for _, e := range entries {
|
|
switch seen[e.value] {
|
|
case 0: // skip, already here
|
|
case 1:
|
|
dedup = append(dedup, e)
|
|
default:
|
|
// value is duplicated
|
|
dedup = append(dedup, entry{fmt.Sprintf("%d", e.value), e.value})
|
|
seen[e.value] = 0
|
|
}
|
|
}
|
|
return dedup
|
|
}
|