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