3902628e35
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>
474 lines
14 KiB
Go
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)
|
|
}
|