graphics: Rename NextPowerOf2Int -> InternalImageSize and add image size adjustment

There is a minimum internal image size on some system like old iOS
devices. This change adds adjustment of the size.

Issue: #810
This commit is contained in:
Hajime Hoshi 2019-02-14 21:04:34 +09:00
parent a2fe3d5962
commit fd250c8d8c
10 changed files with 79 additions and 55 deletions

View File

@ -94,7 +94,7 @@ func TestImagePixels(t *testing.T) {
w, h := img0.Bounds().Size().X, img0.Bounds().Size().Y
// Check out of range part
w2, h2 := graphics.NextPowerOf2Int(w), graphics.NextPowerOf2Int(h)
w2, h2 := graphics.InternalImageSize(w), graphics.InternalImageSize(h)
for j := -100; j < h2+100; j++ {
for i := -100; i < w2+100; i++ {
got := img0.At(i, j)

View File

@ -14,10 +14,19 @@
package graphics
// NextPowerOf2Int returns a nearest power of 2 to x.
func NextPowerOf2Int(x int) int {
// minInternalImageSize is the minimum size of internal images (texture/framebuffer).
//
// For example, the image size less than 15 is not supported on some iOS devices.
// See also: https://stackoverflow.com/questions/15935651/certain-framebuffer-sizes-fail-on-ios-devices-gl-framebuffer-unsupported
const minInternalImageSize = 16
// InternalImageSize returns a nearest appropriate size as an internal image.
func InternalImageSize(x int) int {
if x <= 0 {
panic("x must be positive")
panic("graphics: x must be positive")
}
if x < minInternalImageSize {
return minInternalImageSize
}
r := 1
for r < x {
@ -25,3 +34,13 @@ func NextPowerOf2Int(x int) int {
}
return r
}
func isInternalImageSize(x int) bool {
if x <= 0 {
return false
}
if x < minInternalImageSize {
return false
}
return (x & (x - 1)) == 0
}

View File

@ -20,7 +20,7 @@ import (
. "github.com/hajimehoshi/ebiten/internal/graphics"
)
func TestNextPowerOf2(t *testing.T) {
func TestInternalImageSize(t *testing.T) {
testCases := []struct {
expected int
arg int
@ -31,7 +31,7 @@ func TestNextPowerOf2(t *testing.T) {
}
for _, testCase := range testCases {
got := NextPowerOf2Int(testCase.arg)
got := InternalImageSize(testCase.arg)
wanted := testCase.expected
if wanted != got {
t.Errorf("Clp(%d) = %d, wanted %d", testCase.arg, got, wanted)

View File

@ -59,19 +59,14 @@ func (v *verticesBackend) slice(n int) []float32 {
return s
}
func isPowerOf2(x int) bool {
if x <= 0 {
return false
}
return (x & (x - 1)) == 0
}
func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, cr, cg, cb, ca float32) []float32 {
if !isPowerOf2(width) {
panic(fmt.Sprintf("graphics: width must be power of 2 but not at QuadVertices: %d", width))
// For performance reason, graphics.InternalImageSize is not applied to width/height here.
if !isInternalImageSize(width) {
panic(fmt.Sprintf("graphics: width must be an internal image size at QuadVertices: %d", width))
}
if !isPowerOf2(height) {
panic(fmt.Sprintf("graphics: height must be power of 2 but not at QuadVertices: %d", height))
if !isInternalImageSize(height) {
panic(fmt.Sprintf("graphics: height must be an internal image size at QuadVertices: %d", height))
}
if sx0 >= sx1 || sy0 >= sy1 {
@ -166,11 +161,11 @@ func QuadIndices() []uint16 {
}
func PutVertex(vs []float32, width, height int, dx, dy, su, sv float32, u0, v0, u1, v1 float32, cr, cg, cb, ca float32) {
if !isPowerOf2(width) {
panic(fmt.Sprintf("graphics: width must be power of 2 but not at PutVertices: %d", width))
if !isInternalImageSize(width) {
panic(fmt.Sprintf("graphics: width must be an internal image size at PutVertices: %d", width))
}
if !isPowerOf2(height) {
panic(fmt.Sprintf("graphics: height must be power of 2 but not at PutVertices: %d", height))
if !isInternalImageSize(height) {
panic(fmt.Sprintf("graphics: height must be an internal image size at PutVertices: %d", height))
}
vs[0] = dx

View File

@ -340,10 +340,10 @@ func (d *Driver) checkSize(width, height int) {
})
if width < 1 {
panic(fmt.Sprintf("metal: width (%d) must be equal or more than 1", width))
panic(fmt.Sprintf("metal: width (%d) must be equal or more than %d", width, 1))
}
if height < 1 {
panic(fmt.Sprintf("metal: height (%d) must be equal or more than 1", height))
panic(fmt.Sprintf("metal: height (%d) must be equal or more than %d", height, 1))
}
if width > m {
panic(fmt.Sprintf("metal: width (%d) must be less than or equal to %d", width, m))
@ -357,8 +357,8 @@ func (d *Driver) NewImage(width, height int) (graphicsdriver.Image, error) {
d.checkSize(width, height)
td := mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: graphics.NextPowerOf2Int(width),
Height: graphics.NextPowerOf2Int(height),
Width: graphics.InternalImageSize(width),
Height: graphics.InternalImageSize(height),
StorageMode: mtl.StorageModeManaged,
// MTLTextureUsageRenderTarget might cause a problematic render result. Not sure the reason.
@ -581,8 +581,8 @@ func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode
rce.SetVertexBytes(unsafe.Pointer(&viewportSize[0]), unsafe.Sizeof(viewportSize), 1)
sourceSize := [...]float32{
float32(graphics.NextPowerOf2Int(d.src.width)),
float32(graphics.NextPowerOf2Int(d.src.height)),
float32(graphics.InternalImageSize(d.src.width)),
float32(graphics.InternalImageSize(d.src.height)),
}
rce.SetFragmentBytes(unsafe.Pointer(&sourceSize[0]), unsafe.Sizeof(sourceSize), 2)
@ -644,7 +644,7 @@ func (i *Image) viewportSize() (int, int) {
if i.screen {
return i.width, i.height
}
return graphics.NextPowerOf2Int(i.width), graphics.NextPowerOf2Int(i.height)
return graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height)
}
func (i *Image) Dispose() {

View File

@ -39,10 +39,10 @@ func (d *Driver) SetWindow(window uintptr) {
func (d *Driver) checkSize(width, height int) {
if width < 1 {
panic(fmt.Sprintf("opengl: width (%d) must be equal or more than 1", width))
panic(fmt.Sprintf("opengl: width (%d) must be equal or more than %d", width, 1))
}
if height < 1 {
panic(fmt.Sprintf("opengl: height (%d) must be equal or more than 1", height))
panic(fmt.Sprintf("opengl: height (%d) must be equal or more than %d", height, 1))
}
m := d.context.getMaxTextureSize()
if width > m {
@ -59,8 +59,8 @@ func (d *Driver) NewImage(width, height int) (graphicsdriver.Image, error) {
width: width,
height: height,
}
w := graphics.NextPowerOf2Int(width)
h := graphics.NextPowerOf2Int(height)
w := graphics.InternalImageSize(width)
h := graphics.InternalImageSize(height)
d.checkSize(w, h)
t, err := d.context.newTexture(w, h)
if err != nil {

View File

@ -76,7 +76,7 @@ func (i *Image) ensureFramebuffer() error {
return nil
}
w, h := graphics.NextPowerOf2Int(i.width), graphics.NextPowerOf2Int(i.height)
w, h := graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height)
f, err := newFramebufferFromTexture(&i.driver.context, i.textureNative, w, h)
if err != nil {
return err

View File

@ -310,8 +310,8 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM,
d.state.lastColorMatrixTranslation = esTranslate
}
sw := graphics.NextPowerOf2Int(srcW)
sh := graphics.NextPowerOf2Int(srcH)
sw := graphics.InternalImageSize(srcW)
sh := graphics.InternalImageSize(srcH)
if d.state.lastSourceWidth != sw || d.state.lastSourceHeight != sh {
d.context.uniformFloats(program, "source_size", []float32{float32(sw), float32(sh)})

View File

@ -230,8 +230,8 @@ func (i *Image) Size() (int, int) {
func (i *Image) InternalSize() (int, int) {
if i.w2 == 0 || i.h2 == 0 {
w, h := i.image.Size()
i.w2 = graphics.NextPowerOf2Int(w)
i.h2 = graphics.NextPowerOf2Int(h)
i.w2 = graphics.InternalImageSize(w)
i.h2 = graphics.InternalImageSize(h)
}
return i.w2, i.h2
}

View File

@ -105,6 +105,14 @@ func TestRestoreWithoutDraw(t *testing.T) {
}
}
func quadVertices(dw, dh, sw, sh, x, y int) []float32 {
// dw/dh must be internal image sizes.
return graphics.QuadVertices(dw, dh,
0, 0, sw, sh,
1, 0, 0, 1, float32(x), float32(y),
1, 1, 1, 1)
}
func TestRestoreChain(t *testing.T) {
const num = 10
imgs := []*Image{}
@ -120,8 +128,8 @@ func TestRestoreChain(t *testing.T) {
clr := color.RGBA{0x00, 0x00, 0x00, 0xff}
imgs[0].Fill(clr.R, clr.G, clr.B, clr.A)
for i := 0; i < num-1; i++ {
w, h := imgs[i].Size()
vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
w, h := imgs[i].InternalSize()
vs := quadVertices(w, h, 1, 1, 0, 0)
is := graphics.QuadIndices()
imgs[i+1].DrawImage(imgs[i], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
}
@ -162,7 +170,7 @@ func TestRestoreChain2(t *testing.T) {
clr8 := color.RGBA{0x00, 0x00, 0xff, 0xff}
imgs[8].Fill(clr8.R, clr8.G, clr8.B, clr8.A)
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(w), graphics.InternalImageSize(h), w, h, 0, 0)
is := graphics.QuadIndices()
imgs[8].DrawImage(imgs[7], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
imgs[9].DrawImage(imgs[8], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
@ -204,7 +212,7 @@ func TestRestoreOverrideSource(t *testing.T) {
clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff}
clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff}
img1.Fill(clr0.R, clr0.G, clr0.B, clr0.A)
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(w), graphics.InternalImageSize(h), w, h, 0, 0)
is := graphics.QuadIndices()
img2.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
img3.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
@ -284,24 +292,26 @@ func TestRestoreComplexGraph(t *testing.T) {
img1.Dispose()
img0.Dispose()
}()
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
dw := graphics.InternalImageSize(w)
dh := graphics.InternalImageSize(h)
vs := quadVertices(dw, dh, w, h, 0, 0)
is := graphics.QuadIndices()
img3.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 1, 0)
img3.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 1, 0)
img4.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 2, 0)
img4.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 0, 0)
img5.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 0, 0)
img6.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 1, 0)
img6.DrawImage(img4, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 0, 0)
img7.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
vs = graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0, 1, 1, 1, 1)
vs = quadVertices(dw, dh, w, h, 2, 0)
img7.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
ResolveStaleImages()
if err := Restore(); err != nil {
@ -391,7 +401,7 @@ func TestRestoreRecursive(t *testing.T) {
img1.Dispose()
img0.Dispose()
}()
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(w), graphics.InternalImageSize(h), w, h, 1, 0)
is := graphics.QuadIndices()
img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
@ -481,7 +491,7 @@ func TestDrawImageAndReplacePixels(t *testing.T) {
img1 := NewImage(2, 1)
defer img1.Dispose()
vs := graphics.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(1), graphics.InternalImageSize(1), 1, 1, 0, 0)
is := graphics.QuadIndices()
img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
@ -514,7 +524,7 @@ func TestDispose(t *testing.T) {
img2 := newImageFromImage(base2)
defer img2.Dispose()
vs := graphics.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(1), graphics.InternalImageSize(1), 1, 1, 0, 0)
is := graphics.QuadIndices()
img1.DrawImage(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
@ -608,7 +618,7 @@ func TestReplacePixelsOnly(t *testing.T) {
img0.ReplacePixels([]byte{1, 2, 3, 4}, i%w, i/w, 1, 1)
}
vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(w), graphics.InternalImageSize(h), 1, 1, 0, 0)
is := graphics.QuadIndices()
img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1)
@ -653,7 +663,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
// Second, draw src to dst. If the implementation is correct, dst becomes stale.
src.Fill(0xff, 0xff, 0xff, 0xff)
vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
vs := quadVertices(graphics.InternalImageSize(w), graphics.InternalImageSize(h), 1, 1, 0, 0)
is := graphics.QuadIndices()
dst.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)