diff --git a/internal/graphicsdriver/opengl/context_desktop.go b/internal/graphicsdriver/opengl/context_desktop.go index 65bf280ef..41e0bcd24 100644 --- a/internal/graphicsdriver/opengl/context_desktop.go +++ b/internal/graphicsdriver/opengl/context_desktop.go @@ -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 { diff --git a/internal/graphicsdriver/opengl/context_js.go b/internal/graphicsdriver/opengl/context_js.go index ac8ee28da..271f531cc 100644 --- a/internal/graphicsdriver/opengl/context_js.go +++ b/internal/graphicsdriver/opengl/context_js.go @@ -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) +} diff --git a/internal/graphicsdriver/opengl/context_mobile.go b/internal/graphicsdriver/opengl/context_mobile.go index 51bc5f77d..5e9edc9dd 100644 --- a/internal/graphicsdriver/opengl/context_mobile.go +++ b/internal/graphicsdriver/opengl/context_mobile.go @@ -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}) +} diff --git a/internal/graphicsdriver/opengl/image.go b/internal/graphicsdriver/opengl/image.go index 0edf0c69a..6996f5d1a 100644 --- a/internal/graphicsdriver/opengl/image.go +++ b/internal/graphicsdriver/opengl/image.go @@ -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() { diff --git a/internal/graphicsdriver/opengl/pbo_desktop.go b/internal/graphicsdriver/opengl/pbo_desktop.go deleted file mode 100644 index f244a8153..000000000 --- a/internal/graphicsdriver/opengl/pbo_desktop.go +++ /dev/null @@ -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) -} diff --git a/internal/graphicsdriver/opengl/pbo_notdesktop.go b/internal/graphicsdriver/opengl/pbo_notdesktop.go deleted file mode 100644 index f6f73a12a..000000000 --- a/internal/graphicsdriver/opengl/pbo_notdesktop.go +++ /dev/null @@ -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") -} diff --git a/internal/jsutil/buf_js.go b/internal/jsutil/buf_js.go index fdfef06ce..449991bfe 100644 --- a/internal/jsutil/buf_js.go +++ b/internal/jsutil/buf_js.go @@ -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)