• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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