| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "os" |
| "strconv" |
| "strings" |
| ) |
| |
| const indent = " " |
| |
| // Generate the "gles.api" file from combined online and hand written data. |
| func GenerateApi(reg *Registry, api KhronosAPI) { |
| files := map[string]*os.File{} |
| printEnums(createFile(files, "glenum"), reg, api, false) |
| printEnums(createFile(files, "glbitfield"), reg, api, true) |
| for _, cmd := range reg.Command { |
| versions := reg.GetVersions(api, cmd.Name()) |
| extensions := reg.GetExtensions(api, cmd.Name()) |
| if versions != nil { |
| printCommand(createFile(files, getCategory(cmd.Name())), reg, &cmd, api) |
| } |
| if extensions != nil { |
| if isInAndroidExtensionPack(extensions) { |
| printCommand(createFile(files, "android_extension_pack"), reg, &cmd, api) |
| } else { |
| printCommand(createFile(files, "extensions"), reg, &cmd, api) |
| } |
| } |
| } |
| for _, file := range files { |
| if err := file.Close(); err != nil { |
| panic(err) |
| } |
| } |
| } |
| |
| func createFile(files map[string]*os.File, basename string) *os.File { |
| filename := *apiDir + string(os.PathSeparator) + basename + ".api" |
| if file, ok := files[filename]; ok { |
| return file |
| } |
| file, err := os.Create(filename) |
| if err != nil { |
| panic(err) |
| } |
| files[filename] = file |
| fmt.Fprint(file, `// Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| `) |
| return file |
| } |
| |
| func getCategory(cmdName string) string { |
| var table = []struct { |
| category string |
| cmdName string // Regexp |
| }{ |
| {"asynchronous_queries", ".*(Query|Queries).*"}, |
| {"synchronization", ".*Sync.*"}, |
| {"programs_and_shaders", ".*(Program|Shader|Compute|Uniform|MemoryBarrier|GetFragDataLocation|glValidateProgram|AttribLocation|GetActiveAttrib).*"}, |
| {"framebuffer", ".*(Framebuffer|Renderbuffer).*|glReadBuffer|glReadPixels|gl(Color|Depth|Stencil)Mask|glClear.*|glInvalidate.*"}, |
| {"textures_and_samplers", ".*(Tex|Image|Sampler|PixelStorei|GenerateMipmap).*"}, |
| {"transform_feedback", "gl(.*TransformFeedback.*)"}, |
| {"vertex_arrays", ".*Vertex(Attrib|Buffer|Binding|Array).*"}, |
| {"draw_commands", "glDraw.*"}, |
| {"rasterization", "gl(GetMultisamplefv|LineWidth|FrontFace|CullFace|PolygonOffset|DepthRangef|Viewport)"}, |
| {"fragment_operations", "glScissor|glSample.*|glStencil.*|glDepthFunc|glBlend.*"}, |
| {"state_queries", "gl(GetBoolean|GetInteger|GetFloat|IsEnabled|GetString|GetInternalformativ).*"}, |
| {"other", "glEnable|glDisable|glIsEnabled|glHint|glGetError|glFlush|glFinish"}, |
| {"buffer_objects", ".*Buffer.*"}, |
| } |
| for _, row := range table { |
| if CompileRegexp("^(?:" + row.cmdName + ")").MatchString(cmdName) { |
| return row.category |
| } |
| } |
| panic(fmt.Errorf("Can not find category of %v", cmdName)) |
| } |
| |
| func isInAndroidExtensionPack(extensions []string) (result bool) { |
| aep := map[string]bool{ |
| "GL_KHR_debug": true, |
| "GL_KHR_texture_compression_astc_ldr": true, |
| "GL_KHR_blend_equation_advanced": true, |
| "GL_OES_sample_shading": true, |
| "GL_OES_sample_variables": true, |
| "GL_OES_shader_image_atomic": true, |
| "GL_OES_shader_multisample_interpolation": true, |
| "GL_OES_texture_stencil8": true, |
| "GL_OES_texture_storage_multisample_2d_array": true, |
| "GL_EXT_copy_image": true, |
| "GL_EXT_draw_buffers_indexed": true, |
| "GL_EXT_geometry_shader": true, |
| "GL_EXT_gpu_shader5": true, |
| "GL_EXT_primitive_bounding_box": true, |
| "GL_EXT_shader_io_blocks": true, |
| "GL_EXT_tessellation_shader": true, |
| "GL_EXT_texture_border_clamp": true, |
| "GL_EXT_texture_buffer": true, |
| "GL_EXT_texture_cube_map_array": true, |
| "GL_EXT_texture_sRGB_decode": true, |
| } |
| for _, extension := range extensions { |
| if aep[extension] { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func wrap(text string, width int, linePrefix string) string { |
| if len(text) > width { |
| if idx := strings.LastIndex(text[0:width], " "); idx != -1 { |
| return text[0:idx] + "\n" + wrap(linePrefix+text[idx+1:], width, linePrefix) |
| } |
| } |
| return text |
| } |
| |
| func printEnums(out io.Writer, r *Registry, api KhronosAPI, bitfields bool) { |
| if bitfields { |
| fmt.Fprintf(out, "bitfield GLbitfield {\n") |
| } else { |
| fmt.Fprintf(out, "enum GLenum {\n") |
| } |
| for _, enums := range r.Enums { |
| if (enums.Type == "bitmask") == bitfields && enums.Namespace == "GL" && len(enums.Enum) > 0 { |
| for _, enum := range enums.Enum { |
| // The following 64bit values are not proper GLenum values. |
| if enum.Name == "GL_TIMEOUT_IGNORED" || enum.Name == "GL_TIMEOUT_IGNORED_APPLE" { |
| continue |
| } |
| if enum.API == "" || enum.API == api { |
| var value uint32 |
| if v, err := strconv.ParseUint(enum.Value, 0, 32); err == nil { |
| value = uint32(v) |
| } else if v, err := strconv.ParseInt(enum.Value, 0, 32); err == nil { |
| value = uint32(v) |
| } else { |
| panic(fmt.Errorf("Failed to parse enum value %v", enum.Value)) |
| } |
| fmt.Fprintf(out, indent+"%-64s = 0x%08X,\n", enum.Name, value) |
| } |
| } |
| } |
| } |
| fmt.Fprintf(out, "}\n\n") |
| } |
| |
| func renameType(cmdName string, paramIndex int, paramType, paramName string) string { |
| if paramType == "const void *" { |
| if oldType, ok := oldParamType(cmdName, paramIndex); ok { |
| if strings.Contains(oldType, "Pointer") { |
| return oldType |
| } |
| } |
| } |
| if paramType == "const GLchar *" { |
| if oldType, ok := oldParamType(cmdName, paramIndex); ok { |
| if oldType == "string" { |
| return oldType |
| } |
| } |
| } |
| |
| // Find the root GL type, ignoring any prefix or sufix |
| loc := CompileRegexp(`\bGL\w+\b`).FindStringIndex(paramType) |
| if loc == nil { |
| return paramType |
| } |
| glType := paramType[loc[0]:loc[1]] |
| |
| // Type substitution table |
| table := []struct { |
| oldType string |
| cmdName string // regexp |
| paramName string // regexp |
| newType string |
| }{ |
| {"GLboolean", "glIs.*", "result", "bool"}, |
| {"GLint", ".*Uniform.*", "location|result", "UniformLocation"}, |
| {"GLuint", ".*", "program(|s)", "ProgramId"}, |
| {"GLuint", ".*", "texture(|s)", "TextureId"}, |
| {"GLuint", ".*", "uniformBlockIndex", "UniformBlockId"}, |
| {"GLuint", ".*(Query|Queries).*", "id(|s)", "QueryId"}, |
| {"GLuint", ".*Buffer.*", "buffer(|s)", "BufferId"}, |
| {"GLuint", ".*Framebuffer.*", "framebuffer(|s)", "FramebufferId"}, |
| {"GLuint", ".*Program.*", "pipeline(|s)", "PipelineId"}, |
| {"GLuint", ".*Renderbuffer.*", "renderbuffer(|s)", "RenderbufferId"}, |
| {"GLuint", ".*Sampler.*", "sampler(|s)", "SamplerId"}, |
| {"GLuint", ".*Shader.*", "shader(|s)", "ShaderId"}, |
| {"GLuint", ".*TransformFeedback.*", "id(|s)", "TransformFeedbackId"}, |
| {"GLuint", ".*VertexArray.*", "array(|s)", "VertexArrayId"}, |
| {"GLuint", "glCreateProgram", "result", "ProgramId"}, |
| {"GLuint", "glCreateShader", "result", "ShaderId"}, |
| {"GLuint", "glCreateShaderProgramv", "result", "ProgramId"}, |
| {"GLuint", "glGetUniformBlockIndex", "result", "UniformBlockId"}, |
| {"GLuint", ".*Attrib.*", "result|index", "AttributeLocation"}, |
| } |
| for _, row := range table { |
| if row.oldType == glType && |
| CompileRegexp("^(?:"+row.paramName+")$").MatchString(paramName) && |
| CompileRegexp("^(?:"+row.cmdName+")$").MatchString(cmdName) { |
| // Replacte the GL type, but keep the original prefix and sufix |
| return paramType[:loc[0]] + row.newType + paramType[loc[1]:] |
| } |
| } |
| return paramType |
| } |
| |
| func printChecks(out io.Writer, cmd *Command, versions []Version) { |
| if versions == nil { |
| return |
| } |
| fmt.Fprintf(out, indent+"minRequiredVersion(%v)\n", strings.Replace(string(versions[0]), ".", ", ", -1)) |
| for paramIndex, param := range cmd.Param { |
| paramName := oldParamName(cmd.Name(), paramIndex, param.Name) |
| if param.Type() == "GLenum" { |
| fmt.Fprintf(out, indent+"switch (%s) {\n", paramName) |
| seen := map[string]Version{} |
| for _, version := range versions { |
| doc := DownloadDoc(version, cmd.Name()) |
| var newCases []string |
| for _, value := range doc.Params[paramIndex].Accepts { |
| if _, ok := seen[value]; !ok { |
| newCases = append(newCases, value) |
| } |
| seen[value] = version |
| } |
| // Check that the set of accepted values is increasing from verions to version. |
| for k, v := range seen { |
| if v != version { |
| fmt.Fprintf(out, indent+indent+"// TODO: %s seems to be removed in %v\n", k, version) |
| } |
| } |
| if newCases != nil { |
| line := fmt.Sprintf(indent+indent+"case %s: {\n", strings.Join(newCases, ", ")) |
| fmt.Fprint(out, wrap(line, 100, indent+indent+indent+indent)) |
| if version != versions[0] { |
| fmt.Fprintf(out, indent+indent+indent+"minRequiredVersion(%v)\n", strings.Replace(string(version), ".", ", ", -1)) |
| } else { |
| fmt.Fprintf(out, indent+indent+indent+"// version %v\n", version) |
| } |
| fmt.Fprint(out, indent+indent+"}\n") |
| } |
| } |
| fmt.Fprint(out, indent+indent+"default: {\n") |
| fmt.Fprintf(out, indent+indent+indent+"glErrorInvalidEnum(%s)\n", paramName) |
| fmt.Fprint(out, indent+indent+"}\n") |
| fmt.Fprintf(out, indent+"}\n") |
| } |
| if param.Type() == "GLbitfield" { |
| bits := []string{} |
| seen := map[string]Version{} // name -> min_version |
| for _, version := range versions { |
| doc := DownloadDoc(version, cmd.Name()) |
| for _, value := range doc.Params[paramIndex].Accepts { |
| if _, ok := seen[value]; !ok { |
| bits = append(bits, value) |
| seen[value] = version |
| } |
| } |
| } |
| if len(bits) > 0 { |
| fmt.Fprintf(out, indent+"supportsBits(%s, %s)\n", paramName, strings.Join(bits, "|")) |
| for _, bit := range bits { |
| version := seen[bit] |
| fmt.Fprintf(out, indent+"if (%s in %s) {\n", bit, paramName) |
| if version != versions[0] { |
| fmt.Fprintf(out, indent+indent+"minRequiredVersion(%v)\n", strings.Replace(string(version), ".", ", ", -1)) |
| } |
| fmt.Fprintf(out, indent+"}\n") |
| } |
| } |
| } |
| } |
| } |
| |
| func printCommand(out io.Writer, reg *Registry, cmd *Command, api KhronosAPI) { |
| versions := reg.GetVersions(api, cmd.Name()) |
| extensions := reg.GetExtensions(api, cmd.Name()) |
| if versions != nil && extensions != nil { |
| panic(fmt.Errorf("%s is both core feature and extension", cmd.Name())) |
| } |
| |
| // Print comments |
| for _, version := range versions { |
| url, _, _, _ := GetDocUrl(version, cmd.Name()) |
| fmt.Fprintf(out, `@Doc("%s","OpenGL ES %v")`+"\n", url, version) |
| } |
| for _, extension := range extensions { |
| url := GetExtensionUrl(extension) |
| fmt.Fprintf(out, `@Doc("%s","%v")`+"\n", url, extension) |
| } |
| |
| // Print function signature |
| sig := &bytes.Buffer{} |
| fmt.Fprintf(sig, "cmd %s %s(", renameType(cmd.Name(), -1, cmd.Proto.Type(), "result"), cmd.Name()) |
| leadWidth := sig.Len() |
| maxTypeWidth := 0 |
| paramTypes := []string{} |
| for paramIndex, param := range cmd.Param { |
| paramType := renameType(cmd.Name(), paramIndex, param.Type(), param.Name) |
| paramTypes = append(paramTypes, paramType) |
| if typeWidth := len(paramType); typeWidth > maxTypeWidth { |
| maxTypeWidth = typeWidth |
| } |
| } |
| for i, param := range cmd.Param { |
| if i > 0 { |
| fmt.Fprintf(sig, ",\n%s", strings.Repeat(" ", leadWidth)) |
| } |
| format := fmt.Sprintf("%%-%vs %%s", maxTypeWidth) |
| fmt.Fprintf(sig, format, paramTypes[i], oldParamName(cmd.Name(), i, param.Name)) |
| if oldType, ok := oldParamType(cmd.Name(), i); ok { |
| newType := strings.Replace(paramTypes[i], " *", "*", -1) |
| if newType != oldType { |
| // fmt.Printf("Changed type of %s:%s from %s to %s\n", cmd.Name(), param.Name, oldType, newType) |
| } |
| } |
| } |
| fmt.Fprintf(sig, ")") |
| longSig := string(sig.Bytes()) |
| shortSig := CompileRegexp(`\s+`).ReplaceAllString(longSig, " ") |
| if len(shortSig)+2 <= 100 { |
| fmt.Fprintf(out, "%s ", shortSig) |
| } else { |
| fmt.Fprintf(out, "%s ", longSig) |
| } |
| |
| // Print function body |
| fmt.Fprintf(out, "{\n") |
| for _, extension := range extensions { |
| fmt.Fprintf(out, indent+"requiresExtension(%s)\n", extension) |
| } |
| if len(extensions) > 1 { |
| fmt.Fprintf(out, indent+"// TODO: Multiple extensions\n") |
| } |
| printChecks(out, cmd, versions) |
| foundOldCode := false |
| if oldApi != nil { |
| // Try to add the old code |
| for _, apiCmd := range oldApi.Commands { |
| if apiCmd.Name.Value == cmd.Name() { |
| childs := apiCmd.Block.CST.Children |
| if len(childs) > 0 { |
| start := apiCmd.Block.CST.Children[0].Token() |
| end := apiCmd.Block.CST.Children[len(childs)-1].Token() |
| fmt.Fprintf(out, "%s", string(start.Runes[start.End:end.Start])) |
| foundOldCode = true |
| } |
| } |
| } |
| } |
| if !foundOldCode { |
| fmt.Fprintf(out, indent+"// TODO\n") |
| if cmd.Proto.Type() != "void" { |
| fmt.Fprint(out, indent+"return ?\n") |
| } |
| } |
| fmt.Fprintf(out, "}\n") |
| fmt.Fprintf(out, "\n") |
| } |
| |
| // Rename parameters so that they match our old api file |
| func oldParamName(cmdName string, paramIndex int, paramName string) string { |
| if oldApi != nil { |
| for _, cmd := range oldApi.Commands { |
| if cmd.Name.Value == cmdName { |
| return cmd.Parameters[paramIndex].Name.Value |
| } |
| } |
| } |
| return paramName |
| } |
| |
| func oldParamType(cmdName string, paramIndex int) (string, bool) { |
| if oldApi != nil { |
| for _, cmd := range oldApi.Commands { |
| if cmd.Name.Value == cmdName { |
| token := cmd.Parameters[paramIndex].Type.Node().Token() |
| return string(token.Runes[token.Start:token.End]), true |
| } |
| } |
| } |
| return "", false |
| } |