gpu/internal/opengl: restore BeginFrame state in EndFrame

To ease the integration with foreign OpenGL contexts, carefully save the
context state before rendering a frame and restore it afterwards. Gio
rendering can then be mixed with OpenGL code that expects exclusive
control over context state.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-06-10 20:14:32 +02:00
parent 200957f924
commit 9b5e9ae607
9 changed files with 813 additions and 188 deletions
+2
View File
@@ -365,6 +365,8 @@ func New(api API) (GPU, error) {
if err != nil {
return nil, err
}
d.BeginFrame(false, image.Point{})
defer d.EndFrame()
forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
feats := d.Caps().Features
switch {
+476 -110
View File
@@ -12,15 +12,16 @@ import (
"gioui.org/gpu/internal/driver"
"gioui.org/internal/gl"
"gioui.org/internal/srgb"
)
// Backend implements driver.Device.
type Backend struct {
funcs *gl.Functions
clear bool
state glstate
clear bool
glstate glState
state state
savedState glState
glver [2]int
gles bool
@@ -33,27 +34,61 @@ type Backend struct {
alphaTriple textureTriple
srgbaTriple textureTriple
sRGBFBO *srgb.FBO
enabledSRGB bool
defFBO gl.Framebuffer
sRGBFBO *SRGBFBO
// defVertArray is bound during a frame. We don't need it, but
// vertArray is bound during a frame. We don't need it, but
// core desktop OpenGL profile 3.3 requires some array bound.
defVertArray gl.VertexArray
vertArray gl.VertexArray
}
// State tracking.
type glstate struct {
// nattr is the current number of enabled vertex arrays.
nattr int
prog *gpuProgram
texUnits [4]*gpuTexture
layout *gpuInputLayout
buffer bufferBinding
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
depthMask bool
depthFunc gl.Enum
srgb bool
blend struct {
enable bool
srcRGB, dstRGB gl.Enum
srcA, dstA gl.Enum
}
depthTest bool
clearColor [4]float32
clearDepth float32
viewport [4]int
}
type state struct {
prog *gpuProgram
layout *gpuInputLayout
buffer bufferBinding
}
type bufferBinding struct {
buf *gpuBuffer
obj gl.Buffer
offset int
stride int
}
@@ -94,7 +129,6 @@ type gpuBuffer struct {
type gpuProgram struct {
backend *Backend
obj gl.Program
nattr int
vertUniforms uniformsTracker
fragUniforms uniformsTracker
storage [storageBindings]*gpuBuffer
@@ -179,21 +213,21 @@ func newOpenGLDevice(api driver.OpenGL) (driver.Device, error) {
func (b *Backend) BeginFrame(clear bool, viewport image.Point) driver.Framebuffer {
b.clear = clear
// Assume GL state is reset between frames.
b.state = glstate{}
b.defFBO = gl.Framebuffer(b.funcs.GetBinding(gl.FRAMEBUFFER_BINDING))
renderFBO := b.defFBO
b.glstate = b.queryState()
b.savedState = b.glstate
b.state = state{}
renderFBO := b.glstate.drawFBO
if b.gles {
// If the output framebuffer is not in the sRGB colorspace already, emulate it.
var fbEncoding int
if !b.defFBO.Valid() {
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 {
if fbEncoding == gl.LINEAR && viewport != (image.Point{}) {
if b.sRGBFBO == nil {
sfbo, err := srgb.New(b.funcs)
sfbo, err := NewSRGBFBO(b.funcs, &b.glstate)
if err != nil {
panic(err)
}
@@ -205,16 +239,13 @@ func (b *Backend) BeginFrame(clear bool, viewport image.Point) driver.Framebuffe
renderFBO = b.sRGBFBO.Framebuffer()
}
} else {
b.enabledSRGB = !b.funcs.IsEnabled(gl.FRAMEBUFFER_SRGB)
if b.enabledSRGB {
b.funcs.Enable(gl.FRAMEBUFFER_SRGB)
b.glstate.set(b.funcs, gl.FRAMEBUFFER_SRGB, true)
if !b.vertArray.Valid() {
b.vertArray = b.funcs.CreateVertexArray()
}
if !b.defVertArray.Valid() {
b.defVertArray = b.funcs.CreateVertexArray()
}
b.funcs.BindVertexArray(b.defVertArray)
b.glstate.bindVertexArray(b.funcs, b.vertArray)
}
b.funcs.BindFramebuffer(gl.FRAMEBUFFER, renderFBO)
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
if b.sRGBFBO != nil && !clear {
b.Clear(0, 0, 0, 0)
}
@@ -222,9 +253,8 @@ func (b *Backend) BeginFrame(clear bool, viewport image.Point) driver.Framebuffe
}
func (b *Backend) EndFrame() {
b.funcs.ActiveTexture(gl.TEXTURE0)
if b.sRGBFBO != nil {
b.funcs.BindFramebuffer(gl.FRAMEBUFFER, b.defFBO)
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.savedState.drawFBO)
if b.clear {
b.SetBlend(false)
} else {
@@ -233,15 +263,376 @@ func (b *Backend) EndFrame() {
}
b.sRGBFBO.Blit()
}
b.SetBlend(false)
b.funcs.BindFramebuffer(gl.FRAMEBUFFER, b.defFBO)
if b.enabledSRGB {
b.funcs.Disable(gl.FRAMEBUFFER_SRGB)
}
b.restoreState(b.savedState)
// For single-buffered framebuffers such as on macOS.
b.funcs.Flush()
}
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)),
depthMask: b.funcs.GetInteger(gl.DEPTH_WRITEMASK) != gl.FALSE,
depthTest: b.funcs.IsEnabled(gl.DEPTH_TEST),
depthFunc: gl.Enum(b.funcs.GetInteger(gl.DEPTH_FUNC)),
clearDepth: b.funcs.GetFloat(gl.DEPTH_CLEAR_VALUE),
clearColor: b.funcs.GetFloat4(gl.COLOR_CLEAR_VALUE),
viewport: b.funcs.GetInteger4(gl.VIEWPORT),
}
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.DEPTH_TEST, dst.depthTest)
src.setDepthFunc(f, dst.depthFunc)
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)
src.setDepthMask(f, dst.depthMask)
src.setClearDepth(f, dst.clearDepth)
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])
}
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) setClearDepth(f *gl.Functions, d float32) {
if d != s.clearDepth {
f.ClearDepthf(d)
s.clearDepth = d
}
}
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) setDepthFunc(f *gl.Functions, df gl.Enum) {
if df != s.depthFunc {
f.DepthFunc(df)
s.depthFunc = df
}
}
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) setDepthMask(f *gl.Functions, enable bool) {
if enable != s.depthMask {
f.DepthMask(enable)
s.depthMask = enable
}
}
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
case gl.DEPTH_TEST:
if enable == s.depthTest {
return
}
s.depthTest = enable
default:
panic("unknown enable")
}
if enable {
f.Enable(target)
} else {
f.Disable(target)
}
}
func (b *Backend) Caps() driver.Caps {
return b.feats
}
@@ -277,7 +668,7 @@ func (b *Backend) NewFramebuffer(tex driver.Texture, depthBits int) (driver.Fram
size = gl.DEPTH_COMPONENT24
}
depthBuf := b.funcs.CreateRenderbuffer()
b.funcs.BindRenderbuffer(gl.RENDERBUFFER, depthBuf)
b.glstate.bindRenderbuffer(b.funcs, gl.RENDERBUFFER, depthBuf)
b.funcs.RenderbufferStorage(gl.RENDERBUFFER, size, gltex.width, gltex.height)
b.funcs.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuf)
fbo.depthBuf = depthBuf
@@ -345,7 +736,7 @@ func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer,
return nil, err
}
firstBinding := firstBufferType(typ)
b.funcs.BindBuffer(firstBinding, buf.obj)
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
b.funcs.BufferData(firstBinding, size, gl.DYNAMIC_DRAW)
}
return buf, nil
@@ -356,7 +747,7 @@ func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (dri
obj := b.funcs.CreateBuffer()
buf := &gpuBuffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
firstBinding := firstBufferType(typ)
b.funcs.BindBuffer(firstBinding, buf.obj)
b.glstate.bindBuffer(b.funcs, firstBinding, buf.obj)
b.funcs.BufferData(firstBinding, len(data), gl.STATIC_DRAW)
buf.Upload(data)
buf.immutable = true
@@ -378,8 +769,8 @@ func (b *Backend) Release() {
if b.sRGBFBO != nil {
b.sRGBFBO.Release()
}
if b.defVertArray.Valid() {
b.funcs.DeleteVertexArray(b.defVertArray)
if b.vertArray.Valid() {
b.glstate.deleteVertexArray(b.funcs, b.vertArray)
}
*b = Backend{}
}
@@ -392,7 +783,7 @@ func (b *Backend) DispatchCompute(x, y, z int) {
if p := b.state.prog; p != nil {
for binding, buf := range p.storage {
if buf != nil {
b.funcs.BindBufferBase(gl.SHADER_STORAGE_BUFFER, binding, buf.obj)
b.glstate.bindBufferBase(b.funcs, gl.SHADER_STORAGE_BUFFER, binding, buf.obj)
}
}
}
@@ -420,43 +811,18 @@ func (b *Backend) BindImageTexture(unit int, tex driver.Texture, access driver.A
b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, format)
}
func (b *Backend) bindTexture(unit int, t *gpuTexture) {
if b.state.texUnits[unit] != t {
b.funcs.ActiveTexture(gl.TEXTURE0 + gl.Enum(unit))
b.funcs.BindTexture(gl.TEXTURE_2D, t.obj)
b.state.texUnits[unit] = t
}
}
func (b *Backend) useProgram(p *gpuProgram) {
if b.state.prog != p {
p.backend.funcs.UseProgram(p.obj)
b.state.prog = p
}
}
func (b *Backend) enableVertexArrays(n int) {
// Enable needed arrays.
for i := b.state.nattr; i < n; i++ {
b.funcs.EnableVertexAttribArray(gl.Attrib(i))
}
// Disable extra arrays.
for i := n; i < b.state.nattr; i++ {
b.funcs.DisableVertexAttribArray(gl.Attrib(i))
}
b.state.nattr = n
b.glstate.useProgram(b.funcs, p.obj)
b.state.prog = p
}
func (b *Backend) SetDepthTest(enable bool) {
if enable {
b.funcs.Enable(gl.DEPTH_TEST)
} else {
b.funcs.Disable(gl.DEPTH_TEST)
}
b.glstate.set(b.funcs, gl.DEPTH_TEST, enable)
}
func (b *Backend) BlendFunc(sfactor, dfactor driver.BlendFactor) {
b.funcs.BlendFunc(toGLBlendFactor(sfactor), toGLBlendFactor(dfactor))
src, dst := toGLBlendFactor(sfactor), toGLBlendFactor(dfactor)
b.glstate.setBlendFuncSeparate(b.funcs, src, dst, src, dst)
}
func toGLBlendFactor(f driver.BlendFactor) gl.Enum {
@@ -475,15 +841,11 @@ func toGLBlendFactor(f driver.BlendFactor) gl.Enum {
}
func (b *Backend) DepthMask(mask bool) {
b.funcs.DepthMask(mask)
b.glstate.setDepthMask(b.funcs, mask)
}
func (b *Backend) SetBlend(enable bool) {
if enable {
b.funcs.Enable(gl.BLEND)
} else {
b.funcs.Disable(gl.BLEND)
}
b.glstate.set(b.funcs, gl.BLEND, enable)
}
func (b *Backend) DrawElements(mode driver.DrawMode, off, count int) {
@@ -499,14 +861,12 @@ func (b *Backend) DrawArrays(mode driver.DrawMode, off, count int) {
}
func (b *Backend) prepareDraw() {
nattr := b.state.prog.nattr
b.enableVertexArrays(nattr)
if nattr > 0 {
b.setupVertexArrays()
}
if p := b.state.prog; p != nil {
p.updateUniforms()
p := b.state.prog
if p == nil {
return
}
b.setupVertexArrays()
p.updateUniforms()
}
func toGLDrawMode(mode driver.DrawMode) gl.Enum {
@@ -521,16 +881,16 @@ func toGLDrawMode(mode driver.DrawMode) gl.Enum {
}
func (b *Backend) Viewport(x, y, width, height int) {
b.funcs.Viewport(x, y, width, height)
b.glstate.setViewport(b.funcs, x, y, width, height)
}
func (b *Backend) Clear(colR, colG, colB, colA float32) {
b.funcs.ClearColor(colR, colG, colB, colA)
b.glstate.setClearColor(b.funcs, colR, colG, colB, colA)
b.funcs.Clear(gl.COLOR_BUFFER_BIT)
}
func (b *Backend) ClearDepth(d float32) {
b.funcs.ClearDepthf(d)
b.glstate.setClearDepth(b.funcs, d)
b.funcs.Clear(gl.DEPTH_BUFFER_BIT)
}
@@ -544,7 +904,7 @@ func (b *Backend) DepthFunc(f driver.DepthFunc) {
default:
panic("unsupported depth func")
}
b.funcs.DepthFunc(glfunc)
b.glstate.setDepthFunc(b.funcs, glfunc)
}
func (b *Backend) NewInputLayout(vs driver.ShaderSources, layout []driver.InputDesc) (driver.InputLayout, error) {
@@ -599,7 +959,6 @@ func (b *Backend) NewProgram(vertShader, fragShader driver.ShaderSources) (drive
gpuProg := &gpuProgram{
backend: b,
obj: p,
nattr: len(attr),
}
b.BindProgram(gpuProg)
// Bind texture uniforms.
@@ -667,10 +1026,10 @@ func (p *gpuProgram) updateUniforms() {
f := p.backend.funcs
if p.backend.ubo {
if b := p.vertUniforms.buf; b != nil {
f.BindBufferBase(gl.UNIFORM_BUFFER, 0, b.obj)
p.backend.glstate.bindBufferBase(f, gl.UNIFORM_BUFFER, 0, b.obj)
}
if b := p.fragUniforms.buf; b != nil {
f.BindBufferBase(gl.UNIFORM_BUFFER, 1, b.obj)
p.backend.glstate.bindBufferBase(f, gl.UNIFORM_BUFFER, 1, b.obj)
}
} else {
p.vertUniforms.update(f)
@@ -684,7 +1043,7 @@ func (b *Backend) BindProgram(prog driver.Program) {
}
func (p *gpuProgram) Release() {
p.backend.funcs.DeleteProgram(p.obj)
p.backend.glstate.deleteProgram(p.backend.funcs, p.obj)
}
func (u *uniformsTracker) setup(funcs *gl.Functions, p gl.Program, uniformSize int, uniforms []driver.UniformLocation) {
@@ -751,7 +1110,7 @@ func (b *gpuBuffer) Upload(data []byte) {
copy(b.data, data)
if b.hasBuffer {
firstBinding := firstBufferType(b.typ)
b.backend.funcs.BindBuffer(firstBinding, b.obj)
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.
@@ -771,7 +1130,7 @@ func (b *gpuBuffer) Download(data []byte) error {
return nil
}
firstBinding := firstBufferType(b.typ)
b.backend.funcs.BindBuffer(firstBinding, b.obj)
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())
@@ -785,7 +1144,7 @@ func (b *gpuBuffer) Download(data []byte) error {
func (b *gpuBuffer) Release() {
if b.hasBuffer {
b.backend.funcs.DeleteBuffer(b.obj)
b.backend.glstate.deleteBuffer(b.backend.funcs, b.obj)
b.hasBuffer = false
}
}
@@ -795,7 +1154,7 @@ func (b *Backend) BindVertexBuffer(buf driver.Buffer, stride, offset int) {
if gbuf.typ&driver.BufferBindingVertices == 0 {
panic("not a vertex buffer")
}
b.state.buffer = bufferBinding{buf: gbuf, stride: stride, offset: offset}
b.state.buffer = bufferBinding{obj: gbuf.obj, stride: stride, offset: offset}
}
func (b *Backend) setupVertexArrays() {
@@ -803,8 +1162,9 @@ func (b *Backend) setupVertexArrays() {
if layout == nil {
return
}
const max = len(b.glstate.vertAttribs)
var enabled [max]bool
buf := b.state.buffer
b.funcs.BindBuffer(gl.ARRAY_BUFFER, buf.buf.obj)
for i, inp := range layout.inputs {
l := layout.layout[i]
var gltyp gl.Enum
@@ -816,7 +1176,11 @@ func (b *Backend) setupVertexArrays() {
default:
panic("unsupported data type")
}
b.funcs.VertexAttribPointer(gl.Attrib(inp.Location), l.Size, gltyp, false, buf.stride, buf.offset+l.Offset)
enabled[inp.Location] = true
b.glstate.vertexAttribPointer(b.funcs, buf.obj, inp.Location, l.Size, gltyp, false, buf.stride, buf.offset+l.Offset)
}
for i := 0; i < max; i++ {
b.glstate.setVertexAttribArray(b.funcs, i, enabled[i])
}
}
@@ -825,12 +1189,12 @@ func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
if gbuf.typ&driver.BufferBindingIndices == 0 {
panic("not an index buffer")
}
b.funcs.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
b.glstate.bindBuffer(b.funcs, gl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
}
func (b *Backend) BlitFramebuffer(dst, src driver.Framebuffer, srect, drect image.Rectangle) {
b.funcs.BindFramebuffer(gl.DRAW_FRAMEBUFFER, dst.(*gpuFramebuffer).obj)
b.funcs.BindFramebuffer(gl.READ_FRAMEBUFFER, src.(*gpuFramebuffer).obj)
b.glstate.bindFramebuffer(b.funcs, gl.DRAW_FRAMEBUFFER, dst.(*gpuFramebuffer).obj)
b.glstate.bindFramebuffer(b.funcs, gl.READ_FRAMEBUFFER, src.(*gpuFramebuffer).obj)
b.funcs.BlitFramebuffer(
srect.Min.X, srect.Min.Y, srect.Max.X, srect.Max.Y,
drect.Min.X, drect.Min.Y, drect.Max.X, drect.Max.Y,
@@ -849,7 +1213,7 @@ func (f *gpuFramebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
}
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer) {
b.funcs.BindFramebuffer(gl.FRAMEBUFFER, fbo.(*gpuFramebuffer).obj)
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*gpuFramebuffer).obj)
}
func (f *gpuFramebuffer) Invalidate() {
@@ -861,9 +1225,9 @@ func (f *gpuFramebuffer) Release() {
if f.foreign {
panic("framebuffer not created by NewFramebuffer")
}
f.backend.funcs.DeleteFramebuffer(f.obj)
f.backend.glstate.deleteFramebuffer(f.backend.funcs, f.obj)
if f.hasDepth {
f.backend.funcs.DeleteRenderbuffer(f.depthBuf)
f.backend.glstate.deleteRenderbuffer(f.backend.funcs, f.depthBuf)
}
}
@@ -879,11 +1243,11 @@ func toTexFilter(f driver.TextureFilter) int {
}
func (b *Backend) BindTexture(unit int, t driver.Texture) {
b.bindTexture(unit, t.(*gpuTexture))
b.glstate.bindTexture(b.funcs, unit, t.(*gpuTexture).obj)
}
func (t *gpuTexture) Release() {
t.backend.funcs.DeleteTexture(t.obj)
t.backend.glstate.deleteTexture(t.backend.funcs, t.obj)
}
func (t *gpuTexture) Upload(offset, size image.Point, pixels []byte) {
@@ -945,6 +1309,8 @@ func floatTripleFor(f *gl.Functions, ver [2]int, exts []string) (textureTriple,
}
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)
+191
View File
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: Unlicense OR MIT
package opengl
import (
"errors"
"fmt"
"image"
"runtime"
"strings"
"gioui.org/internal/byteslice"
"gioui.org/internal/gl"
)
// SRGBFBO implements an intermediate sRGB FBO
// for gamma-correct rendering on platforms without
// sRGB enabled native framebuffers.
type SRGBFBO struct {
c *gl.Functions
state *glState
viewport image.Point
srgbBuffer gl.Framebuffer
depthBuffer gl.Renderbuffer
colorTex gl.Texture
blitted bool
quad gl.Buffer
prog gl.Program
gl3 bool
}
func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) {
var gl3 bool
glVer := f.GetString(gl.VERSION)
ver, _, err := gl.ParseGLVersion(glVer)
if err != nil {
return nil, err
}
if ver[0] >= 3 {
gl3 = true
} else {
exts := f.GetString(gl.EXTENSIONS)
if !strings.Contains(exts, "EXT_sRGB") {
return nil, fmt.Errorf("no support for OpenGL ES 3 nor EXT_sRGB")
}
}
s := &SRGBFBO{
c: f,
state: state,
gl3: gl3,
srgbBuffer: f.CreateFramebuffer(),
colorTex: f.CreateTexture(),
depthBuffer: f.CreateRenderbuffer(),
}
state.bindTexture(f, 0, s.colorTex)
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)
return s, nil
}
func (s *SRGBFBO) Blit() {
if !s.blitted {
prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
if err != nil {
panic(err)
}
s.prog = prog
s.state.useProgram(s.c, prog)
s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
s.quad = s.c.CreateBuffer()
s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad)
coords := byteslice.Slice([]float32{
-1, +1, 0, 1,
+1, +1, 1, 1,
-1, -1, 0, 0,
+1, -1, 1, 0,
})
s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW)
s.c.BufferSubData(gl.ARRAY_BUFFER, 0, coords)
s.blitted = true
}
s.state.useProgram(s.c, s.prog)
s.state.bindTexture(s.c, 0, s.colorTex)
s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
s.state.setVertexAttribArray(s.c, 0, true)
s.state.setVertexAttribArray(s.c, 1, true)
s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.srgbBuffer)
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
}
func (s *SRGBFBO) Framebuffer() gl.Framebuffer {
return s.srgbBuffer
}
func (s *SRGBFBO) Refresh(viewport image.Point) error {
if viewport.X == 0 || viewport.Y == 0 {
return errors.New("srgb: zero-sized framebuffer")
}
if s.viewport == viewport {
return nil
}
s.viewport = viewport
s.state.bindTexture(s.c, 0, s.colorTex)
if s.gl3 {
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
} else /* EXT_sRGB */ {
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB_ALPHA_EXT, viewport.X, viewport.Y, gl.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE)
}
s.state.bindRenderbuffer(s.c, gl.RENDERBUFFER, s.depthBuffer)
s.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, viewport.X, viewport.Y)
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.srgbBuffer)
s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.colorTex, 0)
s.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, s.depthBuffer)
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, 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.state.setClearColor(s.c, .5, .5, .5, 1.0)
s.c.Clear(gl.COLOR_BUFFER_BIT)
var pixel [4]byte
s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
if pixel[0] == 128 { // Correct sRGB color value is ~188
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
}
}
}
return nil
}
func (s *SRGBFBO) Release() {
s.state.deleteFramebuffer(s.c, s.srgbBuffer)
s.state.deleteTexture(s.c, s.colorTex)
s.state.deleteRenderbuffer(s.c, s.depthBuffer)
if s.blitted {
s.state.deleteBuffer(s.c, s.quad)
s.state.deleteProgram(s.c, 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);
}
`
)