1// Copyright 2019 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7// gen_interface creates the assemble/validate cpp files given the 8// interface.json5 file. 9// See README for more details. 10 11import ( 12 "flag" 13 "fmt" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "sort" 18 "strings" 19 20 "github.com/flynn/json5" 21) 22 23var ( 24 outDir = flag.String("out_dir", "../../src/gpu/gl", "Where to output the GrGlAssembleInterface_* and GrGlInterface.cpp files") 25 inTable = flag.String("in_table", "./interface.json5", "The JSON5 table to read in") 26 dryRun = flag.Bool("dryrun", false, "Print the outputs, don't write to file") 27) 28 29const ( 30 CORE_FEATURE = "<core>" 31 SPACER = " " 32 GLES_FILE_NAME = "GrGLAssembleGLESInterfaceAutogen.cpp" 33 GL_FILE_NAME = "GrGLAssembleGLInterfaceAutogen.cpp" 34 WEBGL_FILE_NAME = "GrGLAssembleWebGLInterfaceAutogen.cpp" 35 INTERFACE_FILE_NAME = "GrGLInterfaceAutogen.cpp" 36) 37 38// FeatureSet represents one set of requirements for each of the GL "standards" that 39// Skia supports. This is currently OpenGL, OpenGL ES and WebGL. 40// OpenGL is typically abbreviated as just "GL". 41// https://www.khronos.org/registry/OpenGL/index_gl.php 42// https://www.khronos.org/opengles/ 43// https://www.khronos.org/registry/webgl/specs/1.0/ 44type FeatureSet struct { 45 GLReqs []Requirement `json:"GL"` 46 GLESReqs []Requirement `json:"GLES"` 47 WebGLReqs []Requirement `json:"WebGL"` 48 49 Functions []string `json:"functions"` 50 HardCodeFunctions []HardCodeFunction `json:"hardcode_functions"` 51 OptionalFunctions []string `json:"optional"` // not checked in validate 52 53 // only assembled/validated when testing 54 TestOnlyFunctions []string `json:"test_functions"` 55 56 Required bool `json:"required"` 57} 58 59// Requirement lists how we know if a function exists. Extension is the 60// GL extension (or the string CORE_FEATURE if it's part of the core functionality). 61// MinVersion optionally indicates the minimum version of a standard 62// that has the function. 63// SuffixOverride allows the extension suffix to be manually specified instead 64// of automatically derived from the extension name. 65// (for example, if an NV extension specifies some EXT extensions) 66type Requirement struct { 67 Extension string `json:"ext"` // required 68 MinVersion *GLVersion `json:"min_version"` 69 SuffixOverride *string `json:"suffix"` 70} 71 72// HardCodeFunction indicates to not use the C++ macro and just directly 73// adds a given function ptr to the struct. 74type HardCodeFunction struct { 75 PtrName string `json:"ptr_name"` 76 CastName string `json:"cast_name"` 77 GetName string `json:"get_name"` 78} 79 80var CORE_REQUIREMENT = Requirement{Extension: CORE_FEATURE, MinVersion: nil} 81 82type GLVersion [2]int 83 84// RequirementGetter functions allows us to "iterate" over the requirements 85// of the different standards which are stored as keys in FeatureSet and 86// normally not easily iterable. 87type RequirementGetter func(FeatureSet) []Requirement 88 89func glRequirements(fs FeatureSet) []Requirement { 90 return fs.GLReqs 91} 92 93func glesRequirements(fs FeatureSet) []Requirement { 94 return fs.GLESReqs 95} 96 97func webglRequirements(fs FeatureSet) []Requirement { 98 return fs.WebGLReqs 99} 100 101// generateAssembleInterface creates one GrGLAssembleInterface_[type]_gen.cpp 102// for each of the standards 103func generateAssembleInterface(features []FeatureSet) { 104 gl := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL, features, glRequirements) 105 writeToFile(*outDir, GL_FILE_NAME, gl) 106 gles := fillAssembleTemplate(ASSEMBLE_INTERFACE_GL_ES, features, glesRequirements) 107 writeToFile(*outDir, GLES_FILE_NAME, gles) 108 webgl := fillAssembleTemplate(ASSEMBLE_INTERFACE_WEBGL, features, webglRequirements) 109 writeToFile(*outDir, WEBGL_FILE_NAME, webgl) 110} 111 112// fillAssembleTemplate returns a generated file given a template (for a single standard) 113// to fill out and a slice of features with which to fill it. getReqs is used to select 114// the requirements for the standard we are working on. 115func fillAssembleTemplate(template string, features []FeatureSet, getReqs RequirementGetter) string { 116 content := "" 117 for _, feature := range features { 118 // For each feature set, we are going to create a series of 119 // if statements, which check for the requirements (e.g. extensions, version) 120 // and inside those if branches, write the code to load the 121 // correct function pointer to the interface. GET_PROC and 122 // GET_PROC_SUFFIX are macros defined in C++ part of the template 123 // to accomplish this (for a core feature and extensions, respectively). 124 reqs := getReqs(feature) 125 if len(reqs) == 0 { 126 continue 127 } 128 // blocks holds all the if blocks generated - it will be joined with else 129 // after and appended to content 130 blocks := []string{} 131 for i, req := range reqs { 132 block := "" 133 ifExpr := requirementIfExpression(req, true) 134 135 if ifExpr != "" { 136 if strings.HasPrefix(ifExpr, "(") { 137 ifExpr = "if " + ifExpr + " {" 138 } else { 139 ifExpr = "if (" + ifExpr + ") {" 140 } 141 // Indent the first if statement 142 if i == 0 { 143 block = addLine(block, ifExpr) 144 } else { 145 block += ifExpr + "\n" 146 } 147 } 148 // sort for determinism 149 sort.Strings(feature.Functions) 150 for _, function := range feature.Functions { 151 block = assembleFunction(block, ifExpr, function, req) 152 } 153 sort.Strings(feature.TestOnlyFunctions) 154 if len(feature.TestOnlyFunctions) > 0 { 155 block += "#if GR_TEST_UTILS\n" 156 for _, function := range feature.TestOnlyFunctions { 157 block = assembleFunction(block, ifExpr, function, req) 158 } 159 block += "#endif\n" 160 } 161 162 // a hard code function does not use the C++ macro 163 for _, hcf := range feature.HardCodeFunctions { 164 if ifExpr != "" { 165 // extra tab for being in an if statement 166 block += SPACER 167 } 168 line := fmt.Sprintf(`functions->%s =(%s*)get(ctx, "%s");`, hcf.PtrName, hcf.CastName, hcf.GetName) 169 block = addLine(block, line) 170 } 171 if ifExpr != "" { 172 block += SPACER + "}" 173 } 174 blocks = append(blocks, block) 175 } 176 content += strings.Join(blocks, " else ") 177 178 if feature.Required && reqs[0] != CORE_REQUIREMENT { 179 content += ` else { 180 SkASSERT(false); // Required feature 181 return nullptr; 182 }` 183 } 184 185 if !strings.HasSuffix(content, "\n") { 186 content += "\n" 187 } 188 // Add an extra space between blocks for easier readability 189 content += "\n" 190 191 } 192 193 return strings.Replace(template, "[[content]]", content, 1) 194} 195 196// assembleFunction is a little helper that will add a function to the interface 197// using an appropriate macro (e.g. if the function is added) 198// with an extension. 199func assembleFunction(block, ifExpr, function string, req Requirement) string { 200 if ifExpr != "" { 201 // extra tab for being in an if statement 202 block += SPACER 203 } 204 suffix := deriveSuffix(req.Extension) 205 // Some ARB extensions don't have ARB suffixes because they were written 206 // for backwards compatibility simultaneous to adding them as required 207 // in a new GL version. 208 if suffix == "ARB" { 209 suffix = "" 210 } 211 if req.SuffixOverride != nil { 212 suffix = *req.SuffixOverride 213 } 214 if req.Extension == CORE_FEATURE || suffix == "" { 215 block = addLine(block, fmt.Sprintf("GET_PROC(%s);", function)) 216 } else if req.Extension != "" { 217 block = addLine(block, fmt.Sprintf("GET_PROC_SUFFIX(%s, %s);", function, suffix)) 218 } 219 return block 220} 221 222// requirementIfExpression returns a string that is an if expression 223// Notably, there is no if expression if the function is a "core" function 224// on all supported versions. 225// The expressions are wrapped in parentheses so they can be safely 226// joined together with && or ||. 227func requirementIfExpression(req Requirement, isLocal bool) string { 228 mv := req.MinVersion 229 if req == CORE_REQUIREMENT { 230 return "" 231 } 232 if req.Extension == CORE_FEATURE && mv != nil { 233 return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d))", mv[0], mv[1]) 234 } 235 extVar := "fExtensions" 236 if isLocal { 237 extVar = "extensions" 238 } 239 // We know it has an extension 240 if req.Extension != "" { 241 if mv == nil { 242 return fmt.Sprintf("%s.has(%q)", extVar, req.Extension) 243 } else { 244 return fmt.Sprintf("(glVer >= GR_GL_VER(%d,%d) && %s.has(%q))", mv[0], mv[1], extVar, req.Extension) 245 } 246 } 247 abort("ERROR: requirement must have ext") 248 return "ERROR" 249} 250 251// driveSuffix returns the suffix of the function associated with the given 252// extension. 253func deriveSuffix(ext string) string { 254 // Some extensions begin with GL_ and then have the actual 255 // extension like KHR, EXT etc. 256 ext = strings.TrimPrefix(ext, "GL_") 257 return strings.Split(ext, "_")[0] 258} 259 260// addLine is a little helper function which handles the newline and tab 261func addLine(str, line string) string { 262 return str + SPACER + line + "\n" 263} 264 265func writeToFile(parent, file, content string) { 266 p := filepath.Join(parent, file) 267 if *dryRun { 268 fmt.Printf("Writing to %s\n", p) 269 fmt.Println(content) 270 } else { 271 if err := ioutil.WriteFile(p, []byte(content), 0644); err != nil { 272 abort("Error while writing to file %s: %s", p, err) 273 } 274 } 275} 276 277// validationEntry is a helper struct that contains anything 278// necessary to make validation code for a given standard. 279type validationEntry struct { 280 StandardCheck string 281 GetReqs RequirementGetter 282} 283 284func generateValidateInterface(features []FeatureSet) { 285 standards := []validationEntry{ 286 { 287 StandardCheck: "GR_IS_GR_GL(fStandard)", 288 GetReqs: glRequirements, 289 }, { 290 StandardCheck: "GR_IS_GR_GL_ES(fStandard)", 291 GetReqs: glesRequirements, 292 }, { 293 StandardCheck: "GR_IS_GR_WEBGL(fStandard)", 294 GetReqs: webglRequirements, 295 }, 296 } 297 content := "" 298 // For each feature, we are going to generate a series of 299 // boolean expressions which check that the functions we thought 300 // were gathered during the assemble phase actually were applied to 301 // the interface (functionCheck). This check will be guarded 302 // another set of if statements (one per standard) based 303 // on the same requirements (standardChecks) that were used when 304 // assembling the interface. 305 for _, feature := range features { 306 if allReqsAreCore(feature) { 307 content += functionCheck(feature, 1) 308 } else { 309 content += SPACER 310 standardChecks := []string{} 311 for _, std := range standards { 312 reqs := std.GetReqs(feature) 313 if reqs == nil || len(reqs) == 0 { 314 continue 315 } 316 expr := []string{} 317 for _, r := range reqs { 318 e := requirementIfExpression(r, false) 319 if e != "" { 320 expr = append(expr, e) 321 } 322 } 323 check := "" 324 if len(expr) == 0 { 325 check = fmt.Sprintf("%s", std.StandardCheck) 326 } else { 327 lineBreak := "\n" + SPACER + " " 328 check = fmt.Sprintf("(%s && (%s%s))", std.StandardCheck, lineBreak, strings.Join(expr, " ||"+lineBreak)) 329 } 330 standardChecks = append(standardChecks, check) 331 } 332 content += fmt.Sprintf("if (%s) {\n", strings.Join(standardChecks, " ||\n"+SPACER+" ")) 333 content += functionCheck(feature, 2) 334 335 content += SPACER + "}\n" 336 } 337 // add additional line between each block 338 content += "\n" 339 } 340 content = strings.Replace(VALIDATE_INTERFACE, "[[content]]", content, 1) 341 writeToFile(*outDir, INTERFACE_FILE_NAME, content) 342} 343 344// functionCheck returns an if statement that checks that all functions 345// in the passed in slice are on the interface (that is, they are truthy 346// on the fFunctions struct) 347func functionCheck(feature FeatureSet, indentLevel int) string { 348 // sort for determinism 349 sort.Strings(feature.Functions) 350 indent := strings.Repeat(SPACER, indentLevel) 351 352 checks := []string{} 353 for _, function := range feature.Functions { 354 if in(function, feature.OptionalFunctions) { 355 continue 356 } 357 checks = append(checks, "!fFunctions.f"+function) 358 } 359 testOnly := []string{} 360 for _, function := range feature.TestOnlyFunctions { 361 if in(function, feature.OptionalFunctions) { 362 continue 363 } 364 testOnly = append(testOnly, "!fFunctions.f"+function) 365 } 366 for _, hcf := range feature.HardCodeFunctions { 367 checks = append(checks, "!fFunctions."+hcf.PtrName) 368 } 369 preCheck := "" 370 if len(testOnly) != 0 { 371 preCheck = fmt.Sprintf(`#if GR_TEST_UTILS 372%sif (%s) { 373%s%sRETURN_FALSE_INTERFACE; 374%s} 375#endif 376`, indent, strings.Join(testOnly, " ||\n"+indent+" "), indent, SPACER, indent) 377 } 378 379 if len(checks) == 0 { 380 return preCheck + strings.Repeat(SPACER, indentLevel) + "// all functions were marked optional or test_only\n" 381 } 382 383 return preCheck + fmt.Sprintf(`%sif (%s) { 384%s%sRETURN_FALSE_INTERFACE; 385%s} 386`, indent, strings.Join(checks, " ||\n"+indent+" "), indent, SPACER, indent) 387} 388 389// allReqsAreCore returns true iff the FeatureSet is part of "core" for 390// all standards 391func allReqsAreCore(feature FeatureSet) bool { 392 if feature.GLReqs == nil || feature.GLESReqs == nil { 393 return false 394 } 395 return feature.GLReqs[0] == CORE_REQUIREMENT && feature.GLESReqs[0] == CORE_REQUIREMENT && feature.WebGLReqs[0] == CORE_REQUIREMENT 396} 397 398func validateFeatures(features []FeatureSet) { 399 seen := map[string]bool{} 400 for _, feature := range features { 401 for _, fn := range feature.Functions { 402 if seen[fn] { 403 abort("ERROR: Duplicate function %s", fn) 404 } 405 seen[fn] = true 406 } 407 for _, fn := range feature.TestOnlyFunctions { 408 if seen[fn] { 409 abort("ERROR: Duplicate function %s\n", fn) 410 } 411 seen[fn] = true 412 } 413 } 414} 415 416// in returns true if |s| is *in* |a| slice. 417func in(s string, a []string) bool { 418 for _, x := range a { 419 if x == s { 420 return true 421 } 422 } 423 return false 424} 425 426func abort(fmtStr string, inputs ...interface{}) { 427 fmt.Printf(fmtStr+"\n", inputs...) 428 os.Exit(1) 429} 430 431func main() { 432 flag.Parse() 433 b, err := ioutil.ReadFile(*inTable) 434 if err != nil { 435 abort("Could not read file %s", err) 436 } 437 438 dir, err := os.Open(*outDir) 439 if err != nil { 440 abort("Could not write to output dir %s", err) 441 } 442 defer dir.Close() 443 if fi, err := dir.Stat(); err != nil { 444 abort("Error getting info about %s: %s", *outDir, err) 445 } else if !fi.IsDir() { 446 abort("%s must be a directory", *outDir) 447 } 448 449 features := []FeatureSet{} 450 451 err = json5.Unmarshal(b, &features) 452 if err != nil { 453 abort("Invalid JSON: %s", err) 454 } 455 456 validateFeatures(features) 457 458 generateAssembleInterface(features) 459 generateValidateInterface(features) 460} 461