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 (
"errors"
"fmt"
"unsafe"
"github.com/hajimehoshi/ebiten/internal/driver"
"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl/gl"
@ -509,3 +510,42 @@ func (c *context) flush() {
func (c *context) needsRestoring() bool {
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 {
return !web.IsMobileBrowser()
}
func (c *context) canUsePBO() bool {
return false
}

View File

@ -20,6 +20,8 @@ const (
ARRAY_BUFFER = 0x8892
ELEMENT_ARRAY_BUFFER = 0x8893
DYNAMIC_DRAW = 0x88E8
STREAM_DRAW = 0x88E0
PIXEL_UNPACK_BUFFER = 0x88EC
SHORT = 0x1402
FLOAT = 0x1406
@ -56,6 +58,7 @@ const (
UNSIGNED_BYTE = 0x1401
UNSIGNED_SHORT = 0x1403
VERTEX_SHADER = 0x8B31
WRITE_ONLY = 0x88B9
)
// 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 GPISTEXTURE)(GLuint texture);
// 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 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);
@ -155,6 +156,7 @@ package gl
// 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 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 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);
@ -303,6 +305,9 @@ package gl
// static void glowLinkProgram(GPLINKPROGRAM fnptr, GLuint 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) {
// (*fnptr)(pname, param);
// }
@ -336,6 +341,9 @@ package gl
// static void glowUniformMatrix4fv(GPUNIFORMMATRIX4FV fnptr, GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) {
// (*fnptr)(location, count, transpose, value);
// }
// static GLboolean glowUnmapBuffer(GPUNMAPBUFFER fnptr, GLenum target) {
// return (*fnptr)(target);
// }
// static void glowUseProgram(GPUSEPROGRAM fnptr, GLuint program) {
// (*fnptr)(program);
// }
@ -401,6 +409,7 @@ var (
gpIsProgram C.GPISPROGRAM
gpIsTexture C.GPISTEXTURE
gpLinkProgram C.GPLINKPROGRAM
gpMapBuffer C.GPMAPBUFFER
gpPixelStorei C.GPPIXELSTOREI
gpReadPixels C.GPREADPIXELS
gpShaderSource C.GPSHADERSOURCE
@ -412,6 +421,7 @@ var (
gpUniform2fv C.GPUNIFORM2FV
gpUniform4fv C.GPUNIFORM4FV
gpUniformMatrix4fv C.GPUNIFORMMATRIX4FV
gpUnmapBuffer C.GPUNMAPBUFFER
gpUseProgram C.GPUSEPROGRAM
gpVertexAttribPointer C.GPVERTEXATTRIBPOINTER
gpViewport C.GPVIEWPORT
@ -616,6 +626,11 @@ func LinkProgram(program uint32) {
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) {
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)))
}
func UnmapBuffer(target uint32) bool {
ret := C.glowUnmapBuffer(gpUnmapBuffer, (C.GLenum)(target))
return ret == TRUE
}
func UseProgram(program uint32) {
C.glowUseProgram(gpUseProgram, (C.GLuint)(program))
}
@ -813,6 +833,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
if gpLinkProgram == nil {
return errors.New("glLinkProgram")
}
gpMapBuffer = (C.GPMAPBUFFER)(getProcAddr("glMapBuffer"))
if gpMapBuffer == nil {
return errors.New("glMapBuffer")
}
gpPixelStorei = (C.GPPIXELSTOREI)(getProcAddr("glPixelStorei"))
if gpPixelStorei == nil {
return errors.New("glPixelStorei")
@ -857,6 +881,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error {
if gpUniformMatrix4fv == nil {
return errors.New("glUniformMatrix4fv")
}
gpUnmapBuffer = (C.GPUNMAPBUFFER)(getProcAddr("glUnmapBuffer"))
if gpUnmapBuffer == nil {
return errors.New("glUnmapBuffer")
}
gpUseProgram = (C.GPUSEPROGRAM)(getProcAddr("glUseProgram"))
if gpUseProgram == nil {
return errors.New("glUseProgram")

View File

@ -58,6 +58,7 @@ var (
gpIsProgram uintptr
gpIsTexture uintptr
gpLinkProgram uintptr
gpMapBuffer uintptr
gpPixelStorei uintptr
gpReadPixels uintptr
gpShaderSource uintptr
@ -69,6 +70,7 @@ var (
gpUniform2fv uintptr
gpUniform4fv uintptr
gpUniformMatrix4fv uintptr
gpUnmapBuffer uintptr
gpUseProgram uintptr
gpVertexAttribPointer uintptr
gpViewport uintptr
@ -269,6 +271,11 @@ func IsTexture(texture uint32) bool {
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) {
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)
}
func UnmapBuffer(target uint32) bool {
ret, _, _ := syscall.Syscall(gpUnmapBuffer, 1, uintptr(target), 0, 0)
return ret != 0
}
func UseProgram(program uint32) {
syscall.Syscall(gpUseProgram, 1, uintptr(program), 0, 0)
}
@ -466,6 +478,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) uintptr) error {
if gpIsTexture == 0 {
return errors.New("glIsTexture")
}
gpMapBuffer = getProcAddr("glMapBuffer")
if gpMapBuffer == 0 {
return errors.New("glMapBuffer")
}
gpLinkProgram = getProcAddr("glLinkProgram")
if gpLinkProgram == 0 {
return errors.New("glLinkProgram")
@ -514,6 +530,10 @@ func InitWithProcAddrFunc(getProcAddr func(name string) uintptr) error {
if gpUniformMatrix4fv == 0 {
return errors.New("glUniformMatrix4fv")
}
gpUnmapBuffer = getProcAddr("glUnmapBuffer")
if gpUnmapBuffer == 0 {
return errors.New("glUnmapBuffer")
}
gpUseProgram = getProcAddr("glUseProgram")
if gpUseProgram == 0 {
return errors.New("glUseProgram")

View File

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

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")
}
thePBOState.ensurePBOUnmapped()
if err := destination.setViewport(); err != nil {
return err
}