skia2/tools/gpu/gl/interface/gen_interface.go
Kevin Lubick 3902628e35 Support WebGL 1.0 with caps
There is some logic in here for 2.0 as well, just as a
"as long as I was looking at the specs", but only 1.0
is really supported.

This seems to resolve the bug where some GPUs weren't
advertising correctly that they had vertex array object
support, by checking for both extension names (with and
without "GL_" prefix)

Of note, this saves about 18 Kb (5.5 Kb gzipped) of code size
by compiling out the unneeded GLES checks/functionality.

Bug: skia:8378
Change-Id: I773bf4dbf231b991051d2a9f640b8047a9010e7d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/203461
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
2019-03-28 17:21:23 +00:00

474 lines
14 KiB
Go

// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
// gen_interface creates the assemble/validate cpp files given the
// interface.json5 file.
// See README for more details.
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/flynn/json5"
)
var (
outDir = flag.String("out_dir", "../../src/gpu/gl", "Where to output the GrGlAssembleInterface_* and GrGlInterface.cpp files")
inTable = flag.String("in_table", "./interface.json5", "The JSON5 table to read in")
dryRun = flag.Bool("dryrun", false, "Print the outputs, don't write to file")
)
const (
CORE_FEATURE = "<core>"
SPACER = " "
GLES_FILE_NAME = "GrGLAssembleGLESInterfaceAutogen.cpp"
GL_FILE_NAME = "GrGLAssembleGLInterfaceAutogen.cpp"
WEBGL_FILE_NAME = "GrGLAssembleWebGLInterfaceAutogen.cpp"
INTERFACE_FILE_NAME = "GrGLInterfaceAutogen.cpp"
)
// FeatureSet represents one set of requirements for each of the GL "standards" that
// Skia supports. This is currently OpenGL, OpenGL ES and WebGL.
// OpenGL is typically abbreviated as just "GL".
// https://www.khronos.org/registry/OpenGL/index_gl.php
// https://www.khronos.org/opengles/
// https://www.khronos.org/registry/webgl/specs/1.0/
type FeatureSet struct {
GLReqs []Requirement `json:"GL"`
GLESReqs []Requirement `json:"GLES"`
WebGLReqs []Requirement `json:"WebGL"`
Functions []string `json:"functions"`
HardCodeFunctions []HardCodeFunction `json:"hardcode_functions"`
OptionalFunctions []string `json:"optional"` // not checked in validate
// only assembled/validated when testing
TestOnlyFunctions []string `json:"test_functions"`
Required bool `json:"required"`
EGLProc bool `json:"egl_proc"`
}
// Requirement lists how we know if a function exists. Extension is the
// GL extension (or the string CORE_FEATURE if it's part of the core functionality).
// MinVersion optionally indicates the minimum version of a standard
// that has the function.
// SuffixOverride allows the extension suffix to be manually specified instead
// of automatically derived from the extension name.
// (for example, if an NV extension specifies some EXT extensions)
type Requirement struct {
Extension string `json:"ext"` // required
MinVersion *GLVersion `json:"min_version"`
SuffixOverride *string `json:"suffix"`
}
// HardCodeFunction indicates to not use the C++ macro and just directly
// adds a given function ptr to the struct.
type HardCodeFunction struct {
PtrName string `json:"ptr_name"`
CastName string `json:"cast_name"`
GetName string `json:"get_name"`
}
var CORE_REQUIREMENT = Requirement{Extension: CORE_FEATURE, MinVersion: nil}
type GLVersion [2]int
// RequirementGetter functions allows us to "iterate" over the requirements
// of the different standards which are stored as keys in FeatureSet and
// normally not easily iterable.
type RequirementGetter func(FeatureSet) []Requirement
func glRequirements(fs FeatureSet) []Requirement {
return fs.GLReqs
}
func glesRequirements(fs FeatureSet) []Requirement {
return fs.GLESReqs
}
func webglRequirements(fs FeatureSet) []Requirement {
return fs.WebGLReqs
}
// generateAssembleInterface creates one GrGLAssembleInterface_[type]_gen.cpp
// for each of the standards
func generateAssembleInterface(features []FeatureSet) {
gl := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL, features, glRequirements)
writeToFile(*outDir, GL_FILE_NAME, gl)
gles := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL_ES, features, glesRequirements)
writeToFile(*outDir, GLES_FILE_NAME, gles)
webgl := fillAssembleTemplate(ASSEMBLE_INTERFACE_WEBGL, features, webglRequirements)
writeToFile(*outDir, WEBGL_FILE_NAME, webgl)
}
// fillAssembleTemplate returns a generated file given a template (for a single standard)
// to fill out and a slice of features with which to fill it. getReqs is used to select
// the requirements for the standard we are working on.
func fillAssembleTemplate(template string, features []FeatureSet, getReqs RequirementGetter) string {
content := ""
for _, feature := range features {
// For each feature set, we are going to create a series of
// if statements, which check for the requirements (e.g. extensions, version)
// and inside those if branches, write the code to load the
// correct function pointer to the interface. GET_PROC and
// GET_PROC_SUFFIX are macros defined in C++ part of the template
// to accomplish this (for a core feature and extensions, respectively).
reqs := getReqs(feature)
if len(reqs) == 0 {
continue
}
isEGL := feature.EGLProc
// blocks holds all the if blocks generated - it will be joined with else
// after and appended to content
blocks := []string{}
for i, req := range reqs {
block := ""
ifExpr := requirementIfExpression(req, true)
if ifExpr != "" {
if strings.HasPrefix(ifExpr, "(") {
ifExpr = "if " + ifExpr + " {"
} else {
ifExpr = "if (" + ifExpr + ") {"
}
// Indent the first if statement
if i == 0 {
block = addLine(block, ifExpr)
} else {
block += ifExpr + "\n"
}
}
// sort for determinism
sort.Strings(feature.Functions)
for _, function := range feature.Functions {
block = assembleFunction(block, ifExpr, function, isEGL, req)
}
sort.Strings(feature.TestOnlyFunctions)
if len(feature.TestOnlyFunctions) > 0 {
block += "#if GR_TEST_UTILS\n"
for _, function := range feature.TestOnlyFunctions {
block = assembleFunction(block, ifExpr, function, isEGL, req)
}
block += "#endif\n"
}
// a hard code function does not use the C++ macro
for _, hcf := range feature.HardCodeFunctions {
if ifExpr != "" {
// extra tab for being in an if statement
block += SPACER
}
line := fmt.Sprintf(`functions->%s =(%s*)get(ctx, "%s");`, hcf.PtrName, hcf.CastName, hcf.GetName)
block = addLine(block, line)
}
if ifExpr != "" {
block += SPACER + "}"
}
blocks = append(blocks, block)
}
content += strings.Join(blocks, " else ")
if feature.Required && reqs[0] != CORE_REQUIREMENT {
content += ` else {
SkASSERT(false); // Required feature
return nullptr;
}`
}
if !strings.HasSuffix(content, "\n") {
content += "\n"
}
// Add an extra space between blocks for easier readability
content += "\n"
}
return strings.Replace(template, "[[content]]", content, 1)
}
// assembleFunction is a little helper that will add a function to the interface
// using an appropriate macro (e.g. if the function is added)
// with an extension.
func assembleFunction(block, ifExpr, function string, isEGL bool, req Requirement) string {
if ifExpr != "" {
// extra tab for being in an if statement
block += SPACER
}
suffix := deriveSuffix(req.Extension)
// Some ARB extensions don't have ARB suffixes because they were written
// for backwards compatibility simultaneous to adding them as required
// in a new GL version.
if suffix == "ARB" {
suffix = ""
}
if req.SuffixOverride != nil {
suffix = *req.SuffixOverride
}
if isEGL {
block = addLine(block, fmt.Sprintf("GET_EGL_PROC_SUFFIX(%s, %s);", function, suffix))
} else if req.Extension == CORE_FEATURE || suffix == "" {
block = addLine(block, fmt.Sprintf("GET_PROC(%s);", function))
} else if req.Extension != "" {
block = addLine(block, fmt.Sprintf("GET_PROC_SUFFIX(%s, %s);", function, suffix))
}
return block
}
// requirementIfExpression returns a string that is an if expression
// Notably, there is no if expression if the function is a "core" function
// on all supported versions.
// The expressions are wrapped in parentheses so they can be safely
// joined together with && or ||.
func requirementIfExpression(req Requirement, isLocal bool) string {
mv := req.MinVersion
if req == CORE_REQUIREMENT {
return ""
}
if req.Extension == CORE_FEATURE && mv != nil {
return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d))", mv[0], mv[1])
}
extVar := "fExtensions"
if isLocal {
extVar = "extensions"
}
// We know it has an extension
if req.Extension != "" {
if mv == nil {
return fmt.Sprintf("%s.has(%q)", extVar, req.Extension)
} else {
return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d) && %s.has(%q))", mv[0], mv[1], extVar, req.Extension)
}
}
abort("ERROR: requirement must have ext")
return "ERROR"
}
// driveSuffix returns the suffix of the function associated with the given
// extension.
func deriveSuffix(ext string) string {
// Some extensions begin with GL_ or EGL_ and then have the actual
// extension like KHR, EXT etc.
ext = strings.TrimPrefix(ext, "GL_")
ext = strings.TrimPrefix(ext, "EGL_")
return strings.Split(ext, "_")[0]
}
// addLine is a little helper function which handles the newline and tab
func addLine(str, line string) string {
return str + SPACER + line + "\n"
}
func writeToFile(parent, file, content string) {
p := filepath.Join(parent, file)
if *dryRun {
fmt.Printf("Writing to %s\n", p)
fmt.Println(content)
} else {
if err := ioutil.WriteFile(p, []byte(content), 0644); err != nil {
abort("Error while writing to file %s: %s", p, err)
}
}
}
// validationEntry is a helper struct that contains anything
// necessary to make validation code for a given standard.
type validationEntry struct {
StandardCheck string
GetReqs RequirementGetter
}
func generateValidateInterface(features []FeatureSet) {
standards := []validationEntry{
{
StandardCheck: "GR_IS_GR_GL(fStandard)",
GetReqs: glRequirements,
}, {
StandardCheck: "GR_IS_GR_GL_ES(fStandard)",
GetReqs: glesRequirements,
}, {
StandardCheck: "GR_IS_GR_WEBGL(fStandard)",
GetReqs: webglRequirements,
},
}
content := ""
// For each feature, we are going to generate a series of
// boolean expressions which check that the functions we thought
// were gathered during the assemble phase actually were applied to
// the interface (functionCheck). This check will be guarded
// another set of if statements (one per standard) based
// on the same requirements (standardChecks) that were used when
// assembling the interface.
for _, feature := range features {
if allReqsAreCore(feature) {
content += functionCheck(feature, 1)
} else {
content += SPACER
standardChecks := []string{}
for _, std := range standards {
reqs := std.GetReqs(feature)
if reqs == nil || len(reqs) == 0 {
continue
}
expr := []string{}
for _, r := range reqs {
e := requirementIfExpression(r, false)
if e != "" {
expr = append(expr, e)
}
}
check := ""
if len(expr) == 0 {
check = fmt.Sprintf("%s", std.StandardCheck)
} else {
lineBreak := "\n" + SPACER + " "
check = fmt.Sprintf("(%s && (%s%s))", std.StandardCheck, lineBreak, strings.Join(expr, " ||"+lineBreak))
}
standardChecks = append(standardChecks, check)
}
content += fmt.Sprintf("if (%s) {\n", strings.Join(standardChecks, " ||\n"+SPACER+" "))
content += functionCheck(feature, 2)
content += SPACER + "}\n"
}
// add additional line between each block
content += "\n"
}
content = strings.Replace(VALIDATE_INTERFACE, "[[content]]", content, 1)
writeToFile(*outDir, INTERFACE_FILE_NAME, content)
}
// functionCheck returns an if statement that checks that all functions
// in the passed in slice are on the interface (that is, they are truthy
// on the fFunctions struct)
func functionCheck(feature FeatureSet, indentLevel int) string {
// sort for determinism
sort.Strings(feature.Functions)
indent := strings.Repeat(SPACER, indentLevel)
checks := []string{}
for _, function := range feature.Functions {
if in(function, feature.OptionalFunctions) {
continue
}
if feature.EGLProc {
checks = append(checks, "!fFunctions.fEGL"+function)
} else {
checks = append(checks, "!fFunctions.f"+function)
}
}
testOnly := []string{}
for _, function := range feature.TestOnlyFunctions {
if in(function, feature.OptionalFunctions) {
continue
}
if feature.EGLProc {
testOnly = append(testOnly, "!fFunctions.fEGL"+function)
} else {
testOnly = append(testOnly, "!fFunctions.f"+function)
}
}
for _, hcf := range feature.HardCodeFunctions {
checks = append(checks, "!fFunctions."+hcf.PtrName)
}
preCheck := ""
if len(testOnly) != 0 {
preCheck = fmt.Sprintf(`#if GR_TEST_UTILS
%sif (%s) {
%s%sRETURN_FALSE_INTERFACE;
%s}
#endif
`, indent, strings.Join(testOnly, " ||\n"+indent+" "), indent, SPACER, indent)
}
if len(checks) == 0 {
return preCheck + strings.Repeat(SPACER, indentLevel) + "// all functions were marked optional or test_only\n"
}
return preCheck + fmt.Sprintf(`%sif (%s) {
%s%sRETURN_FALSE_INTERFACE;
%s}
`, indent, strings.Join(checks, " ||\n"+indent+" "), indent, SPACER, indent)
}
// allReqsAreCore returns true iff the FeatureSet is part of "core" for
// all standards
func allReqsAreCore(feature FeatureSet) bool {
if feature.GLReqs == nil || feature.GLESReqs == nil {
return false
}
return feature.GLReqs[0] == CORE_REQUIREMENT && feature.GLESReqs[0] == CORE_REQUIREMENT && feature.WebGLReqs[0] == CORE_REQUIREMENT
}
func validateFeatures(features []FeatureSet) {
seen := map[string]bool{}
for _, feature := range features {
for _, fn := range feature.Functions {
if seen[fn] {
abort("ERROR: Duplicate function %s", fn)
}
seen[fn] = true
}
for _, fn := range feature.TestOnlyFunctions {
if seen[fn] {
abort("ERROR: Duplicate function %s\n", fn)
}
seen[fn] = true
}
}
}
// in returns true if |s| is *in* |a| slice.
func in(s string, a []string) bool {
for _, x := range a {
if x == s {
return true
}
}
return false
}
func abort(fmtStr string, inputs ...interface{}) {
fmt.Printf(fmtStr+"\n", inputs...)
os.Exit(1)
}
func main() {
flag.Parse()
b, err := ioutil.ReadFile(*inTable)
if err != nil {
abort("Could not read file %s", err)
}
dir, err := os.Open(*outDir)
if err != nil {
abort("Could not write to output dir %s", err)
}
defer dir.Close()
if fi, err := dir.Stat(); err != nil {
abort("Error getting info about %s: %s", *outDir, err)
} else if !fi.IsDir() {
abort("%s must be a directory", *outDir)
}
features := []FeatureSet{}
err = json5.Unmarshal(b, &features)
if err != nil {
abort("Invalid JSON: %s", err)
}
validateFeatures(features)
generateAssembleInterface(features)
generateValidateInterface(features)
}