graphics: Specify rect to glTexSubImage2D

This commit is contained in:
Hajime Hoshi 2018-03-01 00:27:55 +09:00
parent 9f6fd0db9a
commit 127f6c09c8
8 changed files with 81 additions and 34 deletions

View File

@ -283,12 +283,7 @@ func (i *Image) ReplacePixels(p []byte) error {
if l := 4 * w * h; len(p) != l { if l := 4 * w * h; len(p) != l {
panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l)) panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l))
} }
i.restorable.ReplacePixels(p, 0, 0, w, h)
// Copy the pixels so that this works even p is modified just after ReplacePixels.
pix := make([]byte, len(p))
copy(pix, p)
i.restorable.ReplacePixels(pix)
return nil return nil
} }

View File

@ -269,6 +269,10 @@ func (c *drawImageCommand) quadsNum() int {
type replacePixelsCommand struct { type replacePixelsCommand struct {
dst *Image dst *Image
pixels []byte pixels []byte
x int
y int
width int
height int
} }
// Exec executes the replacePixelsCommand. // Exec executes the replacePixelsCommand.
@ -283,7 +287,7 @@ func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
// glTexSubImage2D didn't work without this hack at least on Nexus 5x and NuAns NEO [Reloaded] (#211). // glTexSubImage2D didn't work without this hack at least on Nexus 5x and NuAns NEO [Reloaded] (#211).
opengl.GetContext().Flush() opengl.GetContext().Flush()
opengl.GetContext().BindTexture(c.dst.texture.native) opengl.GetContext().BindTexture(c.dst.texture.native)
opengl.GetContext().TexSubImage2D(c.pixels, c.dst.width, c.dst.height) opengl.GetContext().TexSubImage2D(c.pixels, c.x, c.y, c.width, c.height)
return nil return nil
} }

View File

@ -86,12 +86,16 @@ func (i *Image) Pixels() ([]byte, error) {
return opengl.GetContext().FramebufferPixels(f.native, i.width, i.height) return opengl.GetContext().FramebufferPixels(f.native, i.width, i.height)
} }
func (i *Image) ReplacePixels(p []byte) { func (i *Image) ReplacePixels(p []byte, x, y, width, height int) {
pixels := make([]byte, len(p)) pixels := make([]byte, len(p))
copy(pixels, p) copy(pixels, p)
c := &replacePixelsCommand{ c := &replacePixelsCommand{
dst: i, dst: i,
pixels: pixels, pixels: pixels,
x: x,
y: y,
width: width,
height: height,
} }
theCommandQueue.Enqueue(c) theCommandQueue.Enqueue(c)
} }

View File

@ -165,8 +165,8 @@ func (c *Context) bindFramebufferImpl(f Framebuffer) {
}) })
} }
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) { func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]byte, error) {
var pixels []uint8 var pixels []byte
if err := c.runOnContextThread(func() error { if err := c.runOnContextThread(func() error {
gl.Flush() gl.Flush()
return nil return nil
@ -175,7 +175,7 @@ func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8,
} }
c.bindFramebuffer(f) c.bindFramebuffer(f)
if err := c.runOnContextThread(func() error { if err := c.runOnContextThread(func() error {
pixels = make([]uint8, 4*width*height) pixels = make([]byte, 4*width*height)
gl.ReadPixels(0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(pixels)) gl.ReadPixels(0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(pixels))
if e := gl.GetError(); e != gl.NO_ERROR { if e := gl.GetError(); e != gl.NO_ERROR {
pixels = nil pixels = nil
@ -218,9 +218,9 @@ func (c *Context) IsTexture(t Texture) bool {
return r return r
} }
func (c *Context) TexSubImage2D(p []uint8, width, height int) { func (c *Context) TexSubImage2D(p []byte, x, y, width, height int) {
_ = c.runOnContextThread(func() error { _ = c.runOnContextThread(func() error {
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(p)) gl.TexSubImage2D(gl.TEXTURE_2D, 0, int32(x), int32(y), int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(p))
return nil return nil
}) })
} }
@ -312,10 +312,10 @@ func (c *Context) NewShader(shaderType ShaderType, source string) (Shader, error
var v int32 var v int32
gl.GetShaderiv(s, gl.COMPILE_STATUS, &v) gl.GetShaderiv(s, gl.COMPILE_STATUS, &v)
if v == gl.FALSE { if v == gl.FALSE {
log := []uint8{} log := []byte{}
gl.GetShaderiv(uint32(s), gl.INFO_LOG_LENGTH, &v) gl.GetShaderiv(uint32(s), gl.INFO_LOG_LENGTH, &v)
if v != 0 { if v != 0 {
log = make([]uint8, int(v)) log = make([]byte, int(v))
gl.GetShaderInfoLog(uint32(s), v, nil, (*uint8)(gl.Ptr(log))) gl.GetShaderInfoLog(uint32(s), v, nil, (*uint8)(gl.Ptr(log)))
} }
return fmt.Errorf("opengl: shader compile failed: %s", log) return fmt.Errorf("opengl: shader compile failed: %s", log)

View File

@ -143,8 +143,6 @@ func (c *Context) NewTexture(width, height int) (Texture, error) {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
// TODO: Can we use glTexSubImage2D with linear filtering?
// void texImage2D(GLenum target, GLint level, GLenum internalformat, // void texImage2D(GLenum target, GLint level, GLenum internalformat,
// GLsizei width, GLsizei height, GLint border, GLenum format, // GLsizei width, GLsizei height, GLint border, GLenum format,
// GLenum type, ArrayBufferView? pixels); // GLenum type, ArrayBufferView? pixels);
@ -158,7 +156,7 @@ func (c *Context) bindFramebufferImpl(f Framebuffer) {
gl.BindFramebuffer(gl.FRAMEBUFFER, f.(*js.Object)) gl.BindFramebuffer(gl.FRAMEBUFFER, f.(*js.Object))
} }
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) { func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]byte, error) {
gl := c.gl gl := c.gl
c.bindFramebuffer(f) c.bindFramebuffer(f)
@ -168,7 +166,7 @@ func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8,
if e := gl.GetError(); e != gl.NO_ERROR { if e := gl.GetError(); e != gl.NO_ERROR {
return nil, errors.New(fmt.Sprintf("opengl: error: %d", e)) return nil, errors.New(fmt.Sprintf("opengl: error: %d", e))
} }
return pixels.Interface().([]uint8), nil return pixels.Interface().([]byte), nil
} }
func (c *Context) bindTextureImpl(t Texture) { func (c *Context) bindTextureImpl(t Texture) {
@ -193,12 +191,12 @@ func (c *Context) IsTexture(t Texture) bool {
return b return b
} }
func (c *Context) TexSubImage2D(p []uint8, width, height int) { func (c *Context) TexSubImage2D(p []byte, x, y, width, height int) {
gl := c.gl gl := c.gl
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, // void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
// GLsizei width, GLsizei height, // GLsizei width, GLsizei height,
// GLenum format, GLenum type, ArrayBufferView? pixels); // GLenum format, GLenum type, ArrayBufferView? pixels);
gl.Call("texSubImage2D", gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p) gl.Call("texSubImage2D", gl.TEXTURE_2D, 0, x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, p)
} }
func (c *Context) NewFramebuffer(t Texture) (Framebuffer, error) { func (c *Context) NewFramebuffer(t Texture) (Framebuffer, error) {

View File

@ -144,13 +144,13 @@ func (c *Context) bindFramebufferImpl(f Framebuffer) {
gl.BindFramebuffer(mgl.FRAMEBUFFER, mgl.Framebuffer(f)) gl.BindFramebuffer(mgl.FRAMEBUFFER, mgl.Framebuffer(f))
} }
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) { func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]byte, error) {
gl := c.gl gl := c.gl
gl.Flush() gl.Flush()
c.bindFramebuffer(f) c.bindFramebuffer(f)
pixels := make([]uint8, 4*width*height) pixels := make([]byte, 4*width*height)
gl.ReadPixels(pixels, 0, 0, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE) gl.ReadPixels(pixels, 0, 0, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE)
if e := gl.GetError(); e != mgl.NO_ERROR { if e := gl.GetError(); e != mgl.NO_ERROR {
return nil, fmt.Errorf("opengl: glReadPixels: %d", e) return nil, fmt.Errorf("opengl: glReadPixels: %d", e)
@ -179,9 +179,9 @@ func (c *Context) IsTexture(t Texture) bool {
return gl.IsTexture(mgl.Texture(t)) return gl.IsTexture(mgl.Texture(t))
} }
func (c *Context) TexSubImage2D(p []uint8, width, height int) { func (c *Context) TexSubImage2D(p []byte, x, y, width, height int) {
gl := c.gl gl := c.gl
gl.TexSubImage2D(mgl.TEXTURE_2D, 0, 0, 0, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE, p) gl.TexSubImage2D(mgl.TEXTURE_2D, 0, x, y, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE, p)
} }
func (c *Context) NewFramebuffer(texture Texture) (Framebuffer, error) { func (c *Context) NewFramebuffer(texture Texture) (Framebuffer, error) {
@ -351,9 +351,9 @@ func (c *Context) DisableVertexAttribArray(p Program, location string) {
gl.DisableVertexAttribArray(mgl.Attrib(l)) gl.DisableVertexAttribArray(mgl.Attrib(l))
} }
func uint16ToBytes(v []uint16) []uint8 { func uint16ToBytes(v []uint16) []byte {
// TODO: Consider endian? // TODO: Consider endian?
b := make([]uint8, len(v)*2) b := make([]byte, len(v)*2)
for i, x := range v { for i, x := range v {
b[2*i] = uint8(x) b[2*i] = uint8(x)
b[2*i+1] = uint8(x >> 8) b[2*i+1] = uint8(x >> 8)

View File

@ -16,6 +16,7 @@ package restorable
import ( import (
"errors" "errors"
"fmt"
"image/color" "image/color"
"runtime" "runtime"
@ -155,10 +156,27 @@ func (i *Image) ClearFramebuffer() {
} }
// ReplacePixels replaces the image pixels with the given pixels slice. // ReplacePixels replaces the image pixels with the given pixels slice.
func (i *Image) ReplacePixels(pixels []byte) { func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
w, h := i.image.Size()
if width <= 0 || height <= 0 {
panic("restorable: width/height must be positive")
}
if x < 0 || y < 0 || w <= x || h <= y || x+width <= 0 || y+height <= 0 || w < x+width || h < y+height {
panic(fmt.Sprintf("restorable: out of range x: %d, y: %d, width: %d, height: %d", x, y, width, height))
}
theImages.makeStaleIfDependingOn(i) theImages.makeStaleIfDependingOn(i)
i.image.ReplacePixels(pixels) i.image.ReplacePixels(pixels, x, y, width, height)
i.basePixels = pixels
// Copy the pixels so that this works even p is modified just after ReplacePixels.
if i.basePixels == nil {
w, h := i.image.Size()
i.basePixels = make([]byte, 4*w*h)
}
idx := 4 * (y*w + x)
for j := 0; j < height; j++ {
copy(i.basePixels[idx:idx+4*width], pixels[4*j*width:4*(j+1)*width])
idx += 4 * w
}
i.drawImageHistory = nil i.drawImageHistory = nil
i.stale = false i.stale = false
} }
@ -316,11 +334,11 @@ func (i *Image) restore() error {
} }
gimg := graphics.NewImage(w, h) gimg := graphics.NewImage(w, h)
if i.basePixels != nil { if i.basePixels != nil {
gimg.ReplacePixels(i.basePixels) gimg.ReplacePixels(i.basePixels, 0, 0, w, h)
} else { } else {
// Clear the image explicitly. // Clear the image explicitly.
pix := make([]uint8, w*h*4) pix := make([]uint8, w*h*4)
gimg.ReplacePixels(pix) gimg.ReplacePixels(pix, 0, 0, w, h)
} }
for _, c := range i.drawImageHistory { for _, c := range i.drawImageHistory {
// All dependencies must be already resolved. // All dependencies must be already resolved.

View File

@ -75,7 +75,7 @@ func fill(img *Image, r, g, b, a uint8) {
pix[4*i+2] = b pix[4*i+2] = b
pix[4*i+3] = a pix[4*i+3] = a
} }
img.ReplacePixels(pix) img.ReplacePixels(pix, 0, 0, w, h)
} }
func TestRestore(t *testing.T) { func TestRestore(t *testing.T) {
@ -326,7 +326,7 @@ func TestRestoreComplexGraph(t *testing.T) {
func newImageFromImage(rgba *image.RGBA) *Image { func newImageFromImage(rgba *image.RGBA) *Image {
s := rgba.Bounds().Size() s := rgba.Bounds().Size()
img := NewImage(s.X, s.Y, false) img := NewImage(s.X, s.Y, false)
img.ReplacePixels(rgba.Pix) img.ReplacePixels(rgba.Pix, 0, 0, s.X, s.Y)
return img return img
} }
@ -381,4 +381,32 @@ func TestRestoreRecursive(t *testing.T) {
} }
} }
func TestReplacePixels(t *testing.T) {
const (
w = 17
h = 31
)
img := NewImage(17, 31, false)
pix := make([]byte, 4*4*4)
for i := range pix {
pix[i] = 0xff
}
img.ReplacePixels(pix, 5, 7, 4, 4)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got, err := img.At(i, j)
if err != nil {
t.Fatal(err)
}
want := color.RGBA{}
if 5 <= i && i < 9 && 7 <= j && j < 11 {
want = color.RGBA{0xff, 0xff, 0xff, 0xff}
}
if got != want {
t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
// TODO: How about volatile/screen images? // TODO: How about volatile/screen images?