1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package opengl
- import (
- "errors"
- "fmt"
- "image"
- "math/bits"
- "runtime"
- "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
- sharedCtx bool
- glver [2]int
- gles bool
- feats driver.Caps
- // floatTriple holds the settings for floating point
- // textures.
- floatTriple textureTriple
- // Single channel alpha textures.
- alphaTriple textureTriple
- srgbaTriple textureTriple
- storage [storageBindings]*buffer
- outputFBO gl.Framebuffer
- 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
- 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
- fbo gl.Framebuffer
- hasFBO bool
- triple textureTriple
- width int
- height int
- mipmap bool
- bindings driver.BufferBinding
- foreign bool
- }
- type pipeline struct {
- prog *program
- inputs []shader.InputLocation
- layout driver.VertexLayout
- blend driver.BlendDesc
- topology driver.Topology
- }
- 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
- }
- // 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
- }
- // Supporting compute programs is theoretically possible with OpenGL ES 3.1. In
- // practice, there are too many driver issues, especially on Android (e.g.
- // Google Pixel, Samsung J2 are both broken i different ways). Disable support
- // and rely on Vulkan for devices that support it, and the CPU fallback for
- // devices that don't.
- const brokenGLES31 = true
- 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)
- gles31 := gles && (ver[0] > 3 || (ver[0] == 3 && ver[1] >= 1))
- b := &Backend{
- glver: ver,
- gles: gles,
- funcs: f,
- floatTriple: floatTriple,
- alphaTriple: alphaTripleFor(ver),
- srgbaTriple: srgbaTriple,
- sharedCtx: api.Shared,
- }
- b.feats.BottomLeftOrigin = true
- if srgbErr == nil {
- b.feats.Features |= driver.FeatureSRGB
- }
- if ffboErr == nil {
- b.feats.Features |= driver.FeatureFloatRenderTargets
- }
- if gles31 && !brokenGLES31 {
- 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)
- if !b.sharedCtx {
- // We have exclusive access to the context, so query the GL state once
- // instead of at each frame.
- b.glstate = b.queryState()
- }
- return b, nil
- }
- func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
- b.clear = clear
- if b.sharedCtx {
- 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 *texture:
- renderFBO = t.ensureFBO()
- default:
- panic(fmt.Errorf("opengl: invalid render target type: %T", target))
- }
- }
- b.outputFBO = renderFBO
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, renderFBO)
- if b.gles {
- // If the output framebuffer is not in the sRGB colorspace already, emulate it.
- fbSRGB := false
- if !b.gles || b.glver[0] > 2 {
- 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)
- }
- fbSRGB = fbEncoding != gl.LINEAR
- }
- if !fbSRGB && 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 &texture{backend: b, fbo: renderFBO, hasFBO: true, foreign: true}
- }
- func (b *Backend) EndFrame() {
- if b.sRGBFBO != nil {
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.outputFBO)
- if b.clear {
- b.SetBlend(false)
- } else {
- b.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha)
- b.SetBlend(true)
- }
- b.sRGBFBO.Blit()
- }
- if b.sharedCtx {
- b.restoreState(b.savedState)
- } else if runtime.GOOS == "android" {
- // The Android emulator needs the output framebuffer to be current when
- // eglSwapBuffers is called.
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, b.outputFBO)
- }
- }
- 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),
- }
- if !b.gles || b.glver[0] > 2 {
- s.unpack_row_length = b.funcs.GetInteger(gl.UNPACK_ROW_LENGTH)
- s.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))
- }
- }
- active := s.texUnits.active
- 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))
- }
- s.activeTexture(b.funcs, active)
- 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) 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) 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 (t *texture) ensureFBO() gl.Framebuffer {
- if t.hasFBO {
- return t.fbo
- }
- b := t.backend
- oldFBO := b.glstate.drawFBO
- defer func() {
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, oldFBO)
- }()
- glErr(b.funcs)
- fb := b.funcs.CreateFramebuffer()
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fb)
- if err := glErr(b.funcs); err != nil {
- b.funcs.DeleteFramebuffer(fb)
- panic(err)
- }
- b.funcs.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.obj, 0)
- if st := b.funcs.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
- b.funcs.DeleteFramebuffer(fb)
- panic(fmt.Errorf("incomplete framebuffer, status = 0x%x, err = %d", st, b.funcs.GetError()))
- }
- t.fbo = fb
- t.hasFBO = true
- return fb
- }
- 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, bindings: binding}
- 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)
- min, mipmap := toTexFilter(minFilter)
- mag, _ := toTexFilter(magFilter)
- if b.gles && b.glver[0] < 3 {
- // OpenGL ES 2 only supports mipmaps for power-of-two textures.
- mipmap = false
- }
- tex.mipmap = mipmap
- b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag)
- b.funcs.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min)
- 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 mipmap {
- nmipmaps := 1
- if mipmap {
- dim := width
- if height > dim {
- dim = height
- }
- log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
- nmipmaps = log2 + 1
- }
- // Immutable textures are required for BindImageTexture, and can't hurt otherwise.
- b.funcs.TexStorage2D(gl.TEXTURE_2D, nmipmaps, 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")
- }
- buf.data = make([]byte, size)
- }
- if typ&^driver.BufferBindingUniforms != 0 {
- 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) 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)
- b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
- }
- func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
- t := tex.(*texture)
- var acc gl.Enum
- switch t.bindings & (driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite) {
- case driver.BufferBindingShaderStorageRead:
- acc = gl.READ_ONLY
- case driver.BufferBindingShaderStorageWrite:
- acc = gl.WRITE_ONLY
- case driver.BufferBindingShaderStorageRead | driver.BufferBindingShaderStorageWrite:
- acc = gl.READ_WRITE
- default:
- panic("unsupported access bits")
- }
- b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, t.triple.internalFormat)
- }
- 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(off, count int) {
- b.prepareDraw()
- // off is in 16-bit indices, but DrawElements take a byte offset.
- byteOff := off * 2
- b.funcs.DrawElements(toGLDrawMode(b.state.pipeline.topology), count, gl.UNSIGNED_SHORT, byteOff)
- }
- func (b *Backend) DrawArrays(off, count int) {
- b.prepareDraw()
- b.funcs.DrawArrays(toGLDrawMode(b.state.pipeline.topology), off, count)
- }
- func (b *Backend) prepareDraw() {
- p := b.state.pipeline
- if p == nil {
- return
- }
- b.setupVertexArrays()
- }
- func toGLDrawMode(mode driver.Topology) gl.Enum {
- switch mode {
- case driver.TopologyTriangleStrip:
- return gl.TRIANGLE_STRIP
- case driver.TopologyTriangles:
- 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) {
- // We don't support ES 3.1 compute, see brokenGLES31 above.
- const GLES31Source = ""
- p, err := gl.CreateComputeProgram(b.funcs, GLES31Source)
- 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.gles {
- return src.GLSL100ES
- } else {
- return src.GLSL150
- }
- }
- 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,
- topology: desc.Topology,
- }, 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)
- }
- }
- 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 (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) BindUniforms(buf driver.Buffer) {
- bf := buf.(*buffer)
- if bf.typ&driver.BufferBindingUniforms == 0 {
- panic("not a uniform buffer")
- }
- b.state.pipeline.prog.vertUniforms.update(b.funcs, bf)
- b.state.pipeline.prog.fragUniforms.update(b.funcs, bf)
- }
- 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 {
- loc := funcs.GetUniformLocation(p, uniform.Name)
- u.locs[i] = uniformLocation{uniform: loc, offset: uniform.Offset, typ: uniform.Type, size: uniform.Size}
- }
- 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 {
- if !u.uniform.Valid() {
- continue
- }
- 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.Texture, 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.FRAMEBUFFER, src.(*texture).ensureFBO())
- 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 (t *texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
- glErr(t.backend.funcs)
- t.backend.glstate.bindFramebuffer(t.backend.funcs, gl.FRAMEBUFFER, t.ensureFBO())
- w, h := src.Dx(), src.Dy()
- if len(pixels) < w*h*4 {
- return errors.New("unexpected RGBA size")
- }
- // OpenGL ES 2 doesn't support PACK_ROW_LENGTH != 0. Avoid it if possible.
- rowLen := 0
- if n := stride / 4; n != w {
- rowLen = n
- }
- if rowLen == 0 || t.backend.glver[0] > 2 {
- t.backend.glstate.pixelStorei(t.backend.funcs, gl.PACK_ROW_LENGTH, rowLen)
- t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
- } else {
- tmp := make([]byte, w*h*4)
- t.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, tmp)
- for y := 0; y < h; y++ {
- copy(pixels[y*stride:], tmp[y*w*4:])
- }
- }
- return glErr(t.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) BeginCompute() {
- b.funcs.MemoryBarrier(gl.ALL_BARRIER_BITS)
- }
- func (b *Backend) EndCompute() {
- }
- func (b *Backend) BeginRenderPass(tex driver.Texture, desc driver.LoadDesc) {
- fbo := tex.(*texture).ensureFBO()
- b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo)
- 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 (b *Backend) EndRenderPass() {
- }
- func (f *texture) ImplementsRenderTarget() {}
- func (p *pipeline) Release() {
- p.prog.Release()
- *p = pipeline{}
- }
- func toTexFilter(f driver.TextureFilter) (int, bool) {
- switch f {
- case driver.FilterNearest:
- return gl.NEAREST, false
- case driver.FilterLinear:
- return gl.LINEAR, false
- case driver.FilterLinearMipmapLinear:
- return gl.LINEAR_MIPMAP_LINEAR, true
- default:
- panic("unsupported texture filter")
- }
- }
- func (b *Backend) PrepareTexture(tex driver.Texture) {}
- func (b *Backend) BindTexture(unit int, t driver.Texture) {
- b.glstate.bindTexture(b.funcs, unit, t.(*texture).obj)
- }
- func (t *texture) Release() {
- if t.foreign {
- panic("texture not created by NewTexture")
- }
- if t.hasFBO {
- t.backend.glstate.deleteFramebuffer(t.backend.funcs, t.fbo)
- }
- 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)
- if t.mipmap {
- t.backend.funcs.GenerateMipmap(gl.TEXTURE_2D)
- }
- }
- 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")
- }
- }
|