// SPDX-License-Identifier: Unlicense OR MIT // GL types, constants, and Functions wrapper over jsbridge/webgl. // WASM/WebGL2 only. GL handle types are prefixed with GL to avoid // conflicts with the driver.Buffer/Texture interfaces. package gio import ( "bytes" "errors" "fmt" "unsafe" "jsbridge/webgl" ) type ( Enum uint32 GLAttrib uint32 ) const ( ACTIVE_TEXTURE = 0x84E0 ALL_BARRIER_BITS = 0xffffffff ARRAY_BUFFER = 0x8892 ARRAY_BUFFER_BINDING = 0x8894 BACK = 0x0405 BLEND = 0xbe2 BLEND_DST_RGB = 0x80C8 BLEND_SRC_RGB = 0x80C9 BLEND_DST_ALPHA = 0x80CA BLEND_SRC_ALPHA = 0x80CB CLAMP_TO_EDGE = 0x812f COLOR_ATTACHMENT0 = 0x8ce0 COLOR_BUFFER_BIT = 0x4000 COLOR_CLEAR_VALUE = 0x0C22 COMPILE_STATUS = 0x8b81 CURRENT_PROGRAM = 0x8B8D DEPTH_ATTACHMENT = 0x8d00 DEPTH_BUFFER_BIT = 0x100 DEPTH_CLEAR_VALUE = 0x0B73 DEPTH_COMPONENT16 = 0x81a5 DEPTH_COMPONENT24 = 0x81A6 DEPTH_COMPONENT32F = 0x8CAC DEPTH_FUNC = 0x0B74 DEPTH_TEST = 0xb71 DEPTH_WRITEMASK = 0x0B72 DRAW_FRAMEBUFFER = 0x8CA9 DST_COLOR = 0x306 DYNAMIC_DRAW = 0x88E8 ELEMENT_ARRAY_BUFFER = 0x8893 ELEMENT_ARRAY_BUFFER_BINDING = 0x8895 EXTENSIONS = 0x1f03 FALSE = 0 FLOAT = 0x1406 FRAGMENT_SHADER = 0x8b30 FRAMEBUFFER = 0x8d40 FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210 FRAMEBUFFER_BINDING = 0x8ca6 FRAMEBUFFER_COMPLETE = 0x8cd5 FRAMEBUFFER_SRGB = 0x8db9 HALF_FLOAT = 0x140b HALF_FLOAT_OES = 0x8d61 INFO_LOG_LENGTH = 0x8B84 INVALID_INDEX = ^uint32(0) GREATER = 0x204 GEQUAL = 0x206 LINEAR = 0x2601 LINEAR_MIPMAP_LINEAR = 0x2703 LINK_STATUS = 0x8b82 LUMINANCE = 0x1909 MAX_TEXTURE_SIZE = 0xd33 NEAREST = 0x2600 NO_ERROR = 0x0 ONE = 0x1 ONE_MINUS_SRC_ALPHA = 0x303 PACK_ROW_LENGTH = 0x0D02 R16F = 0x822d RGBA16F = 0x881A RGBA32F = 0x8814 R8 = 0x8229 READ_FRAMEBUFFER = 0x8ca8 READ_FRAMEBUFFER_BINDING = 0x8CAA RED = 0x1903 RENDERER = 0x1F01 RENDERBUFFER = 0x8d41 RENDERBUFFER_BINDING = 0x8ca7 RGB = 0x1907 GL_RGBA = 0x1908 RGBA8 = 0x8058 SHORT = 0x1402 SRGB = 0x8c40 SRGB_ALPHA_EXT = 0x8c42 SRGB8 = 0x8c41 SRGB8_ALPHA8 = 0x8c43 STATIC_DRAW = 0x88e4 STENCIL_BUFFER_BIT = 0x00000400 TEXTURE_2D = 0xde1 TEXTURE_BINDING_2D = 0x8069 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 TRUE = 1 UNIFORM_BUFFER = 0x8A11 UNIFORM_BUFFER_BINDING = 0x8A28 UNPACK_ALIGNMENT = 0xcf5 UNPACK_ROW_LENGTH = 0x0CF2 UNSIGNED_BYTE = 0x1401 UNSIGNED_SHORT = 0x1403 VIEWPORT = 0x0BA2 VERSION = 0x1f02 VERTEX_ARRAY_BINDING = 0x85B5 VERTEX_SHADER = 0x8b31 WRITE_ONLY = 0x88B9 ZERO = 0x0 ) // GL object handle types. Zero value means unbound/invalid. // Prefixed GL to avoid conflicts with driver.Buffer/Texture interfaces. type ( GLObject struct{ V int32 } GLBuffer GLObject GLFramebuffer GLObject GLProgram GLObject GLRenderbuffer GLObject GLShader GLObject GLTexture GLObject GLUniform struct{ V int32 } ) func (o GLObject) valid() bool { return o.V != 0 } func (b GLBuffer) Valid() bool { return GLObject(b).valid() } func (f GLFramebuffer) Valid() bool { return GLObject(f).valid() } func (p GLProgram) Valid() bool { return GLObject(p).valid() } func (s GLShader) Valid() bool { return GLObject(s).valid() } func (u GLUniform) Valid() bool { return u.V != -1 } func (f GLFramebuffer) Equal(f2 GLFramebuffer) bool { return f == f2 } func (p GLProgram) Equal(p2 GLProgram) bool { return p == p2 } func (s GLShader) Equal(s2 GLShader) bool { return s == s2 } func (u GLUniform) Equal(u2 GLUniform) bool { return u == u2 } func (b GLBuffer) Equal(b2 GLBuffer) bool { return b == b2 } func (t GLTexture) Equal(t2 GLTexture) bool { return t == t2 } func (r GLRenderbuffer) Equal(r2 GLRenderbuffer) bool { return r == r2 } // GLContext is a WebGL2 rendering context handle. type GLContext = webgl.Context // Functions wraps all WebGL2 operations via jsbridge/webgl. type Functions struct { ctx webgl.Context } func NewFunctions(ctx GLContext) (*Functions, error) { if ctx == 0 { return nil, errors.New("gl: null context") } return &Functions{ctx: ctx}, nil } func (f *Functions) ActiveTexture(t Enum) { webgl.ActiveTexture(f.ctx, int32(t)) } func (f *Functions) AttachShader(p GLProgram, s GLShader) { webgl.AttachShader(f.ctx, webgl.Program(p.V), webgl.Shader(s.V)) } func (f *Functions) BindAttribLocation(p GLProgram, a GLAttrib, name string) { webgl.BindAttribLocation(f.ctx, webgl.Program(p.V), int32(a), unsafe.StringData(name), int32(len(name))) } func (f *Functions) BindBuffer(target Enum, b GLBuffer) { webgl.BindBuffer(f.ctx, int32(target), webgl.Buffer(b.V)) } func (f *Functions) BindFramebuffer(target Enum, fb GLFramebuffer) { webgl.BindFramebuffer(f.ctx, int32(target), webgl.Framebuffer(fb.V)) } func (f *Functions) BindRenderbuffer(target Enum, rb GLRenderbuffer) { webgl.BindRenderbuffer(f.ctx, int32(target), webgl.Renderbuffer(rb.V)) } func (f *Functions) BindTexture(target Enum, t GLTexture) { webgl.BindTexture(f.ctx, int32(target), webgl.Texture(t.V)) } func (f *Functions) BlendFuncSeparate(srcRGB, dstRGB, srcA, dstA Enum) { webgl.BlendFuncSeparate(f.ctx, int32(srcRGB), int32(dstRGB), int32(srcA), int32(dstA)) } func (f *Functions) BufferData(target Enum, size int, usage Enum, data []byte) { if data == nil { webgl.BufferData(f.ctx, int32(target), nil, int32(size), int32(usage)) } else { webgl.BufferData(f.ctx, int32(target), &data[0], int32(len(data)), int32(usage)) } } func (f *Functions) BufferSubData(target Enum, offset int, src []byte) { if len(src) > 0 { webgl.BufferSubData(f.ctx, int32(target), int32(offset), &src[0], int32(len(src))) } } func (f *Functions) CheckFramebufferStatus(target Enum) Enum { return Enum(webgl.CheckFramebufferStatus(f.ctx, int32(target))) } func (f *Functions) Clear(mask Enum) { webgl.Clear(f.ctx, int32(mask)) } func (f *Functions) ClearColor(red, green, blue, alpha float32) { webgl.ClearColor(f.ctx, red, green, blue, alpha) } func (f *Functions) ClearDepthf(d float32) { webgl.ClearDepth(f.ctx, d) } func (f *Functions) CompileShader(s GLShader) { webgl.CompileShader(f.ctx, webgl.Shader(s.V)) } func (f *Functions) CopyTexSubImage2D(target Enum, level, xoffset, yoffset, x, y, width, height int) { webgl.CopyTexSubImage2D(f.ctx, int32(target), int32(level), int32(xoffset), int32(yoffset), int32(x), int32(y), int32(width), int32(height)) } func (f *Functions) CreateBuffer() GLBuffer { return GLBuffer{V: int32(webgl.CreateBuffer(f.ctx))} } func (f *Functions) CreateFramebuffer() GLFramebuffer { return GLFramebuffer{V: int32(webgl.CreateFramebuffer(f.ctx))} } func (f *Functions) CreateProgram() GLProgram { return GLProgram{V: int32(webgl.CreateProgram(f.ctx))} } func (f *Functions) CreateRenderbuffer() GLRenderbuffer { return GLRenderbuffer{V: int32(webgl.CreateRenderbuffer(f.ctx))} } func (f *Functions) CreateShader(ty Enum) GLShader { return GLShader{V: int32(webgl.CreateShader(f.ctx, int32(ty)))} } func (f *Functions) CreateTexture() GLTexture { return GLTexture{V: int32(webgl.CreateTexture(f.ctx))} } func (f *Functions) DeleteBuffer(v GLBuffer) { webgl.DeleteBuffer(f.ctx, webgl.Buffer(v.V)) } func (f *Functions) DeleteFramebuffer(v GLFramebuffer) { webgl.DeleteFramebuffer(f.ctx, webgl.Framebuffer(v.V)) } func (f *Functions) DeleteProgram(p GLProgram) { webgl.DeleteProgram(f.ctx, webgl.Program(p.V)) } func (f *Functions) DeleteShader(s GLShader) { webgl.DeleteShader(f.ctx, webgl.Shader(s.V)) } func (f *Functions) DeleteRenderbuffer(v GLRenderbuffer) { webgl.DeleteRenderbuffer(f.ctx, webgl.Renderbuffer(v.V)) } func (f *Functions) DeleteTexture(v GLTexture) { webgl.DeleteTexture(f.ctx, webgl.Texture(v.V)) } func (f *Functions) DepthFunc(fn Enum) { webgl.DepthFunc(f.ctx, int32(fn)) } func (f *Functions) DepthMask(mask bool) { m := int32(0) if mask { m = 1 } webgl.DepthMask(f.ctx, m) } func (f *Functions) DisableVertexAttribArray(a GLAttrib) { webgl.DisableVertexAttribArray(f.ctx, int32(a)) } func (f *Functions) Disable(cap Enum) { webgl.Disable(f.ctx, int32(cap)) } func (f *Functions) DrawArrays(mode Enum, first, count int) { webgl.DrawArrays(f.ctx, int32(mode), int32(first), int32(count)) } func (f *Functions) DrawElements(mode Enum, count int, ty Enum, offset int) { webgl.DrawElements(f.ctx, int32(mode), int32(count), int32(ty), int32(offset)) } func (f *Functions) Enable(cap Enum) { webgl.Enable(f.ctx, int32(cap)) } func (f *Functions) EnableVertexAttribArray(a GLAttrib) { webgl.EnableVertexAttribArray(f.ctx, int32(a)) } func (f *Functions) Finish() { webgl.Finish(f.ctx) } func (f *Functions) Flush() { webgl.Flush(f.ctx) } func (f *Functions) FramebufferRenderbuffer(target, attachment, renderbuffertarget Enum, renderbuffer GLRenderbuffer) { webgl.FramebufferRenderbuffer(f.ctx, int32(target), int32(attachment), int32(renderbuffertarget), webgl.Renderbuffer(renderbuffer.V)) } func (f *Functions) FramebufferTexture2D(target, attachment, texTarget Enum, t GLTexture, level int) { webgl.FramebufferTexture2D(f.ctx, int32(target), int32(attachment), int32(texTarget), webgl.Texture(t.V), int32(level)) } func (f *Functions) GenerateMipmap(target Enum) { webgl.GenerateMipmap(f.ctx, int32(target)) } func (f *Functions) GetError() Enum { return 0 // avoid slow getError; WebGL2 errors are reported as exceptions } func (f *Functions) GetInteger(pname Enum) int { return int(webgl.GetInteger(f.ctx, int32(pname))) } func (f *Functions) GetProgrami(p GLProgram, pname Enum) int { return int(webgl.GetProgramParameter(f.ctx, webgl.Program(p.V), int32(pname))) } func (f *Functions) GetProgramInfoLog(p GLProgram) string { buf := []byte{:512} // heap-allocated, not stack n := webgl.GetProgramInfoLog(f.ctx, webgl.Program(p.V), &buf[0], 512) if n > 512 { n = 512 } return string(buf[:n]) } func (f *Functions) GetShaderi(s GLShader, pname Enum) int { return int(webgl.GetShaderParameter(f.ctx, webgl.Shader(s.V), int32(pname))) } func (f *Functions) GetShaderInfoLog(s GLShader) string { buf := []byte{:512} // heap-allocated, not stack n := webgl.GetShaderInfoLog(f.ctx, webgl.Shader(s.V), &buf[0], 512) if n > 512 { n = 512 } return string(buf[:n]) } func (f *Functions) GetUniformLocation(p GLProgram, name string) GLUniform { loc := webgl.GetUniformLocation(f.ctx, webgl.Program(p.V), unsafe.StringData(name), int32(len(name))) if loc == 0 { return GLUniform{V: -1} } return GLUniform{V: int32(loc)} } func (f *Functions) InvalidateFramebuffer(target, attachment Enum) { webgl.InvalidateFramebuffer(f.ctx, int32(target), int32(attachment)) } func (f *Functions) IsEnabled(cap Enum) bool { return webgl.IsEnabled(f.ctx, int32(cap)) != 0 } func (f *Functions) LinkProgram(p GLProgram) { webgl.LinkProgram(f.ctx, webgl.Program(p.V)) } func (f *Functions) PixelStorei(pname Enum, param int) { webgl.PixelStorei(f.ctx, int32(pname), int32(param)) } func (f *Functions) RenderbufferStorage(target, internalformat Enum, width, height int) { webgl.RenderbufferStorage(f.ctx, int32(target), int32(internalformat), int32(width), int32(height)) } func (f *Functions) ReadPixels(x, y, width, height int, format, ty Enum, data []byte) { if len(data) > 0 { webgl.ReadPixels(f.ctx, int32(x), int32(y), int32(width), int32(height), int32(format), int32(ty), &data[0], int32(len(data))) } } func (f *Functions) Scissor(x, y, width, height int32) { webgl.Scissor(f.ctx, x, y, width, height) } func (f *Functions) ShaderSource(s GLShader, src string) { webgl.ShaderSource(f.ctx, webgl.Shader(s.V), unsafe.StringData(src), int32(len(src))) } func (f *Functions) TexImage2D(target Enum, level int, internalFormat Enum, width, height int, format, ty Enum) { webgl.TexImage2D(f.ctx, int32(target), int32(level), int32(internalFormat), int32(width), int32(height), 0, int32(format), int32(ty), nil, 0) } func (f *Functions) TexStorage2D(target Enum, levels int, internalFormat Enum, width, height int) { webgl.TexStorage2D(f.ctx, int32(target), int32(levels), int32(internalFormat), int32(width), int32(height)) } func (f *Functions) TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte) { if len(data) > 0 { webgl.TexSubImage2D(f.ctx, int32(target), int32(level), int32(x), int32(y), int32(width), int32(height), int32(format), int32(ty), &data[0], int32(len(data))) } } func (f *Functions) TexParameteri(target, pname Enum, param int) { webgl.TexParameteri(f.ctx, int32(target), int32(pname), int32(param)) } func (f *Functions) Uniform1f(dst GLUniform, v float32) { webgl.Uniform1f(f.ctx, webgl.UniformLocation(dst.V), v) } func (f *Functions) Uniform1i(dst GLUniform, v int) { webgl.Uniform1i(f.ctx, webgl.UniformLocation(dst.V), int32(v)) } func (f *Functions) Uniform2f(dst GLUniform, v0, v1 float32) { webgl.Uniform2f(f.ctx, webgl.UniformLocation(dst.V), v0, v1) } func (f *Functions) Uniform3f(dst GLUniform, v0, v1, v2 float32) { webgl.Uniform3f(f.ctx, webgl.UniformLocation(dst.V), v0, v1, v2) } func (f *Functions) Uniform4f(dst GLUniform, v0, v1, v2, v3 float32) { webgl.Uniform4f(f.ctx, webgl.UniformLocation(dst.V), v0, v1, v2, v3) } func (f *Functions) UseProgram(p GLProgram) { webgl.UseProgram(f.ctx, webgl.Program(p.V)) } func (f *Functions) VertexAttribPointer(dst GLAttrib, size int, ty Enum, normalized bool, stride, offset int) { norm := int32(0) if normalized { norm = 1 } webgl.VertexAttribPointer(f.ctx, int32(dst), int32(size), int32(ty), norm, int32(stride), int32(offset)) } func (f *Functions) Viewport(x, y, width, height int) { webgl.Viewport(f.ctx, int32(x), int32(y), int32(width), int32(height)) } // GLCreateProgram links a vertex and fragment shader into a program. func GLCreateProgram(ctx *Functions, vsSrc, fsSrc string, attribs []string) (GLProgram, error) { vs, err := GLCreateShader(ctx, VERTEX_SHADER, vsSrc) if err != nil { return GLProgram{}, err } defer ctx.DeleteShader(vs) fs, err := GLCreateShader(ctx, FRAGMENT_SHADER, fsSrc) if err != nil { return GLProgram{}, err } defer ctx.DeleteShader(fs) prog := ctx.CreateProgram() if !prog.Valid() { return GLProgram{}, errors.New("glCreateProgram failed") } ctx.AttachShader(prog, vs) ctx.AttachShader(prog, fs) for i, a := range attribs { ctx.BindAttribLocation(prog, GLAttrib(i), a) } ctx.LinkProgram(prog) if ctx.GetProgrami(prog, LINK_STATUS) == 0 { log := ctx.GetProgramInfoLog(prog) ctx.DeleteProgram(prog) return GLProgram{}, fmt.Errorf("program link failed: %s", bytes.TrimSpace(log)) } return prog, nil } // GLCreateShader compiles a shader from source. func GLCreateShader(ctx *Functions, typ Enum, src string) (GLShader, error) { sh := ctx.CreateShader(typ) if !sh.Valid() { return GLShader{}, 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 GLShader{}, fmt.Errorf("shader compilation failed: %s", bytes.TrimSpace(log)) } return sh, nil }