Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
Glow
====
# Glow

Glow is an OpenGL binding generator for Go. Glow parses the [OpenGL XML API registry](https://github.com/KhronosGroup/OpenGL-Registry/tree/master/xml) and the [EGL XML API registry](https://github.com/KhronosGroup/EGL-Registry/tree/master/api) to produce a machine-generated cgo bridge between Go functions and native OpenGL functions. Glow is a fork of [GoGL2](https://github.com/chsc/gogl2).

Features:

- Go functions that mirror the C specification using Go types.
- Support for multiple OpenGL APIs (GL/GLES/EGL/WGL/GLX/EGL), versions, and profiles.
- Support for extensions (including debug callbacks).
- Support for overloads to provide Go functions with different parameter signatures.

See the [open issues](https://github.com/go-gl/glow/issues) for caveats about the current state of the implementation.

Generated Packages
------------------
## Generated Packages

Generated OpenGL binding packages are available in the [go-gl/gl](https://github.com/go-gl/gl) repository.

Overloads
---------
## Overloads

See subdirectory `xml/overload` for examples. The motivation here is to provide Go functions with different parameter signatures of existing OpenGL functions.

For example, `glVertexAttribPointer(..., void *)` cannot be used with `gl.VertexAttribPointer(..., unsafe.Pointer)` when using arbitrary offset values. The `checkptr` safeguard will abort the program when doing so.
Overloads allow the creation of an additional `gl.VertexAttribPointerWithOffset(..., uintptr)`, which calls the original OpenGL function with appropriate casts.

Overloads allow the creation of an additional `gl.VertexAttribPointerWithOffset(..., uintptr)`, which calls the original OpenGL function with appropriate casts.

Custom Packages
---------------
## Custom Packages

If the prebuilt, go-gettable packages are not suitable for your needs you can build your own. For example,

Expand All @@ -37,9 +33,10 @@ If the prebuilt, go-gettable packages are not suitable for your needs you can bu
./glow generate -api=gl -version=3.3 -profile=core -remext=GL_ARB_cl_event
go install ./gl-core/3.3/gl

**NOTE:** You will have to provide your GitHub account credentials to update the XML specification files.
**NOTE:** You will have to provide a GitHub token ([personal access or OAuth2 token](https://developer.github.com/v3/auth/#basic-authentication)) to update the XML specification files.

A few notes about the flags to `generate`:

- `api`: One of `gl`, `gles1`, `gles2`, `egl`, `wgl`, or `glx`.
- `version`: The API version to generate. The `all` pseudo-version includes all functions and enumerations for the specified API.
- `profile`: For `gl` packages with version 3.2 or higher, `core` or `compatibility` ([explanation](http://www.opengl.org/wiki/Core_And_Compatibility_in_Contexts)).
Expand Down
32 changes: 22 additions & 10 deletions download.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"sync"
)

Expand Down Expand Up @@ -53,26 +52,30 @@ var specRegexp = regexp.MustCompile(`^(gl|glx|wgl)\.xml$`)
var eglRepoName = "EGL-Registry"
var eglRepoFolder = "api"
var eglRegexp = regexp.MustCompile(`^(egl)\.xml$`)
var khrRepoName = "EGL-Registry"
var khrRepoFolder = "api/KHR"
var khrRegexp = regexp.MustCompile(`^.*\.h$`)
var docRepoName = "OpenGL-Refpages"
var docRepoFolders = []string{
"es1.1",
"es2.0",
"es3.0",
"es3.1",
"es3.2",
"es3",
"gl2.1",
"gl4",
}
var docRegexp = regexp.MustCompile(`^[ew]?gl[^u_].*\.xml$`)

func validatedAuthHeader(username string, password string) (string, error) {
func validatedAuthHeader(token string) (string, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
return "", err
}

autStr := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))))
autStr := fmt.Sprintf("token %s", token)
req.Header.Add("Authorization", autStr)
resp, err := client.Do(req)
if err != nil {
Expand All @@ -82,7 +85,8 @@ func validatedAuthHeader(username string, password string) (string, error) {
defer resp.Body.Close()

if resp.StatusCode != 200 {
return "", errors.New("GitHub authorization failed")
data, _ := ioutil.ReadAll(resp.Body)
return "", errors.New(string(data))
}

return autStr, nil
Expand All @@ -103,15 +107,18 @@ func download(name string, args []string) {
log.Fatalln("error creating documentation output directory:", err)
}

khrDir := filepath.Join(*xmlDir, "include", "KHR")
if err := os.MkdirAll(khrDir, 0755); err != nil {
log.Fatalln("error creating include KHR output directory:", err)
}

reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter GitHub username: ")
fmt.Print("Enter GitHub token: ")
input, _ := reader.ReadString('\n')
username := strings.Trim(input, "\n")
fmt.Print("Enter GitHub password: ")
input, _ = reader.ReadString('\n')
password := strings.Trim(input, "\n")
re := regexp.MustCompile("\r?\n")
token := re.ReplaceAllString(input, "")

authHeader, err := validatedAuthHeader(username, password)
authHeader, err := validatedAuthHeader(token)
if err != nil {
log.Fatalln("error with user authorization:", err)
}
Expand All @@ -126,6 +133,11 @@ func download(name string, args []string) {
log.Fatalln("error downloading egl file:", err)
}

err = DownloadGitDir(authHeader, khrRepoName, khrRepoFolder, khrRegexp, khrDir)
if err != nil {
log.Fatalln("error downloading include KHR files:", err)
}

for _, folder := range docRepoFolders {
if err := DownloadGitDir(authHeader, docRepoName, folder, docRegexp, docDir); err != nil {
log.Fatalln("error downloading documentation files:", err)
Expand Down
69 changes: 66 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
Expand All @@ -15,10 +16,10 @@ import (

func generate(name string, args []string) {
flags := flag.NewFlagSet(name, flag.ExitOnError)
dir := importPathToDir("github.com/go-gl/glow")
glowBaseDir := determineGlowBaseDir()
var (
xmlDir = flags.String("xml", filepath.Join(dir, "xml"), "XML directory")
tmplDir = flags.String("tmpl", filepath.Join(dir, "tmpl"), "Template directory")
xmlDir = flags.String("xml", filepath.Join(glowBaseDir, "xml"), "XML directory")
tmplDir = flags.String("tmpl", filepath.Join(glowBaseDir, "tmpl"), "Template directory")
outDir = flags.String("out", "gl", "Output directory")
api = flags.String("api", "", "API to generate (e.g., gl)")
ver = flags.String("version", "", "API version to generate (e.g., 4.1)")
Expand Down Expand Up @@ -75,6 +76,9 @@ func generate(name string, args []string) {
if err := pkg.GeneratePackage(*outDir); err != nil {
log.Fatalln("error generating package:", err)
}
if err := copyIncludes(filepath.Join(*xmlDir, "include"), *outDir); err != nil {
log.Fatalln("error copying includes:", err)
}
break
}
}
Expand All @@ -84,6 +88,21 @@ func generate(name string, args []string) {
log.Println("generated package in", *outDir)
}

// Attempt to determine the base directory of go-gl/glow. This only works in case of non-module-aware
// cases and acts as a backwards compatible way.
//
// In a module-only case, this function returns the current working directory.
func determineGlowBaseDir() string {
glowBaseDir, err := importPathToDir("github.com/go-gl/glow")
if err != nil {
glowBaseDir, err = os.Getwd()
}
if err != nil {
return "."
}
return glowBaseDir
}

// Converts a slice string into a simple lookup map.
func lookupMap(s []string) map[string]bool {
lookup := make(map[string]bool, len(s))
Expand Down Expand Up @@ -164,6 +183,50 @@ func parseDocumentation(xmlDir string) Documentation {
return doc
}

func copyIncludes(srcDir, dstDir string) error {
files, err := ioutil.ReadDir(srcDir)
if err != nil {
return err
}
for _, file := range files {
srcName := filepath.Join(srcDir, file.Name())
dstName := filepath.Join(dstDir, file.Name())
switch {
case file.IsDir():
if err := os.MkdirAll(dstName, 0755); err != nil {
return err
}
err := copyIncludes(srcName, dstName)
if err != nil {
return err
}
case file.Size() > 0:
err := copyFile(srcName, dstName)
if err != nil {
return err
}
}
}
return nil
}

func copyFile(srcFile, dstFile string) error {
out, err := os.Create(dstFile)
if err != nil {
return err
}
defer out.Close()

in, err := os.Open(srcFile)
if err != nil {
return err
}
defer in.Close()

_, err = io.Copy(out, in)
return err
}

// PackageSpec describes a package to be generated.
type PackageSpec struct {
API string
Expand Down
14 changes: 8 additions & 6 deletions package.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package main

import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -36,7 +36,7 @@ type PackageFunction struct {
func (f *PackageFunction) Comment() string {
var lines []string
if f.Doc != "" {
lines = append(lines, "// " + f.Doc)
lines = append(lines, "// "+f.Doc)
}

// Adds explanations about C types that are unsafe.Pointer in Go world.
Expand Down Expand Up @@ -158,11 +158,13 @@ func (pkg *Package) Filter(enums, functions map[string]bool) {

// importPathToDir resolves the absolute path from importPath.
// There needs to be a valid Go package inside that import path.
// It calls log.Fatalln if it fails.
func importPathToDir(importPath string) string {
func importPathToDir(importPath string) (string, error) {
pkgs, err := packages.Load(nil, importPath)
if err != nil {
log.Fatalln(err)
return "", err
}
return filepath.Dir(pkgs[0].GoFiles[0])
if len(pkgs[0].GoFiles) == 0 {
return "", errors.New("no Go file available")
}
return filepath.Dir(pkgs[0].GoFiles[0]), nil
}
23 changes: 15 additions & 8 deletions tmpl/conversions.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func Ptr(data interface{}) unsafe.Pointer {
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
}
case reflect.Uintptr:
addr = unsafe.Pointer(v.Pointer())
addr = unsafe.Pointer(data.(uintptr))
case reflect.Slice:
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
default:
Expand All @@ -50,8 +50,16 @@ func Ptr(data interface{}) unsafe.Pointer {
}

// PtrOffset takes a pointer offset and returns a GL-compatible pointer.
// Useful for functions such as glVertexAttribPointer that take pointer
// parameters indicating an offset rather than an absolute memory address.
// Originally intended for functions such as glVertexAttribPointer that take pointer
// parameters also for offsets, since Go 1.14 this is no longer recommended.
//
// Use a corresponding offset-compatible variant of the function instead.
// For example, for gl.VertexAttribPointer() there is gl.VertexAttribPointerWithOffset().
//
// See https://github.com/go-gl/gl#go-114-and-checkptr for more details on the checkptr detector.
// See https://github.com/go-gl/glow#overloads, about adding new overloads.
//
// Deprecated: Use more appropriate overload function instead
func PtrOffset(offset int) unsafe.Pointer {
return unsafe.Pointer(uintptr(offset))
}
Expand Down Expand Up @@ -90,14 +98,13 @@ func Strs(strs ...string) (cstrs **uint8, free func()) {
for i := range strs {
n += len(strs[i])
}
if n == 0 {
n = 1 // avoid allocating zero bytes in case all strings are empty.
}
data := C.malloc(C.size_t(n))

// Copy all the strings into data.
dataSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(data),
Len: n,
Cap: n,
}))
dataSlice := (*[1 << 30]byte)(data)[:n]
css := make([]*uint8, len(strs)) // Populated with pointers to each string.
offset := 0
for i := range strs {
Expand Down
10 changes: 6 additions & 4 deletions tmpl/package.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ package {{.Name}}
{{define "paramsGoDecl"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.GoName}} {{$p.Type.GoType}}{{end}}{{end}}
{{define "paramsGoCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.Type.ConvertGoToC $p.GoName}}{{end}}{{end}}

// #cgo darwin LDFLAGS: -framework OpenGL
// #cgo windows LDFLAGS: -lopengl32
// #cgo !gles2,darwin LDFLAGS: -framework OpenGL
// #cgo gles2,darwin LDFLAGS: -framework OpenGLES
// #cgo !gles2,windows LDFLAGS: -lopengl32
// #cgo gles2,windows LDFLAGS: -lGLESv2
//
// #cgo !egl,linux !egl,freebsd pkg-config: gl
// #cgo egl,linux egl,freebsd pkg-config: egl
// #cgo !egl,linux !egl,freebsd !egl,netbsd !egl,openbsd pkg-config: gl
// #cgo egl,linux egl,freebsd egl,netbsd egl,openbsd pkg-config: egl
//
// #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
// #ifndef WIN32_LEAN_AND_MEAN
Expand Down
Loading