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