graphicsdriver/opengl: Use glBufferSubData instead of glTexSubImage2D on browsers

Updates #988
This commit is contained in:
Hajime Hoshi 2020-01-02 03:58:49 +09:00
parent de48a13a6e
commit e66f1fb71e
7 changed files with 210 additions and 137 deletions

View File

@ -228,14 +228,6 @@ func (c *context) isTexture(t textureNative) bool {
return r
}
func (c *context) texSubImage2D(t textureNative, p []byte, x, y, width, height int) {
c.bindTexture(t)
_ = c.t.Call(func() error {
gl.TexSubImage2D(gl.TEXTURE_2D, 0, int32(x), int32(y), int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(p))
return nil
})
}
func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
var framebuffer framebufferNative
var f uint32
@ -534,6 +526,14 @@ func (c *context) needsRestoring() bool {
return false
}
func (c *context) canUsePBO() bool {
return true
}
func (c *context) texSubImage2D(t textureNative, width, height int, args []*driver.ReplacePixelsArgs) {
panic("opengl: texSubImage2D is not implemented on this environment")
}
func (c *context) newPixelBufferObject(width, height int) buffer {
var bf buffer
_ = c.t.Call(func() error {

View File

@ -72,47 +72,102 @@ func getProgramID(p program) programID {
}
var (
vertexShader shaderType
fragmentShader shaderType
arrayBuffer bufferType
elementArrayBuffer bufferType
dynamicDraw bufferUsage
streamDraw bufferUsage
pixelUnpackBuffer bufferType
short dataType
float dataType
zero operation
one operation
srcAlpha operation
dstAlpha operation
oneMinusSrcAlpha operation
oneMinusDstAlpha operation
blend js.Value
clampToEdge js.Value
compileStatus js.Value
colorAttachment0 js.Value
framebuffer_ js.Value
framebufferBinding js.Value
framebufferComplete js.Value
highFloat js.Value
linkStatus js.Value
maxTextureSize js.Value
nearest js.Value
noError js.Value
rgba js.Value
texture2d js.Value
textureMagFilter js.Value
textureMinFilter js.Value
textureWrapS js.Value
textureWrapT js.Value
triangles js.Value
unpackAlignment js.Value
unsignedByte js.Value
unsignedShort js.Value
isWebGL2Available bool
)
func init() {
// Accessing the prototype is rquired on Safari.
contextPrototype = js.Global().Get("WebGLRenderingContext").Get("prototype")
var contextPrototype js.Value
if !jsutil.Equal(js.Global().Get("WebGL2RenderingContext"), js.Undefined()) {
contextPrototype = js.Global().Get("WebGL2RenderingContext").Get("prototype")
isWebGL2Available = true
} else {
contextPrototype = js.Global().Get("WebGLRenderingContext").Get("prototype")
}
vertexShader = shaderType(contextPrototype.Get("VERTEX_SHADER").Int())
fragmentShader = shaderType(contextPrototype.Get("FRAGMENT_SHADER").Int())
arrayBuffer = bufferType(contextPrototype.Get("ARRAY_BUFFER").Int())
vertexShader = shaderType(contextPrototype.Get("VERTEX_SHADER").Int())
fragmentShader = shaderType(contextPrototype.Get("FRAGMENT_SHADER").Int())
arrayBuffer = bufferType(contextPrototype.Get("ARRAY_BUFFER").Int())
elementArrayBuffer = bufferType(contextPrototype.Get("ELEMENT_ARRAY_BUFFER").Int())
dynamicDraw = bufferUsage(contextPrototype.Get("DYNAMIC_DRAW").Int())
short = dataType(contextPrototype.Get("SHORT").Int())
float = dataType(contextPrototype.Get("FLOAT").Int())
dynamicDraw = bufferUsage(contextPrototype.Get("DYNAMIC_DRAW").Int())
streamDraw = bufferUsage(contextPrototype.Get("STREAM_DRAW").Int())
short = dataType(contextPrototype.Get("SHORT").Int())
float = dataType(contextPrototype.Get("FLOAT").Int())
zero = operation(contextPrototype.Get("ZERO").Int())
one = operation(contextPrototype.Get("ONE").Int())
srcAlpha = operation(contextPrototype.Get("SRC_ALPHA").Int())
dstAlpha = operation(contextPrototype.Get("DST_ALPHA").Int())
zero = operation(contextPrototype.Get("ZERO").Int())
one = operation(contextPrototype.Get("ONE").Int())
srcAlpha = operation(contextPrototype.Get("SRC_ALPHA").Int())
dstAlpha = operation(contextPrototype.Get("DST_ALPHA").Int())
oneMinusSrcAlpha = operation(contextPrototype.Get("ONE_MINUS_SRC_ALPHA").Int())
oneMinusDstAlpha = operation(contextPrototype.Get("ONE_MINUS_DST_ALPHA").Int())
blend = contextPrototype.Get("BLEND")
clampToEdge = contextPrototype.Get("CLAMP_TO_EDGE")
compileStatus = contextPrototype.Get("COMPILE_STATUS")
colorAttachment0 = contextPrototype.Get("COLOR_ATTACHMENT0")
framebuffer_ = contextPrototype.Get("FRAMEBUFFER")
framebufferBinding = contextPrototype.Get("FRAMEBUFFER_BINDING")
blend = contextPrototype.Get("BLEND")
clampToEdge = contextPrototype.Get("CLAMP_TO_EDGE")
compileStatus = contextPrototype.Get("COMPILE_STATUS")
colorAttachment0 = contextPrototype.Get("COLOR_ATTACHMENT0")
framebuffer_ = contextPrototype.Get("FRAMEBUFFER")
framebufferBinding = contextPrototype.Get("FRAMEBUFFER_BINDING")
framebufferComplete = contextPrototype.Get("FRAMEBUFFER_COMPLETE")
highFloat = contextPrototype.Get("HIGH_FLOAT")
linkStatus = contextPrototype.Get("LINK_STATUS")
maxTextureSize = contextPrototype.Get("MAX_TEXTURE_SIZE")
nearest = contextPrototype.Get("NEAREST")
noError = contextPrototype.Get("NO_ERROR")
rgba = contextPrototype.Get("RGBA")
texture2d = contextPrototype.Get("TEXTURE_2D")
textureMagFilter = contextPrototype.Get("TEXTURE_MAG_FILTER")
textureMinFilter = contextPrototype.Get("TEXTURE_MIN_FILTER")
textureWrapS = contextPrototype.Get("TEXTURE_WRAP_S")
textureWrapT = contextPrototype.Get("TEXTURE_WRAP_T")
triangles = contextPrototype.Get("TRIANGLES")
unpackAlignment = contextPrototype.Get("UNPACK_ALIGNMENT")
unsignedByte = contextPrototype.Get("UNSIGNED_BYTE")
unsignedShort = contextPrototype.Get("UNSIGNED_SHORT")
)
highFloat = contextPrototype.Get("HIGH_FLOAT")
linkStatus = contextPrototype.Get("LINK_STATUS")
maxTextureSize = contextPrototype.Get("MAX_TEXTURE_SIZE")
nearest = contextPrototype.Get("NEAREST")
noError = contextPrototype.Get("NO_ERROR")
rgba = contextPrototype.Get("RGBA")
texture2d = contextPrototype.Get("TEXTURE_2D")
textureMagFilter = contextPrototype.Get("TEXTURE_MAG_FILTER")
textureMinFilter = contextPrototype.Get("TEXTURE_MIN_FILTER")
textureWrapS = contextPrototype.Get("TEXTURE_WRAP_S")
textureWrapT = contextPrototype.Get("TEXTURE_WRAP_T")
triangles = contextPrototype.Get("TRIANGLES")
unpackAlignment = contextPrototype.Get("UNPACK_ALIGNMENT")
unsignedByte = contextPrototype.Get("UNSIGNED_BYTE")
unsignedShort = contextPrototype.Get("UNSIGNED_SHORT")
if isWebGL2Available {
pixelUnpackBuffer = bufferType(contextPrototype.Get("PIXEL_UNPACK_BUFFER").Int())
}
}
type contextImpl struct {
gl js.Value
@ -124,19 +179,22 @@ func (c *context) ensureGL() {
return
}
if jsutil.Equal(js.Global().Get("WebGLRenderingContext"), js.Undefined()) {
panic("opengl: WebGL is not supported")
}
// TODO: Define id?
canvas := js.Global().Get("document").Call("querySelector", "canvas")
attr := js.Global().Get("Object").New()
attr.Set("alpha", true)
attr.Set("premultipliedAlpha", true)
gl := canvas.Call("getContext", "webgl", attr)
if jsutil.Equal(gl, js.Null()) {
gl = canvas.Call("getContext", "experimental-webgl", attr)
var gl js.Value
if isWebGL2Available {
gl = canvas.Call("getContext", "webgl2", attr)
} else {
gl = canvas.Call("getContext", "webgl", attr)
if jsutil.Equal(gl, js.Null()) {
panic("opengl: getContext failed")
gl = canvas.Call("getContext", "experimental-webgl", attr)
if jsutil.Equal(gl, js.Null()) {
panic("opengl: getContext failed")
}
}
}
@ -245,18 +303,6 @@ func (c *context) isTexture(t textureNative) bool {
return gl.Call("isTexture", js.Value(t)).Bool()
}
func (c *context) texSubImage2D(t textureNative, pixels []byte, x, y, width, height int) {
c.bindTexture(t)
c.ensureGL()
gl := c.gl
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height,
// GLenum format, GLenum type, ArrayBufferView? pixels);
arr := jsutil.TemporaryUint8Array(len(pixels))
jsutil.CopySliceToJS(arr, pixels)
gl.Call("texSubImage2D", texture2d, 0, x, y, width, height, rgba, unsignedByte, arr)
}
func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) {
c.ensureGL()
gl := c.gl
@ -494,3 +540,54 @@ func (c *context) flush() {
func (c *context) needsRestoring() bool {
return !web.IsMobileBrowser()
}
func (c *context) canUsePBO() bool {
return isWebGL2Available
}
func (c *context) texSubImage2D(t textureNative, width, height int, args []*driver.ReplacePixelsArgs) {
c.ensureGL()
c.bindTexture(t)
gl := c.gl
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height,
// GLenum format, GLenum type, ArrayBufferView? pixels);
for _, a := range args {
arr := jsutil.TemporaryUint8Array(len(a.Pixels))
jsutil.CopySliceToJS(arr, a.Pixels)
gl.Call("texSubImage2D", texture2d, 0, a.X, a.Y, a.Width, a.Height, rgba, unsignedByte, arr)
}
}
func (c *context) newPixelBufferObject(width, height int) buffer {
c.ensureGL()
gl := c.gl
b := gl.Call("createBuffer")
gl.Call("bindBuffer", int(pixelUnpackBuffer), js.Value(b))
gl.Call("bufferData", int(pixelUnpackBuffer), 4*width*height, int(streamDraw))
gl.Call("bindBuffer", int(pixelUnpackBuffer), nil)
return buffer(b)
}
func (c *context) replacePixelsWithPBO(buffer buffer, t textureNative, width, height int, args []*driver.ReplacePixelsArgs) {
c.ensureGL()
c.bindTexture(t)
gl := c.gl
gl.Call("bindBuffer", int(pixelUnpackBuffer), js.Value(buffer))
stride := 4 * width
for _, a := range args {
arr := jsutil.TemporaryUint8Array(len(a.Pixels))
jsutil.CopySliceToJS(arr, a.Pixels)
offset := 4 * (a.Y*width + a.X)
for j := 0; j < a.Height; j++ {
gl.Call("bufferSubData", int(pixelUnpackBuffer), offset+stride*j, arr, 4*a.Width*j, 4*a.Width)
}
}
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height,
// GLenum format, GLenum type, GLintptr offset);
gl.Call("texSubImage2D", texture2d, 0, 0, 0, width, height, rgba, unsignedByte, 0)
gl.Call("bindBuffer", int(pixelUnpackBuffer), nil)
}

View File

@ -177,12 +177,6 @@ func (c *context) isTexture(t textureNative) bool {
return gl.IsTexture(mgl.Texture(t))
}
func (c *context) texSubImage2D(t textureNative, p []byte, x, y, width, height int) {
c.bindTexture(t)
gl := c.gl
gl.TexSubImage2D(mgl.TEXTURE_2D, 0, x, y, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE, p)
}
func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) {
gl := c.gl
f := gl.CreateFramebuffer()
@ -393,3 +387,45 @@ func (c *context) flush() {
func (c *context) needsRestoring() bool {
return true
}
func (c *context) canUsePBO() bool {
// The implementation for PBO is almost done, but not finished yet due to Go mobile interface.
// See golang/go#36355.
return false
}
func (c *context) texSubImage2D(t textureNative, width, height int, args []*driver.ReplacePixelsArgs) {
c.bindTexture(t)
gl := c.gl
for _, a := range args {
gl.TexSubImage2D(mgl.TEXTURE_2D, 0, a.X, a.Y, a.Width, a.Height, mgl.RGBA, mgl.UNSIGNED_BYTE, a.Pixels)
}
}
func (c *context) newPixelBufferObject(width, height int) buffer {
gl := c.gl
b := gl.CreateBuffer()
gl.BindBuffer(mgl.PIXEL_UNPACK_BUFFER, b)
gl.BufferInit(mgl.PIXEL_UNPACK_BUFFER, 4*width*height, mgl.STREAM_DRAW)
gl.BindBuffer(mgl.PIXEL_UNPACK_BUFFER, mgl.Buffer{0})
return buffer(b)
}
func (c *context) replacePixelsWithPBO(buffer buffer, t textureNative, width, height int, args []*driver.ReplacePixelsArgs) {
c.bindTexture(t)
gl := c.gl
gl.BindBuffer(mgl.PIXEL_UNPACK_BUFFER, mgl.Buffer(buffer))
stride := 4 * width
for _, a := range args {
offset := 4 * (a.Y*width + a.X)
for j := 0; j < a.Height; j++ {
gl.BufferSubData(mgl.PIXEL_UNPACK_BUFFER, offset+stride*j, a.Pixels[4*a.Width*j:4*a.Width*(j+1)])
}
}
// This implementation is still wrong since TexSubImage2D cannot take an offset integer.
// See golang/go#36355.
gl.TexSubImage2D(mgl.TEXTURE_2D, 0, 0, 0, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE, nil)
gl.BindBuffer(mgl.PIXEL_UNPACK_BUFFER, mgl.Buffer{0})
}

View File

@ -105,14 +105,19 @@ func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) {
}
i.driver.drawCalled = false
if !canUsePBO {
for _, a := range args {
i.driver.context.texSubImage2D(i.textureNative, a.Pixels, a.X, a.Y, a.Width, a.Height)
}
w, h := i.width, i.height
if !i.driver.context.canUsePBO() {
i.driver.context.texSubImage2D(i.textureNative, w, h, args)
return
}
if i.pbo.equal(*new(buffer)) {
i.pbo = i.driver.context.newPixelBufferObject(w, h)
}
if i.pbo.equal(*new(buffer)) {
panic("opengl: newPixelBufferObject failed")
}
drawPixelsWithPBO(i, args)
i.driver.context.replacePixelsWithPBO(i.pbo, i.textureNative, w, h, args)
}
func (i *Image) SetAsSource() {

View File

@ -1,38 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin freebsd linux windows
// +build !js
// +build !android
// +build !ios
package opengl
import (
"github.com/hajimehoshi/ebiten/internal/driver"
)
const canUsePBO = true
func drawPixelsWithPBO(img *Image, args []*driver.ReplacePixelsArgs) {
w, h := img.width, img.height
if img.pbo == *new(buffer) {
img.pbo = img.driver.context.newPixelBufferObject(w, h)
}
if img.pbo == *new(buffer) {
panic("opengl: newPixelBufferObject failed")
}
img.driver.context.replacePixelsWithPBO(img.pbo, img.textureNative, w, h, args)
}

View File

@ -1,27 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build js android ios
package opengl
import (
"github.com/hajimehoshi/ebiten/internal/driver"
)
const canUsePBO = false
func drawPixelsWithPBO(img *Image, args []*driver.ReplacePixelsArgs) {
panic("opengl: PBO is not available in this environment")
}

View File

@ -18,7 +18,7 @@ import (
"syscall/js"
)
// temporaryBuffer is a temporary buffer used at gl.readPixels.
// temporaryBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
// The read data is converted to Go's byte slice as soon as possible.
// To avoid often allocating ArrayBuffer, reuse the buffer whenever possible.
var temporaryBuffer = js.Global().Get("ArrayBuffer").New(16)