| // 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) |
| } |