/tools/gpu/gl/interface/gen_interface.go
Go | 460 lines | 334 code | 48 blank | 78 comment | 103 complexity | d8ce34b59fefbae724d5fa24ac5cc2f1 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, BSD-3-Clause, Apache-2.0
- // 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"`
- }
- // 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
- }
- // 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, 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, 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, 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 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_ and then have the actual
- // extension like KHR, EXT etc.
- ext = strings.TrimPrefix(ext, "GL_")
- 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
- }
- checks = append(checks, "!fFunctions.f"+function)
- }
- testOnly := []string{}
- for _, function := range feature.TestOnlyFunctions {
- if in(function, feature.OptionalFunctions) {
- continue
- }
- 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)
- }