mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-02 16:06:19 +00:00
f896a72ea1
A previous change[0] moved all OpenGL function calls to the internal opengl package, so that Gio can use desktop OpenGL and OpenGL ES (ANGLE) in the same program without confusing the function pointers. However the change also moved the glFlush that constitutes a buffer swap, or present, on macOS. Other platforms don't need the flush, so this change moves it back to macOS-specific code, in glContext.Present where it belongs. It also uses dlopen and dlsym to avoid symbol confusion between Apple's OpenGL framework and ANGLE's libGLESv2.dylib. The motivation is that we're getting rid of the desktop OpenGL backend on macOS in favor of Metal, and so should reduce the number of global special-cases catering to that platform. [0] https://gioui.org/commit/476d2269a Signed-off-by: Elias Naur <mail@eliasnaur.com>
1392 lines
38 KiB
Go
1392 lines
38 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package opengl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"gioui.org/gpu/internal/driver"
|
|
"gioui.org/internal/gl"
|
|
"gioui.org/shader"
|
|
)
|
|
|
|
// Backend implements driver.Device.
|
|
type Backend struct {
|
|
funcs *gl.Functions
|
|
|
|
clear bool
|
|
glstate glState
|
|
state state
|
|
savedState glState
|
|
|
|
glver [2]int
|
|
gles bool
|
|
ubo bool
|
|
feats driver.Caps
|
|
// floatTriple holds the settings for floating point
|
|
// textures.
|
|
floatTriple textureTriple
|
|
// Single channel alpha textures.
|
|
alphaTriple textureTriple
|
|
srgbaTriple textureTriple
|
|
vertUniforms *buffer
|
|
fragUniforms *buffer
|
|
storage [storageBindings]*buffer
|
|
|
|
sRGBFBO *SRGBFBO
|
|
|
|
// vertArray is bound during a frame. We don't need it, but
|
|
// core desktop OpenGL profile 3.3 requires some array bound.
|
|
vertArray gl.VertexArray
|
|
}
|
|
|
|
// State tracking.
|
|
type glState struct {
|
|
drawFBO gl.Framebuffer
|
|
readFBO gl.Framebuffer
|
|
renderBuf gl.Renderbuffer
|
|
vertAttribs [5]struct {
|
|
obj gl.Buffer
|
|
enabled bool
|
|
size int
|
|
typ gl.Enum
|
|
normalized bool
|
|
stride int
|
|
offset uintptr
|
|
}
|
|
prog gl.Program
|
|
texUnits struct {
|
|
active gl.Enum
|
|
binds [2]gl.Texture
|
|
}
|
|
arrayBuf gl.Buffer
|
|
elemBuf gl.Buffer
|
|
uniBuf gl.Buffer
|
|
uniBufs [2]gl.Buffer
|
|
storeBuf gl.Buffer
|
|
storeBufs [4]gl.Buffer
|
|
vertArray gl.VertexArray
|
|
srgb bool
|
|
blend struct {
|
|
enable bool
|
|
srcRGB, dstRGB gl.Enum
|
|
srcA, dstA gl.Enum
|
|
}
|
|
clearColor [4]float32
|
|
viewport [4]int
|
|
unpack_row_length int
|
|
pack_row_length int
|
|
}
|
|
|
|
type state struct {
|
|
pipeline *pipeline
|
|
buffer bufferBinding
|
|
}
|
|
|
|
type bufferBinding struct {
|
|
obj gl.Buffer
|
|
offset int
|
|
}
|
|
|
|
type timer struct {
|
|
funcs *gl.Functions
|
|
obj gl.Query
|
|
}
|
|
|
|
type texture struct {
|
|
backend *Backend
|
|
obj gl.Texture
|
|
triple textureTriple
|
|
width int
|
|
height int
|
|
}
|
|
|
|
type framebuffer struct {
|
|
backend *Backend
|
|
obj gl.Framebuffer
|
|
foreign bool
|
|
}
|
|
|
|
type pipeline struct {
|
|
prog *program
|
|
inputs []shader.InputLocation
|
|
layout driver.VertexLayout
|
|
blend driver.BlendDesc
|
|
}
|
|
|
|
type buffer struct {
|
|
backend *Backend
|
|
hasBuffer bool
|
|
obj gl.Buffer
|
|
typ driver.BufferBinding
|
|
size int
|
|
immutable bool
|
|
// For emulation of uniform buffers.
|
|
data []byte
|
|
}
|
|
|
|
type glshader struct {
|
|
backend *Backend
|
|
obj gl.Shader
|
|
src shader.Sources
|
|
}
|
|
|
|
type program struct {
|
|
backend *Backend
|
|
obj gl.Program
|
|
vertUniforms uniforms
|
|
fragUniforms uniforms
|
|
}
|
|
|
|
type uniforms struct {
|
|
locs []uniformLocation
|
|
size int
|
|
}
|
|
|
|
type uniformLocation struct {
|
|
uniform gl.Uniform
|
|
offset int
|
|
typ shader.DataType
|
|
size int
|
|
}
|
|
|
|
type inputLayout struct {
|
|
inputs []shader.InputLocation
|
|
layout []driver.InputDesc
|
|
}
|
|
|
|
// textureTriple holds the type settings for
|
|
// a TexImage2D call.
|
|
type textureTriple struct {
|
|
internalFormat gl.Enum
|
|
format gl.Enum
|
|
typ gl.Enum
|
|
}
|
|
|
|
const (
|
|
storageBindings = 32
|
|
)
|
|
|
|
func init() {
|
|
driver.NewOpenGLDevice = newOpenGLDevice
|
|
}
|
|
|
|
func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) {
|
|
f, err := gl.NewFunctions(api.Context, api.ES)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
|
|
glVer := f.GetString(gl.VERSION)
|
|
ver, gles, err := gl.ParseGLVersion(glVer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
floatTriple, ffboErr := floatTripleFor(f, ver, exts)
|
|
srgbaTriple, srgbErr := srgbaTripleFor(ver, exts)
|
|
gles30 := gles && ver[0] >= 3
|
|
gles31 := gles && (ver[0] > 3 || (ver[0] == 3 && ver[1] >= 1))
|
|
gl40 := !gles && ver[0] >= 4
|
|
b := &Backend{
|
|
glver: ver,
|
|
gles: gles,
|
|
ubo: gles30 || gl40,
|
|
funcs: f,
|
|
floatTriple: floatTriple,
|
|
alphaTriple: alphaTripleFor(ver),
|
|
srgbaTriple: srgbaTriple,
|
|
}
|
|
b.feats.BottomLeftOrigin = true
|
|
if srgbErr == nil {
|
|
b.feats.Features |= driver.FeatureSRGB
|
|
}
|
|
if ffboErr == nil {
|
|
b.feats.Features |= driver.FeatureFloatRenderTargets
|
|
}
|
|
if gles31 {
|
|
b.feats.Features |= driver.FeatureCompute
|
|
}
|
|
if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") {
|
|
b.feats.Features |= driver.FeatureTimers
|
|
}
|
|
b.feats.MaxTextureSize = f.GetInteger(gl.MAX_TEXTURE_SIZE)
|
|
return b, nil
|
|
}
|
|
|
|
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Framebuffer {
|
|
b.clear = clear
|
|
b.glstate = b.queryState()
|
|
b.savedState = b.glstate
|
|
b.state = state{}
|
|
var renderFBO gl.Framebuffer
|
|
if target != nil {
|
|
switch t := target.(type) {
|
|
case driver.OpenGLRenderTarget:
|
|
renderFBO = gl.Framebuffer(t)
|
|
case *framebuffer:
|
|
renderFBO = t.obj
|
|
default:
|
|
panic(fmt.Errorf("opengl: invalid render target type: %T", target))
|
|
}
|
|
}
|
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
|
|
if b.gles {
|
|
// If the output framebuffer is not in the sRGB colorspace already, emulate it.
|
|
var fbEncoding int
|
|
if !renderFBO.Valid() {
|
|
fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.BACK, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
|
|
} else {
|
|
fbEncoding = b.funcs.GetFramebufferAttachmentParameteri(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
|
|
}
|
|
if fbEncoding == gl.LINEAR && viewport != (image.Point{}) {
|
|
if b.sRGBFBO == nil {
|
|
sfbo, err := NewSRGBFBO(b.funcs, &b.glstate)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
b.sRGBFBO = sfbo
|
|
}
|
|
if err := b.sRGBFBO.Refresh(viewport); err != nil {
|
|
panic(err)
|
|
}
|
|
renderFBO = b.sRGBFBO.Framebuffer()
|
|
} else if b.sRGBFBO != nil {
|
|
b.sRGBFBO.Release()
|
|
b.sRGBFBO = nil
|
|
}
|
|
} else {
|
|
b.glstate.set(b.funcs, gl.FRAMEBUFFER_SRGB, true)
|
|
if !b.vertArray.Valid() {
|
|
b.vertArray = b.funcs.CreateVertexArray()
|
|
}
|
|
b.glstate.bindVertexArray(b.funcs, b.vertArray)
|
|
}
|
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
|
|
if b.sRGBFBO != nil && !clear {
|
|
b.clearOutput(0, 0, 0, 0)
|
|
}
|
|
return &framebuffer{backend: b, obj: renderFBO, foreign: true}
|
|
}
|
|
|
|
func (b *Backend) EndFrame() {
|
|
if b.sRGBFBO != nil {
|
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.savedState.drawFBO)
|
|
if b.clear {
|
|
b.SetBlend(false)
|
|
} else {
|
|
b.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha)
|
|
b.SetBlend(true)
|
|
}
|
|
b.sRGBFBO.Blit()
|
|
}
|
|
b.restoreState(b.savedState)
|
|
}
|
|
|
|
func (b *Backend) queryState() glState {
|
|
s := glState{
|
|
prog: gl.Program(b.funcs.GetBinding(gl.CURRENT_PROGRAM)),
|
|
arrayBuf: gl.Buffer(b.funcs.GetBinding(gl.ARRAY_BUFFER_BINDING)),
|
|
elemBuf: gl.Buffer(b.funcs.GetBinding(gl.ELEMENT_ARRAY_BUFFER_BINDING)),
|
|
drawFBO: gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING)),
|
|
clearColor: b.funcs.GetFloat4(gl.COLOR_CLEAR_VALUE),
|
|
viewport: b.funcs.GetInteger4(gl.VIEWPORT),
|
|
unpack_row_length: b.funcs.GetInteger(gl.UNPACK_ROW_LENGTH),
|
|
pack_row_length: b.funcs.GetInteger(gl.PACK_ROW_LENGTH),
|
|
}
|
|
s.blend.enable = b.funcs.IsEnabled(gl.BLEND)
|
|
s.blend.srcRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_RGB))
|
|
s.blend.dstRGB = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_RGB))
|
|
s.blend.srcA = gl.Enum(b.funcs.GetInteger(gl.BLEND_SRC_ALPHA))
|
|
s.blend.dstA = gl.Enum(b.funcs.GetInteger(gl.BLEND_DST_ALPHA))
|
|
s.texUnits.active = gl.Enum(b.funcs.GetInteger(gl.ACTIVE_TEXTURE))
|
|
if !b.gles {
|
|
s.srgb = b.funcs.IsEnabled(gl.FRAMEBUFFER_SRGB)
|
|
}
|
|
if !b.gles || b.glver[0] >= 3 {
|
|
s.vertArray = gl.VertexArray(b.funcs.GetBinding(gl.VERTEX_ARRAY_BINDING))
|
|
s.readFBO = gl.Framebuffer(b.funcs.GetBinding(gl.READ_FRAMEBUFFER_BINDING))
|
|
s.uniBuf = gl.Buffer(b.funcs.GetBinding(gl.UNIFORM_BUFFER_BINDING))
|
|
for i := range s.uniBufs {
|
|
s.uniBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.UNIFORM_BUFFER_BINDING, i))
|
|
}
|
|
}
|
|
if b.gles && (b.glver[0] > 3 || (b.glver[0] == 3 && b.glver[1] >= 1)) {
|
|
s.storeBuf = gl.Buffer(b.funcs.GetBinding(gl.SHADER_STORAGE_BUFFER_BINDING))
|
|
for i := range s.storeBufs {
|
|
s.storeBufs[i] = gl.Buffer(b.funcs.GetBindingi(gl.SHADER_STORAGE_BUFFER_BINDING, i))
|
|
}
|
|
}
|
|
for i := range s.texUnits.binds {
|
|
s.activeTexture(b.funcs, gl.TEXTURE0+gl.Enum(i))
|
|
s.texUnits.binds[i] = gl.Texture(b.funcs.GetBinding(gl.TEXTURE_BINDING_2D))
|
|
}
|
|
for i := range s.vertAttribs {
|
|
a := &s.vertAttribs[i]
|
|
a.enabled = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED) != gl.FALSE
|
|
a.obj = gl.Buffer(b.funcs.GetVertexAttribBinding(i, gl.VERTEX_ATTRIB_ARRAY_ENABLED))
|
|
a.size = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_SIZE)
|
|
a.typ = gl.Enum(b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_TYPE))
|
|
a.normalized = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_NORMALIZED) != gl.FALSE
|
|
a.stride = b.funcs.GetVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_STRIDE)
|
|
a.offset = b.funcs.GetVertexAttribPointer(i, gl.VERTEX_ATTRIB_ARRAY_POINTER)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (b *Backend) restoreState(dst glState) {
|
|
src := b.glstate
|
|
f := b.funcs
|
|
for i, unit := range dst.texUnits.binds {
|
|
src.bindTexture(f, i, unit)
|
|
}
|
|
src.activeTexture(f, dst.texUnits.active)
|
|
src.bindFramebuffer(f, gl.FRAMEBUFFER, dst.drawFBO)
|
|
src.bindFramebuffer(f, gl.READ_FRAMEBUFFER, dst.readFBO)
|
|
src.set(f, gl.BLEND, dst.blend.enable)
|
|
bf := dst.blend
|
|
src.setBlendFuncSeparate(f, bf.srcRGB, bf.dstRGB, bf.srcA, bf.dstA)
|
|
src.set(f, gl.FRAMEBUFFER_SRGB, dst.srgb)
|
|
src.bindVertexArray(f, dst.vertArray)
|
|
src.useProgram(f, dst.prog)
|
|
src.bindBuffer(f, gl.ELEMENT_ARRAY_BUFFER, dst.elemBuf)
|
|
for i, b := range dst.uniBufs {
|
|
src.bindBufferBase(f, gl.UNIFORM_BUFFER, i, b)
|
|
}
|
|
src.bindBuffer(f, gl.UNIFORM_BUFFER, dst.uniBuf)
|
|
for i, b := range dst.storeBufs {
|
|
src.bindBufferBase(f, gl.SHADER_STORAGE_BUFFER, i, b)
|
|
}
|
|
src.bindBuffer(f, gl.SHADER_STORAGE_BUFFER, dst.storeBuf)
|
|
col := dst.clearColor
|
|
src.setClearColor(f, col[0], col[1], col[2], col[3])
|
|
for i, attr := range dst.vertAttribs {
|
|
src.setVertexAttribArray(f, i, attr.enabled)
|
|
src.vertexAttribPointer(f, attr.obj, i, attr.size, attr.typ, attr.normalized, attr.stride, int(attr.offset))
|
|
}
|
|
src.bindBuffer(f, gl.ARRAY_BUFFER, dst.arrayBuf)
|
|
v := dst.viewport
|
|
src.setViewport(f, v[0], v[1], v[2], v[3])
|
|
src.pixelStorei(f, gl.UNPACK_ROW_LENGTH, dst.unpack_row_length)
|
|
src.pixelStorei(f, gl.PACK_ROW_LENGTH, dst.pack_row_length)
|
|
}
|
|
|
|
func (s *glState) setVertexAttribArray(f *gl.Functions, idx int, enabled bool) {
|
|
a := &s.vertAttribs[idx]
|
|
if enabled != a.enabled {
|
|
if enabled {
|
|
f.EnableVertexAttribArray(gl.Attrib(idx))
|
|
} else {
|
|
f.DisableVertexAttribArray(gl.Attrib(idx))
|
|
}
|
|
a.enabled = enabled
|
|
}
|
|
}
|
|
|
|
func (s *glState) vertexAttribPointer(f *gl.Functions, buf gl.Buffer, idx, size int, typ gl.Enum, normalized bool, stride, offset int) {
|
|
s.bindBuffer(f, gl.ARRAY_BUFFER, buf)
|
|
a := &s.vertAttribs[idx]
|
|
a.obj = buf
|
|
a.size = size
|
|
a.typ = typ
|
|
a.normalized = normalized
|
|
a.stride = stride
|
|
a.offset = uintptr(offset)
|
|
f.VertexAttribPointer(gl.Attrib(idx), a.size, a.typ, a.normalized, a.stride, int(a.offset))
|
|
}
|
|
|
|
func (s *glState) activeTexture(f *gl.Functions, unit gl.Enum) {
|
|
if unit != s.texUnits.active {
|
|
f.ActiveTexture(unit)
|
|
s.texUnits.active = unit
|
|
}
|
|
}
|
|
|
|
func (s *glState) bindRenderbuffer(f *gl.Functions, target gl.Enum, r gl.Renderbuffer) {
|
|
if !r.Equal(s.renderBuf) {
|
|
f.BindRenderbuffer(gl.RENDERBUFFER, r)
|
|
s.renderBuf = r
|
|
}
|
|
}
|
|
|
|
func (s *glState) bindTexture(f *gl.Functions, unit int, t gl.Texture) {
|
|
s.activeTexture(f, gl.TEXTURE0+gl.Enum(unit))
|
|
if !t.Equal(s.texUnits.binds[unit]) {
|
|
f.BindTexture(gl.TEXTURE_2D, t)
|
|
s.texUnits.binds[unit] = t
|
|
}
|
|
}
|
|
|
|
func (s *glState) bindVertexArray(f *gl.Functions, a gl.VertexArray) {
|
|
if !a.Equal(s.vertArray) {
|
|
f.BindVertexArray(a)
|
|
s.vertArray = a
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteRenderbuffer(f *gl.Functions, r gl.Renderbuffer) {
|
|
f.DeleteRenderbuffer(r)
|
|
if r.Equal(s.renderBuf) {
|
|
s.renderBuf = gl.Renderbuffer{}
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteFramebuffer(f *gl.Functions, fbo gl.Framebuffer) {
|
|
f.DeleteFramebuffer(fbo)
|
|
if fbo.Equal(s.drawFBO) {
|
|
s.drawFBO = gl.Framebuffer{}
|
|
}
|
|
if fbo.Equal(s.readFBO) {
|
|
s.readFBO = gl.Framebuffer{}
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteBuffer(f *gl.Functions, b gl.Buffer) {
|
|
f.DeleteBuffer(b)
|
|
if b.Equal(s.arrayBuf) {
|
|
s.arrayBuf = gl.Buffer{}
|
|
}
|
|
if b.Equal(s.elemBuf) {
|
|
s.elemBuf = gl.Buffer{}
|
|
}
|
|
if b.Equal(s.uniBuf) {
|
|
s.uniBuf = gl.Buffer{}
|
|
}
|
|
if b.Equal(s.storeBuf) {
|
|
s.uniBuf = gl.Buffer{}
|
|
}
|
|
for i, b2 := range s.storeBufs {
|
|
if b.Equal(b2) {
|
|
s.storeBufs[i] = gl.Buffer{}
|
|
}
|
|
}
|
|
for i, b2 := range s.uniBufs {
|
|
if b.Equal(b2) {
|
|
s.uniBufs[i] = gl.Buffer{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteProgram(f *gl.Functions, p gl.Program) {
|
|
f.DeleteProgram(p)
|
|
if p.Equal(s.prog) {
|
|
s.prog = gl.Program{}
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteVertexArray(f *gl.Functions, a gl.VertexArray) {
|
|
f.DeleteVertexArray(a)
|
|
if a.Equal(s.vertArray) {
|
|
s.vertArray = gl.VertexArray{}
|
|
}
|
|
}
|
|
|
|
func (s *glState) deleteTexture(f *gl.Functions, t gl.Texture) {
|
|
f.DeleteTexture(t)
|
|
binds := &s.texUnits.binds
|
|
for i, obj := range binds {
|
|
if t.Equal(obj) {
|
|
binds[i] = gl.Texture{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *glState) useProgram(f *gl.Functions, p gl.Program) {
|
|
if !p.Equal(s.prog) {
|
|
f.UseProgram(p)
|
|
s.prog = p
|
|
}
|
|
}
|
|
|
|
func (s *glState) bindFramebuffer(f *gl.Functions, target gl.Enum, fbo gl.Framebuffer) {
|
|
switch target {
|
|
case gl.FRAMEBUFFER:
|
|
if fbo.Equal(s.drawFBO) && fbo.Equal(s.readFBO) {
|
|
return
|
|
}
|
|
s.drawFBO = fbo
|
|
s.readFBO = fbo
|
|
case gl.READ_FRAMEBUFFER:
|
|
if fbo.Equal(s.readFBO) {
|
|
return
|
|
}
|
|
s.readFBO = fbo
|
|
case gl.DRAW_FRAMEBUFFER:
|
|
if fbo.Equal(s.drawFBO) {
|
|
return
|
|
}
|
|
s.drawFBO = fbo
|
|
default:
|
|
panic("unknown target")
|
|
}
|
|
f.BindFramebuffer(target, fbo)
|
|
}
|
|
|
|
func (s *glState) bindBufferBase(f *gl.Functions, target gl.Enum, idx int, buf gl.Buffer) {
|
|
switch target {
|
|
case gl.UNIFORM_BUFFER:
|
|
if buf.Equal(s.uniBuf) && buf.Equal(s.uniBufs[idx]) {
|
|
return
|
|
}
|
|
s.uniBuf = buf
|
|
s.uniBufs[idx] = buf
|
|
case gl.SHADER_STORAGE_BUFFER:
|
|
if buf.Equal(s.storeBuf) && buf.Equal(s.storeBufs[idx]) {
|
|
return
|
|
}
|
|
s.storeBuf = buf
|
|
s.storeBufs[idx] = buf
|
|
default:
|
|
panic("unknown buffer target")
|
|
}
|
|
f.BindBufferBase(target, idx, buf)
|
|
}
|
|
|
|
func (s *glState) bindBuffer(f *gl.Functions, target gl.Enum, buf gl.Buffer) {
|
|
switch target {
|
|
case gl.ARRAY_BUFFER:
|
|
if buf.Equal(s.arrayBuf) {
|
|
return
|
|
}
|
|
s.arrayBuf = buf
|
|
case gl.ELEMENT_ARRAY_BUFFER:
|
|
if buf.Equal(s.elemBuf) {
|
|
return
|
|
}
|
|
s.elemBuf = buf
|
|
case gl.UNIFORM_BUFFER:
|
|
if buf.Equal(s.uniBuf) {
|
|
return
|
|
}
|
|
s.uniBuf = buf
|
|
case gl.SHADER_STORAGE_BUFFER:
|
|
if buf.Equal(s.storeBuf) {
|
|
return
|
|
}
|
|
s.storeBuf = buf
|
|
default:
|
|
panic("unknown buffer target")
|
|
}
|
|
f.BindBuffer(target, buf)
|
|
}
|
|
|
|
func (s *glState) pixelStorei(f *gl.Functions, pname gl.Enum, val int) {
|
|
switch pname {
|
|
case gl.UNPACK_ROW_LENGTH:
|
|
if val == s.unpack_row_length {
|
|
return
|
|
}
|
|
s.unpack_row_length = val
|
|
case gl.PACK_ROW_LENGTH:
|
|
if val == s.pack_row_length {
|
|
return
|
|
}
|
|
s.pack_row_length = val
|
|
default:
|
|
panic("unsupported PixelStorei pname")
|
|
}
|
|
f.PixelStorei(pname, val)
|
|
}
|
|
|
|
func (s *glState) setClearColor(f *gl.Functions, r, g, b, a float32) {
|
|
col := [4]float32{r, g, b, a}
|
|
if col != s.clearColor {
|
|
f.ClearColor(r, g, b, a)
|
|
s.clearColor = col
|
|
}
|
|
}
|
|
|
|
func (s *glState) setViewport(f *gl.Functions, x, y, width, height int) {
|
|
view := [4]int{x, y, width, height}
|
|
if view != s.viewport {
|
|
f.Viewport(x, y, width, height)
|
|
s.viewport = view
|
|
}
|
|
}
|
|
|
|
func (s *glState) setBlendFuncSeparate(f *gl.Functions, srcRGB, dstRGB, srcA, dstA gl.Enum) {
|
|
if srcRGB != s.blend.srcRGB || dstRGB != s.blend.dstRGB || srcA != s.blend.srcA || dstA != s.blend.dstA {
|
|
s.blend.srcRGB = srcRGB
|
|
s.blend.dstRGB = dstRGB
|
|
s.blend.srcA = srcA
|
|
s.blend.dstA = dstA
|
|
f.BlendFuncSeparate(srcA, dstA, srcA, dstA)
|
|
}
|
|
}
|
|
|
|
func (s *glState) set(f *gl.Functions, target gl.Enum, enable bool) {
|
|
switch target {
|
|
case gl.FRAMEBUFFER_SRGB:
|
|
if s.srgb == enable {
|
|
return
|
|
}
|
|
s.srgb = enable
|
|
case gl.BLEND:
|
|
if enable == s.blend.enable {
|
|
return
|
|
}
|
|
s.blend.enable = enable
|
|
default:
|
|
panic("unknown enable")
|
|
}
|
|
if enable {
|
|
f.Enable(target)
|
|
} else {
|
|
f.Disable(target)
|
|
}
|
|
}
|
|
|
|
func (b *Backend) Caps() driver.Caps {
|
|
return b.feats
|
|
}
|
|
|
|
func (b *Backend) NewTimer() driver.Timer {
|
|
return &timer{
|
|
funcs: b.funcs,
|
|
obj: b.funcs.CreateQuery(),
|
|
}
|
|
}
|
|
|
|
func (b *Backend) IsTimeContinuous() bool {
|
|
return b.funcs.GetInteger(gl.GPU_DISJOINT_EXT) == gl.FALSE
|
|
}
|
|
|
|
func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error) {
|
|
glErr(b.funcs)
|
|
gltex := tex.(*texture)
|
|
fb := b.funcs.CreateFramebuffer()
|
|
fbo := &framebuffer{backend: b, obj: fb}
|
|
b.BindFramebuffer(fbo, driver.LoadDesc{})
|
|
if err := glErr(b.funcs); err != nil {
|
|
fbo.Release()
|
|
return nil, err
|
|
}
|
|
b.funcs.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gltex.obj, 0)
|
|
if st := b.funcs.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
|
fbo.Release()
|
|
return nil, fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError())
|
|
}
|
|
return fbo, nil
|
|
}
|
|
|
|
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, binding driver.BufferBinding) (driver.Texture, error) {
|
|
glErr(b.funcs)
|
|
tex := &texture{backend: b, obj: b.funcs.CreateTexture(), width: width, height: height}
|
|
switch format {
|
|
case driver.TextureFormatFloat:
|
|
tex.triple = b.floatTriple
|
|
case driver.TextureFormatSRGBA:
|
|
tex.triple = b.srgbaTriple
|
|
case driver.TextureFormatRGBA8:
|
|
tex.triple = textureTriple{gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE}
|
|
default:
|
|
return nil, errors.New("unsupported texture format")
|
|
}
|
|
b.BindTexture(0, tex)
|
|
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, toTexFilter(magFilter))
|
|
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, toTexFilter(minFilter))
|
|
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
if b.gles && b.glver[0] >= 3 {
|
|
// Immutable textures are required for BindImageTexture, and can't hurt otherwise.
|
|
b.funcs.TexStorage2D(gl.TEXTURE_2D, 1, tex.triple.internalFormat, width, height)
|
|
} else {
|
|
b.funcs.TexImage2D(gl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ)
|
|
}
|
|
if err := glErr(b.funcs); err != nil {
|
|
tex.Release()
|
|
return nil, err
|
|
}
|
|
return tex, nil
|
|
}
|
|
|
|
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
|
|
glErr(b.funcs)
|
|
buf := &buffer{backend: b, typ: typ, size: size}
|
|
if typ&driver.BufferBindingUniforms != 0 {
|
|
if typ != driver.BufferBindingUniforms {
|
|
return nil, errors.New("uniforms buffers cannot be bound as anything else")
|
|
}
|
|
if !b.ubo {
|
|
// GLES 2 doesn't support uniform buffers.
|
|
buf.data = make([]byte, size)
|
|
}
|
|
}
|
|
if typ&^driver.BufferBindingUniforms != 0 || b.ubo {
|
|
buf.hasBuffer = true
|
|
buf.obj = b.funcs.CreateBuffer()
|
|
if err := glErr(b.funcs); err != nil {
|
|
buf.Release()
|
|
return nil, err
|
|
}
|
|
firstBinding := firstBufferType(typ)
|
|
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
|
|
b.funcs.BufferData(firstBinding, size, gl.DYNAMIC_DRAW, nil)
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
|
|
glErr(b.funcs)
|
|
obj := b.funcs.CreateBuffer()
|
|
buf := &buffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
|
|
firstBinding := firstBufferType(typ)
|
|
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
|
|
b.funcs.BufferData(firstBinding, len(data), gl.STATIC_DRAW, data)
|
|
buf.immutable = true
|
|
if err := glErr(b.funcs); err != nil {
|
|
buf.Release()
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func glErr(f *gl.Functions) error {
|
|
if st := f.GetError(); st != gl.NO_ERROR {
|
|
return fmt.Errorf("glGetError: %#x", st)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) Release() {
|
|
if b.sRGBFBO != nil {
|
|
b.sRGBFBO.Release()
|
|
}
|
|
if b.vertArray.Valid() {
|
|
b.glstate.deleteVertexArray(b.funcs, b.vertArray)
|
|
}
|
|
*b = Backend{}
|
|
}
|
|
|
|
func (b *Backend) MemoryBarrier() {
|
|
b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
|
|
}
|
|
|
|
func (b *Backend) DispatchCompute(x, y, z int) {
|
|
for binding, buf := range b.storage {
|
|
if buf != nil {
|
|
b.glstate.bindBufferBase(b.funcs, gl.SHADER_STORAGE_BUFFER, binding, buf.obj)
|
|
}
|
|
}
|
|
b.funcs.DispatchCompute(x, y, z)
|
|
}
|
|
|
|
func (b *Backend) BindImageTexture(unit int, tex driver.Texture, access driver.AccessBits, f driver.TextureFormat) {
|
|
t := tex.(*texture)
|
|
var acc gl.Enum
|
|
switch access {
|
|
case driver.AccessWrite:
|
|
acc = gl.WRITE_ONLY
|
|
case driver.AccessRead:
|
|
acc = gl.READ_ONLY
|
|
case driver.AccessRead | driver.AccessWrite:
|
|
acc = gl.READ_WRITE
|
|
default:
|
|
panic("unsupported access bits")
|
|
}
|
|
var format gl.Enum
|
|
switch f {
|
|
case driver.TextureFormatRGBA8:
|
|
format = gl.RGBA8
|
|
default:
|
|
panic("unsupported format")
|
|
}
|
|
b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, format)
|
|
}
|
|
|
|
func (b *Backend) BlendFunc(sfactor, dfactor driver.BlendFactor) {
|
|
src, dst := toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)
|
|
b.glstate.setBlendFuncSeparate(b.funcs, src, dst, src, dst)
|
|
}
|
|
|
|
func toGLBlendFactor(f driver.BlendFactor) gl.Enum {
|
|
switch f {
|
|
case driver.BlendFactorOne:
|
|
return gl.ONE
|
|
case driver.BlendFactorOneMinusSrcAlpha:
|
|
return gl.ONE_MINUS_SRC_ALPHA
|
|
case driver.BlendFactorZero:
|
|
return gl.ZERO
|
|
case driver.BlendFactorDstColor:
|
|
return gl.DST_COLOR
|
|
default:
|
|
panic("unsupported blend factor")
|
|
}
|
|
}
|
|
|
|
func (b *Backend) SetBlend(enable bool) {
|
|
b.glstate.set(b.funcs, gl.BLEND, enable)
|
|
}
|
|
|
|
func (b *Backend) DrawElements(mode driver.DrawMode, off, count int) {
|
|
b.prepareDraw()
|
|
// off is in 16-bit indices, but DrawElements take a byte offset.
|
|
byteOff := off * 2
|
|
b.funcs.DrawElements(toGLDrawMode(mode), count, gl.UNSIGNED_SHORT, byteOff)
|
|
}
|
|
|
|
func (b *Backend) DrawArrays(mode driver.DrawMode, off, count int) {
|
|
b.prepareDraw()
|
|
b.funcs.DrawArrays(toGLDrawMode(mode), off, count)
|
|
}
|
|
|
|
func (b *Backend) prepareDraw() {
|
|
p := b.state.pipeline
|
|
if p == nil {
|
|
return
|
|
}
|
|
b.setupVertexArrays()
|
|
p.prog.updateUniforms()
|
|
}
|
|
|
|
func toGLDrawMode(mode driver.DrawMode) gl.Enum {
|
|
switch mode {
|
|
case driver.DrawModeTriangleStrip:
|
|
return gl.TRIANGLE_STRIP
|
|
case driver.DrawModeTriangles:
|
|
return gl.TRIANGLES
|
|
default:
|
|
panic("unsupported draw mode")
|
|
}
|
|
}
|
|
|
|
func (b *Backend) Viewport(x, y, width, height int) {
|
|
b.glstate.setViewport(b.funcs, x, y, width, height)
|
|
}
|
|
|
|
func (b *Backend) clearOutput(colR, colG, colB, colA float32) {
|
|
b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
|
|
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
|
|
}
|
|
|
|
func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
|
|
p, err := gl.CreateComputeProgram(b.funcs, src.GLSL310ES)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %v", src.Name, err)
|
|
}
|
|
return &program{
|
|
backend: b,
|
|
obj: p,
|
|
}, nil
|
|
}
|
|
|
|
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
|
|
glslSrc := b.glslFor(src)
|
|
sh, err := gl.CreateShader(b.funcs, gl.VERTEX_SHADER, glslSrc)
|
|
return &glshader{backend: b, obj: sh, src: src}, err
|
|
}
|
|
|
|
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
|
|
glslSrc := b.glslFor(src)
|
|
sh, err := gl.CreateShader(b.funcs, gl.FRAGMENT_SHADER, glslSrc)
|
|
return &glshader{backend: b, obj: sh, src: src}, err
|
|
}
|
|
|
|
func (b *Backend) glslFor(src shader.Sources) string {
|
|
if b.glver[0] < 3 {
|
|
return src.GLSL100ES
|
|
}
|
|
// OpenGL (ES) 3.0.
|
|
switch {
|
|
case b.gles:
|
|
return src.GLSL300ES
|
|
case b.glver[0] >= 4 || b.glver[1] >= 2:
|
|
// OpenGL 3.2 Core only accepts glsl 1.50 or newer.
|
|
return src.GLSL150
|
|
default:
|
|
return src.GLSL130
|
|
}
|
|
}
|
|
|
|
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
|
|
p, err := b.newProgram(desc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
layout := desc.VertexLayout
|
|
vsrc := desc.VertexShader.(*glshader).src
|
|
if len(vsrc.Inputs) != len(layout.Inputs) {
|
|
return nil, fmt.Errorf("opengl: got %d inputs, expected %d", len(layout.Inputs), len(vsrc.Inputs))
|
|
}
|
|
for i, inp := range vsrc.Inputs {
|
|
if exp, got := inp.Size, layout.Inputs[i].Size; exp != got {
|
|
return nil, fmt.Errorf("opengl: data size mismatch for %q: got %d expected %d", inp.Name, got, exp)
|
|
}
|
|
}
|
|
return &pipeline{
|
|
prog: p,
|
|
inputs: vsrc.Inputs,
|
|
layout: layout,
|
|
blend: desc.BlendDesc,
|
|
}, nil
|
|
}
|
|
|
|
func (b *Backend) newProgram(desc driver.PipelineDesc) (*program, error) {
|
|
p := b.funcs.CreateProgram()
|
|
if !p.Valid() {
|
|
return nil, errors.New("opengl: glCreateProgram failed")
|
|
}
|
|
vsh, fsh := desc.VertexShader.(*glshader), desc.FragmentShader.(*glshader)
|
|
b.funcs.AttachShader(p, vsh.obj)
|
|
b.funcs.AttachShader(p, fsh.obj)
|
|
for _, inp := range vsh.src.Inputs {
|
|
b.funcs.BindAttribLocation(p, gl.Attrib(inp.Location), inp.Name)
|
|
}
|
|
b.funcs.LinkProgram(p)
|
|
if b.funcs.GetProgrami(p, gl.LINK_STATUS) == 0 {
|
|
log := b.funcs.GetProgramInfoLog(p)
|
|
b.funcs.DeleteProgram(p)
|
|
return nil, fmt.Errorf("opengl: program link failed: %s", strings.TrimSpace(log))
|
|
}
|
|
prog := &program{
|
|
backend: b,
|
|
obj: p,
|
|
}
|
|
b.glstate.useProgram(b.funcs, p)
|
|
// Bind texture uniforms.
|
|
for _, tex := range vsh.src.Textures {
|
|
u := b.funcs.GetUniformLocation(p, tex.Name)
|
|
if u.Valid() {
|
|
b.funcs.Uniform1i(u, tex.Binding)
|
|
}
|
|
}
|
|
for _, tex := range fsh.src.Textures {
|
|
u := b.funcs.GetUniformLocation(p, tex.Name)
|
|
if u.Valid() {
|
|
b.funcs.Uniform1i(u, tex.Binding)
|
|
}
|
|
}
|
|
if b.ubo {
|
|
for _, block := range vsh.src.Uniforms.Blocks {
|
|
blockIdx := b.funcs.GetUniformBlockIndex(p, block.Name)
|
|
if blockIdx != gl.INVALID_INDEX {
|
|
b.funcs.UniformBlockBinding(p, blockIdx, uint(block.Binding))
|
|
}
|
|
}
|
|
// To match Direct3D 11 with separate vertex and fragment
|
|
// shader uniform buffers, offset all fragment blocks to be
|
|
// located after the vertex blocks.
|
|
off := len(vsh.src.Uniforms.Blocks)
|
|
for _, block := range fsh.src.Uniforms.Blocks {
|
|
blockIdx := b.funcs.GetUniformBlockIndex(p, block.Name)
|
|
if blockIdx != gl.INVALID_INDEX {
|
|
b.funcs.UniformBlockBinding(p, blockIdx, uint(block.Binding+off))
|
|
}
|
|
}
|
|
} else {
|
|
prog.vertUniforms.setup(b.funcs, p, vsh.src.Uniforms.Size, vsh.src.Uniforms.Locations)
|
|
prog.fragUniforms.setup(b.funcs, p, fsh.src.Uniforms.Size, fsh.src.Uniforms.Locations)
|
|
}
|
|
return prog, nil
|
|
}
|
|
|
|
func lookupUniform(funcs *gl.Functions, p gl.Program, loc shader.UniformLocation) uniformLocation {
|
|
u := funcs.GetUniformLocation(p, loc.Name)
|
|
if !u.Valid() {
|
|
panic(fmt.Errorf("uniform %q not found", loc.Name))
|
|
}
|
|
return uniformLocation{uniform: u, offset: loc.Offset, typ: loc.Type, size: loc.Size}
|
|
}
|
|
|
|
func (b *Backend) BindStorageBuffer(binding int, buf driver.Buffer) {
|
|
bf := buf.(*buffer)
|
|
if bf.typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) == 0 {
|
|
panic("not a shader storage buffer")
|
|
}
|
|
b.storage[binding] = bf
|
|
}
|
|
|
|
func (b *Backend) BindVertexUniforms(buf driver.Buffer) {
|
|
bf := buf.(*buffer)
|
|
if bf.typ&driver.BufferBindingUniforms == 0 {
|
|
panic("not a uniform buffer")
|
|
}
|
|
b.vertUniforms = bf
|
|
}
|
|
|
|
func (b *Backend) BindFragmentUniforms(buf driver.Buffer) {
|
|
bf := buf.(*buffer)
|
|
if bf.typ&driver.BufferBindingUniforms == 0 {
|
|
panic("not a uniform buffer")
|
|
}
|
|
b.fragUniforms = bf
|
|
}
|
|
|
|
func (p *program) updateUniforms() {
|
|
f := p.backend.funcs
|
|
if b := p.backend.vertUniforms; b != nil {
|
|
if p.backend.ubo {
|
|
p.backend.glstate.bindBufferBase(f, gl.UNIFORM_BUFFER, 0, b.obj)
|
|
} else {
|
|
p.vertUniforms.update(f, b)
|
|
}
|
|
}
|
|
if b := p.backend.fragUniforms; b != nil {
|
|
if p.backend.ubo {
|
|
p.backend.glstate.bindBufferBase(f, gl.UNIFORM_BUFFER, 1, b.obj)
|
|
} else {
|
|
p.fragUniforms.update(f, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Backend) BindProgram(prog driver.Program) {
|
|
p := prog.(*program)
|
|
b.glstate.useProgram(b.funcs, p.obj)
|
|
}
|
|
|
|
func (s *glshader) Release() {
|
|
s.backend.funcs.DeleteShader(s.obj)
|
|
}
|
|
|
|
func (p *program) Release() {
|
|
p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
|
|
}
|
|
|
|
func (u *uniforms) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []shader.UniformLocation) {
|
|
u.locs = make([]uniformLocation, len(uniforms))
|
|
for i, uniform := range uniforms {
|
|
u.locs[i] = lookupUniform(funcs, p, uniform)
|
|
}
|
|
u.size = uniformSize
|
|
}
|
|
|
|
func (p *uniforms) update(funcs *gl.Functions, buf *buffer) {
|
|
if buf.size < p.size {
|
|
panic(fmt.Errorf("uniform buffer too small, got %d need %d", buf.size, p.size))
|
|
}
|
|
data := buf.data
|
|
for _, u := range p.locs {
|
|
data := data[u.offset:]
|
|
switch {
|
|
case u.typ == shader.DataTypeFloat && u.size == 1:
|
|
data := data[:4]
|
|
v := *(*[1]float32)(unsafe.Pointer(&data[0]))
|
|
funcs.Uniform1f(u.uniform, v[0])
|
|
case u.typ == shader.DataTypeFloat && u.size == 2:
|
|
data := data[:8]
|
|
v := *(*[2]float32)(unsafe.Pointer(&data[0]))
|
|
funcs.Uniform2f(u.uniform, v[0], v[1])
|
|
case u.typ == shader.DataTypeFloat && u.size == 3:
|
|
data := data[:12]
|
|
v := *(*[3]float32)(unsafe.Pointer(&data[0]))
|
|
funcs.Uniform3f(u.uniform, v[0], v[1], v[2])
|
|
case u.typ == shader.DataTypeFloat && u.size == 4:
|
|
data := data[:16]
|
|
v := *(*[4]float32)(unsafe.Pointer(&data[0]))
|
|
funcs.Uniform4f(u.uniform, v[0], v[1], v[2], v[3])
|
|
default:
|
|
panic("unsupported uniform data type or size")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *buffer) Upload(data []byte) {
|
|
if b.immutable {
|
|
panic("immutable buffer")
|
|
}
|
|
if len(data) > b.size {
|
|
panic("buffer size overflow")
|
|
}
|
|
copy(b.data, data)
|
|
if b.hasBuffer {
|
|
firstBinding := firstBufferType(b.typ)
|
|
b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
|
|
if len(data) == b.size {
|
|
// the iOS GL implementation doesn't recognize when BufferSubData
|
|
// clears the entire buffer. Tell it and avoid GPU stalls.
|
|
// See also https://github.com/godotengine/godot/issues/23956.
|
|
b.backend.funcs.BufferData(firstBinding, b.size, gl.DYNAMIC_DRAW, data)
|
|
} else {
|
|
b.backend.funcs.BufferSubData(firstBinding, 0, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *buffer) Download(data []byte) error {
|
|
if len(data) > b.size {
|
|
panic("buffer size overflow")
|
|
}
|
|
if !b.hasBuffer {
|
|
copy(data, b.data)
|
|
return nil
|
|
}
|
|
firstBinding := firstBufferType(b.typ)
|
|
b.backend.glstate.bindBuffer(b.backend.funcs, firstBinding, b.obj)
|
|
bufferMap := b.backend.funcs.MapBufferRange(firstBinding, 0, len(data), gl.MAP_READ_BIT)
|
|
if bufferMap == nil {
|
|
return fmt.Errorf("MapBufferRange: error %#x", b.backend.funcs.GetError())
|
|
}
|
|
copy(data, bufferMap)
|
|
if !b.backend.funcs.UnmapBuffer(firstBinding) {
|
|
return driver.ErrContentLost
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *buffer) Release() {
|
|
if b.hasBuffer {
|
|
b.backend.glstate.deleteBuffer(b.backend.funcs, b.obj)
|
|
b.hasBuffer = false
|
|
}
|
|
}
|
|
|
|
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
|
|
gbuf := buf.(*buffer)
|
|
if gbuf.typ&driver.BufferBindingVertices == 0 {
|
|
panic("not a vertex buffer")
|
|
}
|
|
b.state.buffer = bufferBinding{obj: gbuf.obj, offset: offset}
|
|
}
|
|
|
|
func (b *Backend) setupVertexArrays() {
|
|
p := b.state.pipeline
|
|
inputs := p.inputs
|
|
if len(inputs) == 0 {
|
|
return
|
|
}
|
|
layout := p.layout
|
|
const max = len(b.glstate.vertAttribs)
|
|
var enabled [max]bool
|
|
buf := b.state.buffer
|
|
for i, inp := range inputs {
|
|
l := layout.Inputs[i]
|
|
var gltyp gl.Enum
|
|
switch l.Type {
|
|
case shader.DataTypeFloat:
|
|
gltyp = gl.FLOAT
|
|
case shader.DataTypeShort:
|
|
gltyp = gl.SHORT
|
|
default:
|
|
panic("unsupported data type")
|
|
}
|
|
enabled[inp.Location] = true
|
|
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, p.layout.Stride, buf.offset+l.Offset)
|
|
}
|
|
for i := 0; i < max; i++ {
|
|
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
|
|
}
|
|
}
|
|
|
|
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
|
|
gbuf := buf.(*buffer)
|
|
if gbuf.typ&driver.BufferBindingIndices == 0 {
|
|
panic("not an index buffer")
|
|
}
|
|
b.glstate.bindBuffer(b.funcs, gl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
|
|
}
|
|
|
|
func (b *Backend) CopyTexture(dst driver.Texture, dstOrigin image.Point, src driver.Framebuffer, srcRect image.Rectangle) {
|
|
const unit = 0
|
|
oldTex := b.glstate.texUnits.binds[unit]
|
|
defer func() {
|
|
b.glstate.bindTexture(b.funcs, unit, oldTex)
|
|
}()
|
|
b.glstate.bindTexture(b.funcs, unit, dst.(*texture).obj)
|
|
b.glstate.bindFramebuffer(b.funcs, gl.READ_FRAMEBUFFER, src.(*framebuffer).obj)
|
|
sz := srcRect.Size()
|
|
b.funcs.CopyTexSubImage2D(gl.TEXTURE_2D, 0, dstOrigin.X, dstOrigin.Y, srcRect.Min.X, srcRect.Min.Y, sz.X, sz.Y)
|
|
}
|
|
|
|
func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
|
|
glErr(f.backend.funcs)
|
|
f.backend.BindFramebuffer(f, driver.LoadDesc{})
|
|
if len(pixels) < src.Dx()*src.Dy()*4 {
|
|
return errors.New("unexpected RGBA size")
|
|
}
|
|
w, h := src.Dx(), src.Dy()
|
|
// WebGL 1 doesn't support PACK_ROW_LENGTH != 0. Avoid it if possible.
|
|
rowLen := 0
|
|
if n := stride / 4; n != w {
|
|
rowLen = n
|
|
}
|
|
f.backend.glstate.pixelStorei(f.backend.funcs, gl.PACK_ROW_LENGTH, rowLen)
|
|
f.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
|
return glErr(f.backend.funcs)
|
|
}
|
|
|
|
func (b *Backend) BindPipeline(pl driver.Pipeline) {
|
|
p := pl.(*pipeline)
|
|
b.state.pipeline = p
|
|
b.glstate.useProgram(b.funcs, p.prog.obj)
|
|
b.SetBlend(p.blend.Enable)
|
|
b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
|
|
}
|
|
|
|
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, desc driver.LoadDesc) {
|
|
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj)
|
|
switch desc.Action {
|
|
case driver.LoadActionClear:
|
|
c := desc.ClearColor
|
|
b.clearOutput(c.R, c.G, c.B, c.A)
|
|
case driver.LoadActionInvalidate:
|
|
b.funcs.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
|
|
}
|
|
}
|
|
|
|
func (f *framebuffer) Release() {
|
|
if f.foreign {
|
|
panic("framebuffer not created by NewFramebuffer")
|
|
}
|
|
f.backend.glstate.deleteFramebuffer(f.backend.funcs, f.obj)
|
|
}
|
|
|
|
func (f *framebuffer) ImplementsRenderTarget() {}
|
|
|
|
func (p *pipeline) Release() {
|
|
p.prog.Release()
|
|
*p = pipeline{}
|
|
}
|
|
|
|
func toTexFilter(f driver.TextureFilter) int {
|
|
switch f {
|
|
case driver.FilterNearest:
|
|
return gl.NEAREST
|
|
case driver.FilterLinear:
|
|
return gl.LINEAR
|
|
default:
|
|
panic("unsupported texture filter")
|
|
}
|
|
}
|
|
|
|
func (b *Backend) BindTexture(unit int, t driver.Texture) {
|
|
b.glstate.bindTexture(b.funcs, unit, t.(*texture).obj)
|
|
}
|
|
|
|
func (t *texture) Release() {
|
|
t.backend.glstate.deleteTexture(t.backend.funcs, t.obj)
|
|
}
|
|
|
|
func (t *texture) Upload(offset, size image.Point, pixels []byte, stride int) {
|
|
if min := size.X * size.Y * 4; min > len(pixels) {
|
|
panic(fmt.Errorf("size %d larger than data %d", min, len(pixels)))
|
|
}
|
|
t.backend.BindTexture(0, t)
|
|
// WebGL 1 doesn't support UNPACK_ROW_LENGTH != 0. Avoid it if possible.
|
|
rowLen := 0
|
|
if n := stride / 4; n != size.X {
|
|
rowLen = n
|
|
}
|
|
t.backend.glstate.pixelStorei(t.backend.funcs, gl.UNPACK_ROW_LENGTH, rowLen)
|
|
t.backend.funcs.TexSubImage2D(gl.TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels)
|
|
}
|
|
|
|
func (t *timer) Begin() {
|
|
t.funcs.BeginQuery(gl.TIME_ELAPSED_EXT, t.obj)
|
|
}
|
|
|
|
func (t *timer) End() {
|
|
t.funcs.EndQuery(gl.TIME_ELAPSED_EXT)
|
|
}
|
|
|
|
func (t *timer) ready() bool {
|
|
return t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT_AVAILABLE) == gl.TRUE
|
|
}
|
|
|
|
func (t *timer) Release() {
|
|
t.funcs.DeleteQuery(t.obj)
|
|
}
|
|
|
|
func (t *timer) Duration() (time.Duration, bool) {
|
|
if !t.ready() {
|
|
return 0, false
|
|
}
|
|
nanos := t.funcs.GetQueryObjectuiv(t.obj, gl.QUERY_RESULT)
|
|
return time.Duration(nanos), true
|
|
}
|
|
|
|
// floatTripleFor determines the best texture triple for floating point FBOs.
|
|
func floatTripleFor(f *gl.Functions, ver [2]int, exts []string) (textureTriple, error) {
|
|
var triples []textureTriple
|
|
if ver[0] >= 3 {
|
|
triples = append(triples, textureTriple{gl.R16F, gl.Enum(gl.RED), gl.Enum(gl.HALF_FLOAT)})
|
|
}
|
|
// According to the OES_texture_half_float specification, EXT_color_buffer_half_float is needed to
|
|
// render to FBOs. However, the Safari WebGL1 implementation does support half-float FBOs but does not
|
|
// report EXT_color_buffer_half_float support. The triples are verified below, so it doesn't matter if we're
|
|
// wrong.
|
|
if hasExtension(exts, "GL_OES_texture_half_float") || hasExtension(exts, "GL_EXT_color_buffer_half_float") {
|
|
// Try single channel.
|
|
triples = append(triples, textureTriple{gl.LUMINANCE, gl.Enum(gl.LUMINANCE), gl.Enum(gl.HALF_FLOAT_OES)})
|
|
// Fallback to 4 channels.
|
|
triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.HALF_FLOAT_OES)})
|
|
}
|
|
if hasExtension(exts, "GL_OES_texture_float") || hasExtension(exts, "GL_EXT_color_buffer_float") {
|
|
triples = append(triples, textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.FLOAT)})
|
|
}
|
|
tex := f.CreateTexture()
|
|
defer f.DeleteTexture(tex)
|
|
defTex := gl.Texture(f.GetBinding(gl.TEXTURE_BINDING_2D))
|
|
defer f.BindTexture(gl.TEXTURE_2D, defTex)
|
|
f.BindTexture(gl.TEXTURE_2D, tex)
|
|
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
fbo := f.CreateFramebuffer()
|
|
defer f.DeleteFramebuffer(fbo)
|
|
defFBO := gl.Framebuffer(f.GetBinding(gl.FRAMEBUFFER_BINDING))
|
|
f.BindFramebuffer(gl.FRAMEBUFFER, fbo)
|
|
defer f.BindFramebuffer(gl.FRAMEBUFFER, defFBO)
|
|
var attempts []string
|
|
for _, tt := range triples {
|
|
const size = 256
|
|
f.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ)
|
|
f.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
|
|
st := f.CheckFramebufferStatus(gl.FRAMEBUFFER)
|
|
if st == gl.FRAMEBUFFER_COMPLETE {
|
|
return tt, nil
|
|
}
|
|
attempts = append(attempts, fmt.Sprintf("(0x%x, 0x%x, 0x%x): 0x%x", tt.internalFormat, tt.format, tt.typ, st))
|
|
}
|
|
return textureTriple{}, fmt.Errorf("floating point fbos not supported (attempted %s)", attempts)
|
|
}
|
|
|
|
func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) {
|
|
switch {
|
|
case ver[0] >= 3:
|
|
return textureTriple{gl.SRGB8_ALPHA8, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}, nil
|
|
case hasExtension(exts, "GL_EXT_sRGB"):
|
|
return textureTriple{gl.SRGB_ALPHA_EXT, gl.Enum(gl.SRGB_ALPHA_EXT), gl.Enum(gl.UNSIGNED_BYTE)}, nil
|
|
default:
|
|
return textureTriple{}, errors.New("no sRGB texture formats found")
|
|
}
|
|
}
|
|
|
|
func alphaTripleFor(ver [2]int) textureTriple {
|
|
intf, f := gl.Enum(gl.R8), gl.Enum(gl.RED)
|
|
if ver[0] < 3 {
|
|
// R8, RED not supported on OpenGL ES 2.0.
|
|
intf, f = gl.LUMINANCE, gl.Enum(gl.LUMINANCE)
|
|
}
|
|
return textureTriple{intf, f, gl.UNSIGNED_BYTE}
|
|
}
|
|
|
|
func hasExtension(exts []string, ext string) bool {
|
|
for _, e := range exts {
|
|
if ext == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func firstBufferType(typ driver.BufferBinding) gl.Enum {
|
|
switch {
|
|
case typ&driver.BufferBindingIndices != 0:
|
|
return gl.ELEMENT_ARRAY_BUFFER
|
|
case typ&driver.BufferBindingVertices != 0:
|
|
return gl.ARRAY_BUFFER
|
|
case typ&driver.BufferBindingUniforms != 0:
|
|
return gl.UNIFORM_BUFFER
|
|
case typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0:
|
|
return gl.SHADER_STORAGE_BUFFER
|
|
default:
|
|
panic("unsupported buffer type")
|
|
}
|
|
}
|