Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Cgo dependency on Windows #102

Closed
wants to merge 12 commits into from
33 changes: 32 additions & 1 deletion functions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import "fmt"
import (
"fmt"
"strings"
)

// A Function definition.
type Function struct {
Expand All @@ -10,6 +13,34 @@ type Function struct {
Return Type
}

// IsImplementedForSyscall returns a boolean value indicating whether the function is implemented for syscall or not.
hajimehoshi marked this conversation as resolved.
Show resolved Hide resolved
func (f Function) IsImplementedForSyscall() bool {
// As there is no syscall.Syscall18 or more, more than 15 arguments cannot be accepted.
// See https://github.com/golang/go/issues/28434.
hajimehoshi marked this conversation as resolved.
Show resolved Hide resolved
if len(f.Parameters) > 15 {
return false
}
return true
}

// Syscall returns a syscall expression for Windows.
func (f Function) Syscall() string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this would be more idiomatic/clear if it returned an error when !IsImplementedForSyscall (rather than having a separate func that needs to be called)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called at templates to generate Syscall calling, then returning error here would not make sense.

var ps []string
for _, p := range f.Parameters {
ps = append(ps, p.Type.ConvertGoToUintptr(p.GoName()))
}
for len(ps) == 0 || len(ps)%3 != 0 {
ps = append(ps, "0")
}

post := ""
if len(ps) > 3 {
post = fmt.Sprintf("%d", len(ps))
}

return fmt.Sprintf("syscall.Syscall%s(gp%s, %d, %s)", post, f.GoName, len(f.Parameters), strings.Join(ps, ", "))
}

// A Parameter to a Function.
type Parameter struct {
Name string
Expand Down
16 changes: 14 additions & 2 deletions package.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,26 @@ func (pkg *Package) GeneratePackage(dir string) error {
if err := pkg.generateFile("package", dir); err != nil {
return err
}
if err := pkg.generateFile("package_notwindows", dir); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having all these files is super unfortunate, but I'm not sure whether there is anything to be done for it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps something like:

for _, v := range []string{"package", "package_notwindows", "package_windows",
  "conversions", "conversions_notwindows", "conversions_windows",
  "procaddr_notwindows", "procaddr_windows"} {
  if err := pkg.generateFile(v, dir); err != nil {
	  return err
  }
}

return err
}
if err := pkg.generateFile("package_windows", dir); err != nil {
return err
}
if err := pkg.generateFile("conversions", dir); err != nil {
return err
}
if err := pkg.generateFile("procaddr", dir); err != nil {
if err := pkg.generateFile("procaddr_notwindows", dir); err != nil {
return err
}
if err := pkg.generateFile("procaddr_windows", dir); err != nil {
return err
}
if pkg.HasDebugCallbackFeature() {
if err := pkg.generateFile("debug", dir); err != nil {
if err := pkg.generateFile("debug_notwindows", dir); err != nil {
return err
}
if err := pkg.generateFile("debug_windows", dir); err != nil {
return err
}
}
Expand Down
44 changes: 21 additions & 23 deletions tmpl/conversions.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ package {{.Name}}
import (
"fmt"
"reflect"
"runtime"
"strings"
"unsafe"
)

// #include <stdlib.h>
import "C"

// Ptr takes a slice or pointer (to a singular scalar value or the first
// element of an array or slice) and returns its GL-compatible address.
//
Expand Down Expand Up @@ -70,7 +68,15 @@ func Str(str string) *uint8 {
// GoStr takes a null-terminated string returned by OpenGL and constructs a
// corresponding Go string.
func GoStr(cstr *uint8) string {
return C.GoString((*C.char)(unsafe.Pointer(cstr)))
str := ""
for {
if *cstr == 0 {
break
}
str += string(*cstr)
cstr = (*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(cstr)) + 1))
}
return str
}

// Strs takes a list of Go strings (with or without null-termination) and
Expand All @@ -85,26 +91,18 @@ func Strs(strs ...string) (cstrs **uint8, free func()) {
panic("Strs: expected at least 1 string")
}

// Allocate a contiguous array large enough to hold all the strings' contents.
n := 0
for i := range strs {
n += len(strs[i])
var pinned []string
var ptrs []*uint8
hajimehoshi marked this conversation as resolved.
Show resolved Hide resolved
for _, str := range strs {
if !strings.HasSuffix(str, "\x00") {
str += "\x00"
}
pinned = append(pinned, str)
ptrs = append(ptrs, Str(str))
}
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,
}))
css := make([]*uint8, len(strs)) // Populated with pointers to each string.
offset := 0
for i := range strs {
copy(dataSlice[offset:offset+len(strs[i])], strs[i][:]) // Copy strs[i] into proper data location.
css[i] = (*uint8)(unsafe.Pointer(&dataSlice[offset])) // Set a pointer to it.
offset += len(strs[i])
return &ptrs[0], func() {
runtime.KeepAlive(pinned)
pinned = nil
}

return (**uint8)(&css[0]), func() { C.free(data) }
}
2 changes: 2 additions & 0 deletions tmpl/debug.tmpl → tmpl/debug_notwindows.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//glow:keepspace
// +build !windows

// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.

package {{.Name}}
Expand Down
16 changes: 16 additions & 0 deletions tmpl/debug_windows.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//glow:keepspace
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.

package {{.Name}}
//glow:rmspace

import "unsafe"

type DebugProc func(
source uint32,
gltype uint32,
id uint32,
severity uint32,
length int32,
message string,
userParam unsafe.Pointer)
104 changes: 3 additions & 101 deletions tmpl/package.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,95 +18,12 @@
package {{.Name}}
//glow:rmspace

{{define "paramsCDecl"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.Type.CType}} {{$p.CName}}{{end}}{{end}}
{{define "paramsCCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{if $p.Type.IsDebugProc}}glowCDebugCallback{{else}}{{$p.CName}}{{end}}{{end}}{{end}}

{{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 linux freebsd LDFLAGS: -lGL
// #cgo windows LDFLAGS: -lopengl32
//
// #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
// #ifndef WIN32_LEAN_AND_MEAN
// #define WIN32_LEAN_AND_MEAN 1
// #endif
// #include <windows.h>
// #endif
//
// #ifndef APIENTRY
// #define APIENTRY
// #endif
// #ifndef APIENTRYP
// #define APIENTRYP APIENTRY *
// #endif
// #ifndef GLAPI
// #define GLAPI extern
// #endif
//
// {{range .Typedefs}}
// {{replace .CTypedef "\n" "\n// " -1}}
// {{end}}
//
// {{if .HasDebugCallbackFeature}}
// extern void glowDebugCallback_{{.UniqueName}}(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam);
// static void APIENTRY glowCDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
// glowDebugCallback_{{.UniqueName}}(source, type, id, severity, length, message, userParam);
// }
// {{end}}
//
// {{range .Functions}}
// typedef {{.Return.CType}} (APIENTRYP GP{{toUpper .GoName}})({{template "paramsCDecl" .Parameters}});
// {{end}}
//
// {{range .Functions}}
// static {{.Return.CType}} glow{{.GoName}}(GP{{toUpper .GoName}} fnptr{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsCDecl" .Parameters}}) {
// {{if not .Return.IsVoid}}return {{end}}(*fnptr)({{template "paramsCCall" .Parameters}});
// }
// {{end}}
//
import "C"
import (
{{if .HasRequiredFunctions}}
"errors"
{{end}}
"unsafe"
)

const (
{{range .Enums}}
{{.GoName}} = {{.Value}}
{{end}}
)

var (
{{range .Functions}}
gp{{.GoName}} C.GP{{toUpper .GoName}}
{{end}}
{{range .Enums}}
{{.GoName}} = {{.Value}}
{{end}}
)

// Helper functions
func boolToInt(b bool) int {
if b { return 1 }
return 0
}

{{define "bridgeCall"}}C.glow{{.GoName}}(gp{{.GoName}}{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsGoCall" .Parameters}}){{end}}
{{range .Functions}}
{{.Comment}}
func {{.GoName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid}} {{.Return.GoType}}{{end}} {
{{range .Parameters}}
{{if .Type.IsDebugProc}}userDebugCallback = {{.GoName}}{{end}}
{{end}}
{{if .Return.IsVoid}}{{template "bridgeCall" .}}
{{else}}
ret := {{template "bridgeCall" .}}
return {{.Return.ConvertCToGo "ret"}}
{{end}}
}
{{end}}

//glow:keepspace
// Init initializes the OpenGL bindings by loading the function pointers (for
// each OpenGL function) from the active OpenGL context.
Expand All @@ -131,18 +48,3 @@ func {{.GoName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid
func Init() error {
return InitWithProcAddrFunc(getProcAddress)
}

// InitWithProcAddrFunc intializes the package using the specified OpenGL
// function pointer loading function. For more cases Init should be used
// instead.
func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
{{range .Functions}}
gp{{.GoName}} = (C.GP{{toUpper .GoName}})(getProcAddr("{{.Name}}"))
{{if .Required}}
if gp{{.GoName}} == nil {
return errors.New("{{.Name}}")
}
{{end}}
{{end}}
return nil
}
106 changes: 106 additions & 0 deletions tmpl/package_notwindows.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//glow:keepspace
// +build !windows

// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.

package {{.Name}}
//glow:rmspace

{{define "paramsCDecl"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.Type.CType}} {{$p.CName}}{{end}}{{end}}
{{define "paramsCCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{if $p.Type.IsDebugProc}}glowCDebugCallback{{else}}{{$p.CName}}{{end}}{{end}}{{end}}

{{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 linux freebsd LDFLAGS: -lGL
// #cgo windows LDFLAGS: -lopengl32
//
// #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

presumably these windows-specific bits are no longer necessary in the not-windows files?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// #ifndef WIN32_LEAN_AND_MEAN
// #define WIN32_LEAN_AND_MEAN 1
// #endif
// #include <windows.h>
// #endif
//
// #ifndef APIENTRY
// #define APIENTRY
// #endif
// #ifndef APIENTRYP
// #define APIENTRYP APIENTRY *
// #endif
// #ifndef GLAPI
// #define GLAPI extern
// #endif
//
// {{range .Typedefs}}
// {{replace .CTypedef "\n" "\n// " -1}}
// {{end}}
//
// {{if .HasDebugCallbackFeature}}
// extern void glowDebugCallback_{{.UniqueName}}(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam);
// static void APIENTRY glowCDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
// glowDebugCallback_{{.UniqueName}}(source, type, id, severity, length, message, userParam);
// }
// {{end}}
//
// {{range .Functions}}
// typedef {{.Return.CType}} (APIENTRYP GP{{toUpper .GoName}})({{template "paramsCDecl" .Parameters}});
// {{end}}
//
// {{range .Functions}}
// static {{.Return.CType}} glow{{.GoName}}(GP{{toUpper .GoName}} fnptr{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsCDecl" .Parameters}}) {
// {{if not .Return.IsVoid}}return {{end}}(*fnptr)({{template "paramsCCall" .Parameters}});
// }
// {{end}}
//
import "C"
import (
{{if .HasRequiredFunctions}}
"errors"
{{end}}
"unsafe"
)

var (
{{range .Functions}}
gp{{.GoName}} C.GP{{toUpper .GoName}}
{{end}}
)

// Helper functions
func boolToInt(b bool) int {
if b { return 1 }
return 0
}

{{define "bridgeCall"}}C.glow{{.GoName}}(gp{{.GoName}}{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsGoCall" .Parameters}}){{end}}
{{range .Functions}}
{{.Comment}}
func {{.GoName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid}} {{.Return.GoType}}{{end}} {
{{range .Parameters}}
{{if .Type.IsDebugProc}}userDebugCallback = {{.GoName}}{{end}}
{{end}}
{{if .Return.IsVoid}}{{template "bridgeCall" .}}
{{else}}
ret := {{template "bridgeCall" .}}
return {{.Return.ConvertCToGo "ret"}}
{{end}}
}
{{end}}


// InitWithProcAddrFunc intializes the package using the specified OpenGL
// function pointer loading function. For more cases Init should be used
// instead.
func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
{{range .Functions}}
gp{{.GoName}} = (C.GP{{toUpper .GoName}})(getProcAddr("{{.Name}}"))
{{if .Required}}
if gp{{.GoName}} == nil {
return errors.New("{{.Name}}")
}
{{end}}
{{end}}
return nil
}
Loading