graphicsdriver/opengl: Experimental PBO implementation

This change is an experimental implementation to use Pixel Buffer
Objects. This reduces calls of glTexSubImage2D.

This works only on desktops. Unfortunately WebGL does not have
this features. Mobiles can have PBO as of OpenGL ES 3.

Updates #976
This commit is contained in:
Hajime Hoshi 2019-11-17 02:10:10 +09:00
parent 16b3a5c296
commit acc933b7c3
9 changed files with 232 additions and 1 deletions

View File

@ -22,6 +22,7 @@ package opengl
import ( import (
"errors" "errors"
"fmt" "fmt"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/driver" "github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl/gl" "github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl/gl"
@ -509,3 +510,42 @@ func (c *context) flush() {
func (c *context) needsRestoring() bool { func (c *context) needsRestoring() bool {
return false return false
} }
func (c *context) newPixelBufferObject(width, height int) buffer {
var bf buffer
_ = c.t.Call(func() error {
var b uint32
gl.GenBuffers(1, &b)
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, b)
gl.BufferData(gl.PIXEL_UNPACK_BUFFER, 4*width*height, nil, gl.STREAM_DRAW)
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0)
bf = buffer(b)
return nil
})
return bf
}
func (c *context) mapPixelBuffer(buffer buffer) unsafe.Pointer {
var ptr unsafe.Pointer
_ = c.t.Call(func() error {
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, uint32(buffer))
ptr = gl.MapBuffer(gl.PIXEL_UNPACK_BUFFER, gl.WRITE_ONLY)
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0)
return nil
})
return ptr
}
func (c *context) unmapPixelBuffer(buffer buffer, t textureNative, width, height int) {
_ = c.t.Call(func() error {
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, uint32(buffer))
gl.UnmapBuffer(gl.PIXEL_UNPACK_BUFFER)
return nil
})
c.bindTexture(t)
_ = c.t.Call(func() error {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)
gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0)
return nil
})
}

View File

@ -470,3 +470,7 @@ func (c *context) flush() {
func (c *context) needsRestoring() bool { func (c *context) needsRestoring() bool {
return !web.IsMobileBrowser() return !web.IsMobileBrowser()
} }
func (c *context) canUsePBO() bool {
return false
}

View File

@ -20,6 +20,8 @@ const (
ARRAY_BUFFER = 0x8892 ARRAY_BUFFER = 0x8892
ELEMENT_ARRAY_BUFFER = 0x8893 ELEMENT_ARRAY_BUFFER = 0x8893
DYNAMIC_DRAW = 0x88E8 DYNAMIC_DRAW = 0x88E8
STREAM_DRAW = 0x88E0
PIXEL_UNPACK_BUFFER = 0x88EC
SHORT = 0x1402 SHORT = 0x1402
FLOAT = 0x1406 FLOAT = 0x1406
@ -56,6 +58,7 @@ const (
UNSIGNED_BYTE = 0x1401 UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403 UNSIGNED_SHORT = 0x1403
VERTEX_SHADER = 0x8B31 VERTEX_SHADER = 0x8B31
WRITE_ONLY = 0x88B9
) )
// Init initializes the OpenGL bindings by loading the function pointers (for // Init initializes the OpenGL bindings by loading the function pointers (for

View File

@ -144,6 +144,7 @@ package gl
// typedef GLboolean (APIENTRYP GPISPROGRAM)(GLuint program); // typedef GLboolean (APIENTRYP GPISPROGRAM)(GLuint program);
// typedef GLboolean (APIENTRYP GPISTEXTURE)(GLuint texture); // typedef GLboolean (APIENTRYP GPISTEXTURE)(GLuint texture);
// typedef void (APIENTRYP GPLINKPROGRAM)(GLuint program); // typedef void (APIENTRYP GPLINKPROGRAM)(GLuint program);
// typedef void * (APIENTRYP GPMAPBUFFER)(GLenum target, GLenum access);
// typedef void (APIENTRYP GPPIXELSTOREI)(GLenum pname, GLint param); // typedef void (APIENTRYP GPPIXELSTOREI)(GLenum pname, GLint param);
// typedef void (APIENTRYP GPREADPIXELS)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels); // typedef void (APIENTRYP GPREADPIXELS)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels);
// typedef void (APIENTRYP GPSHADERSOURCE)(GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length); // typedef void (APIENTRYP GPSHADERSOURCE)(GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length);
@ -155,6 +156,7 @@ package gl
// typedef void (APIENTRYP GPUNIFORM2FV)(GLint location, GLsizei count, const GLfloat * value); // typedef void (APIENTRYP GPUNIFORM2FV)(GLint location, GLsizei count, const GLfloat * value);
// typedef void (APIENTRYP GPUNIFORM4FV)(GLint location, GLsizei count, const GLfloat * value); // typedef void (APIENTRYP GPUNIFORM4FV)(GLint location, GLsizei count, const GLfloat * value);
// typedef void (APIENTRYP GPUNIFORMMATRIX4FV)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); // typedef void (APIENTRYP GPUNIFORMMATRIX4FV)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
// typedef GLboolean (APIENTRYP GPUNMAPBUFFER)(GLenum target);
// typedef void (APIENTRYP GPUSEPROGRAM)(GLuint program); // typedef void (APIENTRYP GPUSEPROGRAM)(GLuint program);
// typedef void (APIENTRYP GPVERTEXATTRIBPOINTER)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const uintptr_t pointer); // typedef void (APIENTRYP GPVERTEXATTRIBPOINTER)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const uintptr_t pointer);
// typedef void (APIENTRYP GPVIEWPORT)(GLint x, GLint y, GLsizei width, GLsizei height); // typedef void (APIENTRYP GPVIEWPORT)(GLint x, GLint y, GLsizei width, GLsizei height);
@ -303,6 +305,9 @@ package gl
// static void glowLinkProgram(GPLINKPROGRAM fnptr, GLuint program) { // static void glowLinkProgram(GPLINKPROGRAM fnptr, GLuint program) {
// (*fnptr)(program); // (*fnptr)(program);
// } // }
// static void * glowMapBuffer(GPMAPBUFFER fnptr, GLenum target, GLenum access) {
// return (*fnptr)(target, access);
// }
// static void glowPixelStorei(GPPIXELSTOREI fnptr, GLenum pname, GLint param) { // static void glowPixelStorei(GPPIXELSTOREI fnptr, GLenum pname, GLint param) {
// (*fnptr)(pname, param); // (*fnptr)(pname, param);
// } // }
@ -336,6 +341,9 @@ package gl
// static void glowUniformMatrix4fv(GPUNIFORMMATRIX4FV fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { // static void glowUniformMatrix4fv(GPUNIFORMMATRIX4FV fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) {
// (*fnptr)(location, count, transpose, value); // (*fnptr)(location, count, transpose, value);
// } // }
// static GLboolean glowUnmapBuffer(GPUNMAPBUFFER fnptr, GLenum target) {
// return (*fnptr)(target);
// }
// static void glowUseProgram(GPUSEPROGRAM fnptr, GLuint program) { // static void glowUseProgram(GPUSEPROGRAM fnptr, GLuint program) {
// (*fnptr)(program); // (*fnptr)(program);
// } // }
@ -401,6 +409,7 @@ var (
gpIsProgram C.GPISPROGRAM gpIsProgram C.GPISPROGRAM
gpIsTexture C.GPISTEXTURE gpIsTexture C.GPISTEXTURE
gpLinkProgram C.GPLINKPROGRAM gpLinkProgram C.GPLINKPROGRAM
gpMapBuffer C.GPMAPBUFFER
gpPixelStorei C.GPPIXELSTOREI gpPixelStorei C.GPPIXELSTOREI
gpReadPixels C.GPREADPIXELS gpReadPixels C.GPREADPIXELS
gpShaderSource C.GPSHADERSOURCE gpShaderSource C.GPSHADERSOURCE
@ -412,6 +421,7 @@ var (
gpUniform2fv C.GPUNIFORM2FV gpUniform2fv C.GPUNIFORM2FV
gpUniform4fv C.GPUNIFORM4FV gpUniform4fv C.GPUNIFORM4FV
gpUniformMatrix4fv C.GPUNIFORMMATRIX4FV gpUniformMatrix4fv C.GPUNIFORMMATRIX4FV
gpUnmapBuffer C.GPUNMAPBUFFER
gpUseProgram C.GPUSEPROGRAM gpUseProgram C.GPUSEPROGRAM
gpVertexAttribPointer C.GPVERTEXATTRIBPOINTER gpVertexAttribPointer C.GPVERTEXATTRIBPOINTER
gpViewport C.GPVIEWPORT gpViewport C.GPVIEWPORT
@ -616,6 +626,11 @@ func LinkProgram(program uint32) {
C.glowLinkProgram(gpLinkProgram, (C.GLuint)(program)) C.glowLinkProgram(gpLinkProgram, (C.GLuint)(program))
} }
func MapBuffer(target uint32, access uint32) unsafe.Pointer {
ret := C.glowMapBuffer(gpMapBuffer, (C.GLenum)(target), (C.GLenum)(access))
return (unsafe.Pointer)(ret)
}
func PixelStorei(pname uint32, param int32) { func PixelStorei(pname uint32, param int32) {
C.glowPixelStorei(gpPixelStorei, (C.GLenum)(pname), (C.GLint)(param)) C.glowPixelStorei(gpPixelStorei, (C.GLenum)(pname), (C.GLint)(param))
} }
@ -660,6 +675,11 @@ func UniformMatrix4fv(location int32, count int32, transpose bool, value *float3
C.glowUniformMatrix4fv(gpUniformMatrix4fv, (C.GLint)(location), (C.GLsizei)(count), (C.GLboolean)(boolToInt(transpose)), (*C.GLfloat)(unsafe.Pointer(value))) C.glowUniformMatrix4fv(gpUniformMatrix4fv, (C.GLint)(location), (C.GLsizei)(count), (C.GLboolean)(boolToInt(transpose)), (*C.GLfloat)(unsafe.Pointer(value)))
} }
func UnmapBuffer(target uint32) bool {
ret := C.glowUnmapBuffer(gpUnmapBuffer, (C.GLenum)(target))
return ret == TRUE
}
func UseProgram(program uint32) { func UseProgram(program uint32) {
C.glowUseProgram(gpUseProgram, (C.GLuint)(program)) C.glowUseProgram(gpUseProgram, (C.GLuint)(program))
} }
@ -813,6 +833,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
if gpLinkProgram == nil { if gpLinkProgram == nil {
return errors.New("glLinkProgram") return errors.New("glLinkProgram")
} }
gpMapBuffer = (C.GPMAPBUFFER)(getProcAddr("glMapBuffer"))
if gpMapBuffer == nil {
return errors.New("glMapBuffer")
}
gpPixelStorei = (C.GPPIXELSTOREI)(getProcAddr("glPixelStorei")) gpPixelStorei = (C.GPPIXELSTOREI)(getProcAddr("glPixelStorei"))
if gpPixelStorei == nil { if gpPixelStorei == nil {
return errors.New("glPixelStorei") return errors.New("glPixelStorei")
@ -857,6 +881,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
if gpUniformMatrix4fv == nil { if gpUniformMatrix4fv == nil {
return errors.New("glUniformMatrix4fv") return errors.New("glUniformMatrix4fv")
} }
gpUnmapBuffer = (C.GPUNMAPBUFFER)(getProcAddr("glUnmapBuffer"))
if gpUnmapBuffer == nil {
return errors.New("glUnmapBuffer")
}
gpUseProgram = (C.GPUSEPROGRAM)(getProcAddr("glUseProgram")) gpUseProgram = (C.GPUSEPROGRAM)(getProcAddr("glUseProgram"))
if gpUseProgram == nil { if gpUseProgram == nil {
return errors.New("glUseProgram") return errors.New("glUseProgram")

View File

@ -58,6 +58,7 @@ var (
gpIsProgram uintptr gpIsProgram uintptr
gpIsTexture uintptr gpIsTexture uintptr
gpLinkProgram uintptr gpLinkProgram uintptr
gpMapBuffer uintptr
gpPixelStorei uintptr gpPixelStorei uintptr
gpReadPixels uintptr gpReadPixels uintptr
gpShaderSource uintptr gpShaderSource uintptr
@ -69,6 +70,7 @@ var (
gpUniform2fv uintptr gpUniform2fv uintptr
gpUniform4fv uintptr gpUniform4fv uintptr
gpUniformMatrix4fv uintptr gpUniformMatrix4fv uintptr
gpUnmapBuffer uintptr
gpUseProgram uintptr gpUseProgram uintptr
gpVertexAttribPointer uintptr gpVertexAttribPointer uintptr
gpViewport uintptr gpViewport uintptr
@ -269,6 +271,11 @@ func IsTexture(texture uint32) bool {
return ret != 0 return ret != 0
} }
func MapBuffer(target uint32, access uint32) unsafe.Pointer {
ret, _, _ := syscall.Syscall(gpMapBuffer, 2, uintptr(target), uintptr(access), 0)
return unsafe.Pointer(ret)
}
func LinkProgram(program uint32) { func LinkProgram(program uint32) {
syscall.Syscall(gpLinkProgram, 1, uintptr(program), 0, 0) syscall.Syscall(gpLinkProgram, 1, uintptr(program), 0, 0)
} }
@ -317,6 +324,11 @@ func UniformMatrix4fv(location int32, count int32, transpose bool, value *float3
syscall.Syscall6(gpUniformMatrix4fv, 4, uintptr(location), uintptr(count), boolToUintptr(transpose), uintptr(unsafe.Pointer(value)), 0, 0) syscall.Syscall6(gpUniformMatrix4fv, 4, uintptr(location), uintptr(count), boolToUintptr(transpose), uintptr(unsafe.Pointer(value)), 0, 0)
} }
func UnmapBuffer(target uint32) bool {
ret, _, _ := syscall.Syscall(gpUnmapBuffer, 1, uintptr(target), 0, 0)
return ret != 0
}
func UseProgram(program uint32) { func UseProgram(program uint32) {
syscall.Syscall(gpUseProgram, 1, uintptr(program), 0, 0) syscall.Syscall(gpUseProgram, 1, uintptr(program), 0, 0)
} }
@ -466,6 +478,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) uintptr) error {
if gpIsTexture == 0 { if gpIsTexture == 0 {
return errors.New("glIsTexture") return errors.New("glIsTexture")
} }
gpMapBuffer = getProcAddr("glMapBuffer")
if gpMapBuffer == 0 {
return errors.New("glMapBuffer")
}
gpLinkProgram = getProcAddr("glLinkProgram") gpLinkProgram = getProcAddr("glLinkProgram")
if gpLinkProgram == 0 { if gpLinkProgram == 0 {
return errors.New("glLinkProgram") return errors.New("glLinkProgram")
@ -514,6 +530,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) uintptr) error {
if gpUniformMatrix4fv == 0 { if gpUniformMatrix4fv == 0 {
return errors.New("glUniformMatrix4fv") return errors.New("glUniformMatrix4fv")
} }
gpUnmapBuffer = getProcAddr("glUnmapBuffer")
if gpUnmapBuffer == 0 {
return errors.New("glUnmapBuffer")
}
gpUseProgram = getProcAddr("glUseProgram") gpUseProgram = getProcAddr("glUseProgram")
if gpUseProgram == 0 { if gpUseProgram == 0 {
return errors.New("glUseProgram") return errors.New("glUseProgram")

View File

@ -22,6 +22,7 @@ type Image struct {
driver *Driver driver *Driver
textureNative textureNative textureNative textureNative
framebuffer *framebuffer framebuffer *framebuffer
pbo buffer
width int width int
height int height int
screen bool screen bool
@ -32,6 +33,10 @@ func (i *Image) IsInvalidated() bool {
} }
func (i *Image) Dispose() { func (i *Image) Dispose() {
thePBOState.ensurePBOUnmapped()
if i.pbo != *new(buffer) {
i.driver.context.deleteBuffer(i.pbo)
}
if i.framebuffer != nil { if i.framebuffer != nil {
i.framebuffer.delete(&i.driver.context) i.framebuffer.delete(&i.driver.context)
} }
@ -53,6 +58,7 @@ func (i *Image) setViewport() error {
} }
func (i *Image) Pixels() ([]byte, error) { func (i *Image) Pixels() ([]byte, error) {
thePBOState.ensurePBOUnmapped()
if err := i.ensureFramebuffer(); err != nil { if err := i.ensureFramebuffer(); err != nil {
return nil, err return nil, err
} }
@ -96,7 +102,13 @@ func (i *Image) ReplacePixels(p []byte, x, y, width, height int) {
i.driver.context.flush() i.driver.context.flush()
} }
i.driver.drawCalled = false i.driver.drawCalled = false
i.driver.context.texSubImage2D(i.textureNative, p, x, y, width, height)
if canUsePBO {
thePBOState.mapPBOIfNecessary(i)
thePBOState.draw(p, x, y, width, height)
} else {
i.driver.context.texSubImage2D(i.textureNative, p, x, y, width, height)
}
} }
func (i *Image) SetAsSource() { func (i *Image) SetAsSource() {

View File

@ -0,0 +1,87 @@
// 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 (
"reflect"
"runtime"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/graphics"
)
const canUsePBO = true
type pboState struct {
image *Image
mappedPBO unsafe.Pointer
}
var thePBOState pboState
func (s *pboState) mapPBOIfNecessary(img *Image) {
if s.image == img {
return
}
s.ensurePBOUnmapped()
if img.pbo == *new(buffer) {
w, h := graphics.InternalImageSize(img.width), graphics.InternalImageSize(img.height)
img.pbo = img.driver.context.newPixelBufferObject(w, h)
}
s.image = img
s.mappedPBO = img.driver.context.mapPixelBuffer(img.pbo)
if s.mappedPBO == nil {
panic("opengl: mapPixelBuffer failed")
}
}
func (s *pboState) draw(pix []byte, x, y, width, height int) {
w, h := graphics.InternalImageSize(s.image.width), graphics.InternalImageSize(s.image.height)
var mapped []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mapped))
sh.Data = uintptr(s.mappedPBO)
sh.Len = 4 * w * h
sh.Cap = 4 * w * h
stride := 4 * w
offset := 4 * (y*w + x)
for j := 0; j < height; j++ {
copy(mapped[offset+stride*j:offset+stride*j+4*width], pix[4*width*j:4*width*(j+1)])
}
runtime.KeepAlive(mapped)
}
func (s *pboState) ensurePBOUnmapped() {
if s.mappedPBO == nil {
return
}
i := s.image
w, h := graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height)
i.driver.context.unmapPixelBuffer(i.pbo, i.textureNative, w, h)
s.image = nil
s.mappedPBO = nil
}

View File

@ -0,0 +1,35 @@
// 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
const canUsePBO = false
type pboState struct{}
var thePBOState pboState
func (s *pboState) mapPBOIfNecessary(img *Image) {
panic("opengl: PBO is not available in this environment")
}
func (s *pboState) draw(pix []byte, x, y, width, height int) {
panic("opengl: PBO is not available in this environment")
}
func (s *pboState) ensurePBOUnmapped() {
// Do nothing
}

View File

@ -265,6 +265,8 @@ func (d *Driver) useProgram(mode driver.CompositeMode, colorM *affine.ColorM, fi
panic("source image is not set") panic("source image is not set")
} }
thePBOState.ensurePBOUnmapped()
if err := destination.setViewport(); err != nil { if err := destination.setViewport(); err != nil {
return err return err
} }