blob: 6eb94a56055b5243f2d7340fe7fa2110ab42e99c [file] [log] [blame]
// 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)
}