mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 08:55:35 +00:00
gpu: expose the rendering implementation
The rendering implementation is needed for using Gio UI with external window libraries such as GLFW. Expose it in the new package gpu. Updates #26 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+157
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Attrib uint
|
||||
Enum uint
|
||||
)
|
||||
|
||||
const (
|
||||
ARRAY_BUFFER = 0x8892
|
||||
BLEND = 0xbe2
|
||||
CLAMP_TO_EDGE = 0x812f
|
||||
COLOR_ATTACHMENT0 = 0x8ce0
|
||||
COLOR_BUFFER_BIT = 0x4000
|
||||
COMPILE_STATUS = 0x8b81
|
||||
DEPTH_BUFFER_BIT = 0x100
|
||||
DEPTH_ATTACHMENT = 0x8d00
|
||||
DEPTH_COMPONENT16 = 0x81a5
|
||||
DEPTH_TEST = 0xb71
|
||||
DST_COLOR = 0x306
|
||||
ELEMENT_ARRAY_BUFFER = 0x8893
|
||||
EXTENSIONS = 0x1f03
|
||||
FLOAT = 0x1406
|
||||
FRAGMENT_SHADER = 0x8b30
|
||||
FRAMEBUFFER = 0x8d40
|
||||
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
|
||||
FRAMEBUFFER_BINDING = 0x8ca6
|
||||
FRAMEBUFFER_COMPLETE = 0x8cd5
|
||||
HALF_FLOAT = 0x140b
|
||||
HALF_FLOAT_OES = 0x8d61
|
||||
INFO_LOG_LENGTH = 0x8B84
|
||||
GREATER = 0x204
|
||||
LINEAR = 0x2601
|
||||
LINK_STATUS = 0x8b82
|
||||
LUMINANCE = 0x1909
|
||||
MAX_TEXTURE_SIZE = 0xd33
|
||||
NEAREST = 0x2600
|
||||
NO_ERROR = 0x0
|
||||
NUM_EXTENSIONS = 0x821D
|
||||
ONE = 0x1
|
||||
ONE_MINUS_SRC_ALPHA = 0x303
|
||||
QUERY_RESULT = 0x8866
|
||||
QUERY_RESULT_AVAILABLE = 0x8867
|
||||
R16F = 0x822d
|
||||
R8 = 0x8229
|
||||
READ_FRAMEBUFFER = 0x8ca8
|
||||
RED = 0x1903
|
||||
RENDERER = 0x1F01
|
||||
RENDERBUFFER = 0x8d41
|
||||
RENDERBUFFER_BINDING = 0x8ca7
|
||||
RENDERBUFFER_HEIGHT = 0x8d43
|
||||
RENDERBUFFER_WIDTH = 0x8d42
|
||||
RGB = 0x1907
|
||||
RGBA = 0x1908
|
||||
RGBA8 = 0x8058
|
||||
SHORT = 0x1402
|
||||
SRGB = 0x8c40
|
||||
SRGB_ALPHA_EXT = 0x8c42
|
||||
SRGB8 = 0x8c41
|
||||
SRGB8_ALPHA8 = 0x8c43
|
||||
STATIC_DRAW = 0x88e4
|
||||
TEXTURE_2D = 0xde1
|
||||
TEXTURE_MAG_FILTER = 0x2800
|
||||
TEXTURE_MIN_FILTER = 0x2801
|
||||
TEXTURE_WRAP_S = 0x2802
|
||||
TEXTURE_WRAP_T = 0x2803
|
||||
TEXTURE0 = 0x84c0
|
||||
TEXTURE1 = 0x84c1
|
||||
TRIANGLE_STRIP = 0x5
|
||||
TRIANGLES = 0x4
|
||||
UNPACK_ALIGNMENT = 0xcf5
|
||||
UNSIGNED_BYTE = 0x1401
|
||||
UNSIGNED_SHORT = 0x1403
|
||||
VERSION = 0x1f02
|
||||
VERTEX_SHADER = 0x8b31
|
||||
ZERO = 0x0
|
||||
|
||||
// EXT_disjoint_timer_query
|
||||
TIME_ELAPSED_EXT = 0x88BF
|
||||
GPU_DISJOINT_EXT = 0x8FBB
|
||||
)
|
||||
|
||||
type Functions interface {
|
||||
ActiveTexture(texture Enum)
|
||||
AttachShader(p Program, s Shader)
|
||||
BeginQuery(target Enum, query Query)
|
||||
BindAttribLocation(p Program, a Attrib, name string)
|
||||
BindBuffer(target Enum, b Buffer)
|
||||
BindFramebuffer(target Enum, fb Framebuffer)
|
||||
BindRenderbuffer(target Enum, rb Renderbuffer)
|
||||
BindTexture(target Enum, t Texture)
|
||||
BlendEquation(mode Enum)
|
||||
BlendFunc(sfactor, dfactor Enum)
|
||||
BufferData(target Enum, src []byte, usage Enum)
|
||||
CheckFramebufferStatus(target Enum) Enum
|
||||
Clear(mask Enum)
|
||||
ClearColor(red, green, blue, alpha float32)
|
||||
ClearDepthf(d float32)
|
||||
CompileShader(s Shader)
|
||||
CreateBuffer() Buffer
|
||||
CreateFramebuffer() Framebuffer
|
||||
CreateProgram() Program
|
||||
CreateQuery() Query
|
||||
CreateRenderbuffer() Renderbuffer
|
||||
CreateShader(ty Enum) Shader
|
||||
CreateTexture() Texture
|
||||
DeleteBuffer(v Buffer)
|
||||
DeleteFramebuffer(v Framebuffer)
|
||||
DeleteProgram(p Program)
|
||||
DeleteQuery(query Query)
|
||||
DeleteRenderbuffer(v Renderbuffer)
|
||||
DeleteShader(s Shader)
|
||||
DeleteTexture(v Texture)
|
||||
DepthFunc(f Enum)
|
||||
DepthMask(mask bool)
|
||||
DisableVertexAttribArray(a Attrib)
|
||||
Disable(cap Enum)
|
||||
DrawArrays(mode Enum, first, count int)
|
||||
DrawElements(mode Enum, count int, ty Enum, offset int)
|
||||
Enable(cap Enum)
|
||||
EnableVertexAttribArray(a Attrib)
|
||||
EndQuery(target Enum)
|
||||
Finish()
|
||||
FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer Renderbuffer)
|
||||
FramebufferTexture2D(target, attachment, texTarget Enum, t Texture, level int)
|
||||
GetBinding(pname Enum) Object
|
||||
GetError() Enum
|
||||
GetRenderbufferParameteri(target, pname Enum) int
|
||||
GetFramebufferAttachmentParameteri(target, attachment, pname Enum) int
|
||||
GetInteger(pname Enum) int
|
||||
GetProgrami(p Program, pname Enum) int
|
||||
GetProgramInfoLog(p Program) string
|
||||
GetQueryObjectuiv(query Query, pname Enum) uint
|
||||
GetShaderi(s Shader, pname Enum) int
|
||||
GetShaderInfoLog(s Shader) string
|
||||
GetString(pname Enum) string
|
||||
GetUniformLocation(p Program, name string) Uniform
|
||||
InvalidateFramebuffer(target, attachment Enum)
|
||||
LinkProgram(p Program)
|
||||
PixelStorei(pname Enum, param int32)
|
||||
ReadPixels(x, y, width, height int, format, ty Enum, data []byte)
|
||||
RenderbufferStorage(target, internalformat Enum, width, height int)
|
||||
Scissor(x, y, width, height int32)
|
||||
ShaderSource(s Shader, src string)
|
||||
TexImage2D(target Enum, level int, internalFormat int, width, height int, format, ty Enum, data []byte)
|
||||
TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte)
|
||||
TexParameteri(target, pname Enum, param int)
|
||||
Uniform1f(dst Uniform, v float32)
|
||||
Uniform1i(dst Uniform, v int)
|
||||
Uniform2f(dst Uniform, v0, v1 float32)
|
||||
Uniform3f(dst Uniform, v0, v1, v2 float32)
|
||||
Uniform4f(dst Uniform, v0, v1, v2, v3 float32)
|
||||
UseProgram(p Program)
|
||||
VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)
|
||||
Viewport(x, y, width, height int)
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SRGBFBO implements an intermediate sRGB FBO
|
||||
// for gamma-correct rendering on platforms without
|
||||
// sRGB enabled native framebuffers.
|
||||
type SRGBFBO struct {
|
||||
c Functions
|
||||
width, height int
|
||||
frameBuffer Framebuffer
|
||||
depthBuffer Renderbuffer
|
||||
colorTex Texture
|
||||
blitted bool
|
||||
quad Buffer
|
||||
prog Program
|
||||
es3 bool
|
||||
}
|
||||
|
||||
func NewSRGBFBO(f Functions) (*SRGBFBO, error) {
|
||||
var es3 bool
|
||||
glVer := f.GetString(VERSION)
|
||||
ver, err := ParseGLVersion(glVer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ver[0] >= 3 {
|
||||
es3 = true
|
||||
} else {
|
||||
exts := f.GetString(EXTENSIONS)
|
||||
if !strings.Contains(exts, "EXT_sRGB") {
|
||||
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
|
||||
}
|
||||
}
|
||||
s := &SRGBFBO{
|
||||
c: f,
|
||||
es3: es3,
|
||||
frameBuffer: f.CreateFramebuffer(),
|
||||
colorTex: f.CreateTexture(),
|
||||
depthBuffer: f.CreateRenderbuffer(),
|
||||
}
|
||||
f.BindTexture(TEXTURE_2D, s.colorTex)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST)
|
||||
f.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Blit() {
|
||||
if !s.blitted {
|
||||
prog, err := CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.prog = prog
|
||||
s.c.UseProgram(prog)
|
||||
s.c.Uniform1i(GetUniformLocation(s.c, prog, "tex"), 0)
|
||||
s.quad = s.c.CreateBuffer()
|
||||
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
|
||||
s.c.BufferData(ARRAY_BUFFER,
|
||||
BytesView([]float32{
|
||||
-1, +1, 0, 1,
|
||||
+1, +1, 1, 1,
|
||||
-1, -1, 0, 0,
|
||||
+1, -1, 1, 0,
|
||||
}),
|
||||
STATIC_DRAW)
|
||||
s.blitted = true
|
||||
}
|
||||
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
|
||||
s.c.ClearColor(1, 0, 1, 1)
|
||||
s.c.Clear(COLOR_BUFFER_BIT)
|
||||
s.c.UseProgram(s.prog)
|
||||
s.c.BindTexture(TEXTURE_2D, s.colorTex)
|
||||
s.c.BindBuffer(ARRAY_BUFFER, s.quad)
|
||||
s.c.VertexAttribPointer(0 /* pos */, 2, FLOAT, false, 4*4, 0)
|
||||
s.c.VertexAttribPointer(1 /* uv */, 2, FLOAT, false, 4*4, 4*2)
|
||||
s.c.EnableVertexAttribArray(0)
|
||||
s.c.EnableVertexAttribArray(1)
|
||||
s.c.DrawArrays(TRIANGLE_STRIP, 0, 4)
|
||||
s.c.BindTexture(TEXTURE_2D, Texture{})
|
||||
s.c.DisableVertexAttribArray(0)
|
||||
s.c.DisableVertexAttribArray(1)
|
||||
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
|
||||
s.c.InvalidateFramebuffer(FRAMEBUFFER, COLOR_ATTACHMENT0)
|
||||
s.c.InvalidateFramebuffer(FRAMEBUFFER, DEPTH_ATTACHMENT)
|
||||
// The Android emulator requires framebuffer 0 bound at eglSwapBuffer time.
|
||||
// Bind the sRGB framebuffer again in afterPresent.
|
||||
s.c.BindFramebuffer(FRAMEBUFFER, Framebuffer{})
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) AfterPresent() {
|
||||
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Refresh(w, h int) error {
|
||||
s.width, s.height = w, h
|
||||
if w == 0 || h == 0 {
|
||||
return nil
|
||||
}
|
||||
s.c.BindTexture(TEXTURE_2D, s.colorTex)
|
||||
if s.es3 {
|
||||
s.c.TexImage2D(TEXTURE_2D, 0, SRGB8_ALPHA8, w, h, RGBA, UNSIGNED_BYTE, nil)
|
||||
} else /* EXT_sRGB */ {
|
||||
s.c.TexImage2D(TEXTURE_2D, 0, SRGB_ALPHA_EXT, w, h, SRGB_ALPHA_EXT, UNSIGNED_BYTE, nil)
|
||||
}
|
||||
currentRB := Renderbuffer(s.c.GetBinding(RENDERBUFFER_BINDING))
|
||||
s.c.BindRenderbuffer(RENDERBUFFER, s.depthBuffer)
|
||||
s.c.RenderbufferStorage(RENDERBUFFER, DEPTH_COMPONENT16, w, h)
|
||||
s.c.BindRenderbuffer(RENDERBUFFER, currentRB)
|
||||
s.c.BindFramebuffer(FRAMEBUFFER, s.frameBuffer)
|
||||
s.c.FramebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, s.colorTex, 0)
|
||||
s.c.FramebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, s.depthBuffer)
|
||||
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
|
||||
}
|
||||
|
||||
if runtime.GOOS == "js" {
|
||||
// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
|
||||
// texture result in twice gamma corrected colors. Using a plain RGBA
|
||||
// texture seems to work.
|
||||
s.c.ClearColor(.5, .5, .5, 1.0)
|
||||
s.c.Clear(COLOR_BUFFER_BIT)
|
||||
var pixel [4]byte
|
||||
s.c.ReadPixels(0, 0, 1, 1, RGBA, UNSIGNED_BYTE, pixel[:])
|
||||
if pixel[0] == 128 { // Correct sRGB color value is ~188
|
||||
s.c.TexImage2D(TEXTURE_2D, 0, RGBA, w, h, RGBA, UNSIGNED_BYTE, nil)
|
||||
if st := s.c.CheckFramebufferStatus(FRAMEBUFFER); st != FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", s.width, s.height, st, s.c.GetError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SRGBFBO) Release() {
|
||||
s.c.DeleteFramebuffer(s.frameBuffer)
|
||||
s.c.DeleteTexture(s.colorTex)
|
||||
s.c.DeleteRenderbuffer(s.depthBuffer)
|
||||
if s.blitted {
|
||||
s.c.DeleteBuffer(s.quad)
|
||||
s.c.DeleteProgram(s.prog)
|
||||
}
|
||||
s.c = nil
|
||||
}
|
||||
|
||||
const (
|
||||
blitVSrc = `
|
||||
#version 100
|
||||
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 pos;
|
||||
attribute vec2 uv;
|
||||
|
||||
varying vec2 vUV;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(pos, 0, 1);
|
||||
vUV = uv;
|
||||
}
|
||||
`
|
||||
blitFSrc = `
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D tex;
|
||||
varying vec2 vUV;
|
||||
|
||||
vec3 gamma(vec3 rgb) {
|
||||
vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
|
||||
vec3 lin = rgb * vec3(12.92);
|
||||
bvec3 cut = lessThan(rgb, vec3(0.0031308));
|
||||
return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 col = texture2D(tex, vUV);
|
||||
vec3 rgb = col.rgb;
|
||||
rgb = gamma(rgb);
|
||||
gl_FragColor = vec4(rgb, col.a);
|
||||
}
|
||||
`
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
// +build !js
|
||||
|
||||
package gl
|
||||
|
||||
type (
|
||||
Buffer struct{ V uint }
|
||||
Framebuffer struct{ V uint }
|
||||
Program struct{ V uint }
|
||||
Renderbuffer struct{ V uint }
|
||||
Shader struct{ V uint }
|
||||
Texture struct{ V uint }
|
||||
Query struct{ V uint }
|
||||
Uniform struct{ V int }
|
||||
Object struct{ V uint }
|
||||
)
|
||||
|
||||
func (u Uniform) Valid() bool {
|
||||
return u.V != -1
|
||||
}
|
||||
|
||||
func (p Program) Valid() bool {
|
||||
return p.V != 0
|
||||
}
|
||||
|
||||
func (s Shader) Valid() bool {
|
||||
return s.V != 0
|
||||
}
|
||||
|
||||
func (t Texture) Valid() bool {
|
||||
return t.V != 0
|
||||
}
|
||||
|
||||
func (t Texture) Equal(t2 Texture) bool {
|
||||
return t == t2
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
type (
|
||||
Buffer js.Value
|
||||
Framebuffer js.Value
|
||||
Program js.Value
|
||||
Renderbuffer js.Value
|
||||
Shader js.Value
|
||||
Texture js.Value
|
||||
Query js.Value
|
||||
Uniform js.Value
|
||||
Object js.Value
|
||||
)
|
||||
|
||||
func (p Program) Valid() bool {
|
||||
return !js.Value(p).IsUndefined() && !js.Value(p).IsNull()
|
||||
}
|
||||
|
||||
func (s Shader) Valid() bool {
|
||||
return !js.Value(s).IsUndefined() && !js.Value(s).IsNull()
|
||||
}
|
||||
|
||||
func (u Uniform) Valid() bool {
|
||||
return !js.Value(u).IsUndefined() && !js.Value(u).IsNull()
|
||||
}
|
||||
|
||||
func (t Texture) Valid() bool {
|
||||
return !js.Value(t).IsUndefined() && !js.Value(t).IsNull()
|
||||
}
|
||||
|
||||
func (t Texture) Equal(t2 Texture) bool {
|
||||
return js.Value(t).Equal(js.Value(t2))
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CreateProgram(ctx Functions, vsSrc, fsSrc string, attribs []string) (Program, error) {
|
||||
vs, err := createShader(ctx, VERTEX_SHADER, vsSrc)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
defer ctx.DeleteShader(vs)
|
||||
fs, err := createShader(ctx, FRAGMENT_SHADER, fsSrc)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
defer ctx.DeleteShader(fs)
|
||||
prog := ctx.CreateProgram()
|
||||
if !prog.Valid() {
|
||||
return Program{}, errors.New("glCreateProgram failed")
|
||||
}
|
||||
ctx.AttachShader(prog, vs)
|
||||
ctx.AttachShader(prog, fs)
|
||||
for i, a := range attribs {
|
||||
ctx.BindAttribLocation(prog, Attrib(i), a)
|
||||
}
|
||||
ctx.LinkProgram(prog)
|
||||
if ctx.GetProgrami(prog, LINK_STATUS) == 0 {
|
||||
log := ctx.GetProgramInfoLog(prog)
|
||||
ctx.DeleteProgram(prog)
|
||||
return Program{}, fmt.Errorf("program link failed: %s", strings.TrimSpace(log))
|
||||
}
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
func GetUniformLocation(ctx Functions, prog Program, name string) Uniform {
|
||||
loc := ctx.GetUniformLocation(prog, name)
|
||||
if !loc.Valid() {
|
||||
panic(fmt.Errorf("uniform %s not found", name))
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
func createShader(ctx Functions, typ Enum, src string) (Shader, error) {
|
||||
sh := ctx.CreateShader(typ)
|
||||
if !sh.Valid() {
|
||||
return Shader{}, errors.New("glCreateShader failed")
|
||||
}
|
||||
ctx.ShaderSource(sh, src)
|
||||
ctx.CompileShader(sh)
|
||||
if ctx.GetShaderi(sh, COMPILE_STATUS) == 0 {
|
||||
log := ctx.GetShaderInfoLog(sh)
|
||||
ctx.DeleteShader(sh)
|
||||
return Shader{}, fmt.Errorf("shader compilation failed: %s", strings.TrimSpace(log))
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
// BytesView returns a byte slice view of a slice.
|
||||
func BytesView(s interface{}) []byte {
|
||||
v := reflect.ValueOf(s)
|
||||
first := v.Index(0)
|
||||
sz := int(first.Type().Size())
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(first.UnsafeAddr())))),
|
||||
Len: v.Len() * sz,
|
||||
Cap: v.Cap() * sz,
|
||||
}))
|
||||
}
|
||||
|
||||
func ParseGLVersion(glVer string) ([2]int, error) {
|
||||
var ver [2]int
|
||||
if _, err := fmt.Sscanf(glVer, "OpenGL ES %d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, nil
|
||||
} else if _, err := fmt.Sscanf(glVer, "WebGL %d.%d", &ver[0], &ver[1]); err == nil {
|
||||
// WebGL major version v corresponds to OpenGL ES version v + 1
|
||||
ver[0]++
|
||||
return ver, nil
|
||||
} else if _, err := fmt.Sscanf(glVer, "%d.%d", &ver[0], &ver[1]); err == nil {
|
||||
return ver, nil
|
||||
}
|
||||
return ver, fmt.Errorf("failed to parse OpenGL ES version (%s)", glVer)
|
||||
}
|
||||
|
||||
func SliceOf(s uintptr) []byte {
|
||||
if s == 0 {
|
||||
return nil
|
||||
}
|
||||
sh := reflect.SliceHeader{
|
||||
Data: s,
|
||||
Len: 1 << 30,
|
||||
Cap: 1 << 30,
|
||||
}
|
||||
return *(*[]byte)(unsafe.Pointer(&sh))
|
||||
}
|
||||
|
||||
// GoString convert a NUL-terminated C string
|
||||
// to a Go string.
|
||||
func GoString(s []byte) string {
|
||||
i := 0
|
||||
for {
|
||||
if s[i] == 0 {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return string(s[:i])
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package gl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoString(t *testing.T) {
|
||||
tests := [][2]string{
|
||||
{"Hello\x00", "Hello"},
|
||||
{"\x00", ""},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := GoString([]byte(test[0]))
|
||||
if exp := test[1]; exp != got {
|
||||
t.Errorf("expected %q got %q", exp, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user