graphicscommand: Span to 1/3 pixels

The center of pixels is problematic as the behavior depends on GPU.
In order to avoid this, align the vertices with about 1/3 pixels.

Updates #929
Fixes #1171
This commit is contained in:
Hajime Hoshi 2020-06-16 01:40:28 +09:00
parent 0b94e2e036
commit 484473b6d9
2 changed files with 69 additions and 78 deletions

View File

@ -16,6 +16,7 @@ package ebiten_test
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@ -2067,10 +2068,37 @@ func BenchmarkImageDrawOver(b *testing.B) {
} }
// Issue #1171 // Issue #1171
func Disabled_TestImageFloatTranslate(t *testing.T) { func TestImageFloatTranslate(t *testing.T) {
const w, h = 16, 16 const w, h = 32, 32
dst, _ := NewImage(320, 240, FilterDefault) for s := 2; s <= 8; s++ {
s := s
t.Run(fmt.Sprintf("scale%d", s), func(t *testing.T) {
check := func(src *Image) {
dst, _ := NewImage(w*(s+1), h*(s+1), FilterDefault)
dst.Fill(color.RGBA{0xff, 0, 0, 0xff})
op := &DrawImageOptions{}
op.GeoM.Scale(float64(s), float64(s))
op.GeoM.Translate(0, 0.501)
dst.DrawImage(src, op)
for j := 0; j < h*s+1; j++ {
for i := 0; i < w*s; i++ {
got := dst.At(i, j)
x := byte(0xff)
if j > 0 {
x = (byte(j) - 1) / byte(s)
}
want := color.RGBA{x, 0, 0, 0xff}
if got != want {
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
t.Run("image", func(t *testing.T) {
src, _ := NewImage(w, h, FilterDefault) src, _ := NewImage(w, h, FilterDefault)
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
for j := 0; j < h; j++ { for j := 0; j < h; j++ {
@ -2080,50 +2108,21 @@ func Disabled_TestImageFloatTranslate(t *testing.T) {
} }
} }
src.ReplacePixels(pix) src.ReplacePixels(pix)
check(src)
})
op := &DrawImageOptions{} t.Run("subimage", func(t *testing.T) {
op.GeoM.Scale(2, 2) src, _ := NewImage(w*s, h*s, FilterDefault)
op.GeoM.Translate(0, 0.501) pix := make([]byte, 4*(w*s)*(h*s))
dst.DrawImage(src, op) for j := 0; j < h*s; j++ {
for i := 0; i < w*s; i++ {
for j := 1; j < h*2+1; j++ { pix[4*(j*(w*s)+i)] = byte(j)
for i := 0; i < w*2; i++ { pix[4*(j*(w*s)+i)+3] = 0xff
got := dst.At(i, j)
want := color.RGBA{(byte(j) - 1) / 2, 0, 0, 0xff}
if got != want {
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
// Issue #1171
func Disabled_TestImageSubImageFloatTranslate(t *testing.T) {
const w, h = 16, 16
dst, _ := NewImage(320, 240, FilterDefault)
src, _ := NewImage(w*2, h*2, FilterDefault)
pix := make([]byte, 4*(w*2)*(h*2))
for j := 0; j < h*2; j++ {
for i := 0; i < w*2; i++ {
pix[4*(j*(w*2)+i)] = byte(j)
pix[4*(j*(w*2)+i)+3] = 0xff
} }
} }
src.ReplacePixels(pix) src.ReplacePixels(pix)
check(src.SubImage(image.Rect(0, 0, w, h)).(*Image))
op := &DrawImageOptions{} })
op.GeoM.Scale(2, 2) })
op.GeoM.Translate(0, 0.501)
dst.DrawImage(src.SubImage(image.Rect(0, 0, w, h)).(*Image), op)
for j := 1; j < h*2+1; j++ {
for i := 0; i < w*2; i++ {
got := dst.At(i, j)
want := color.RGBA{(byte(j) - 1) / 2, 0, 0, 0xff}
if got != want {
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
} }
} }

View File

@ -203,14 +203,6 @@ func (q *commandQueue) Enqueue(command command) {
q.commands = append(q.commands, command) q.commands = append(q.commands, command)
} }
func fract(x float32) float32 {
return x - float32(math.Floor(float64(x)))
}
const (
dstAdjustmentFactor = 1.0 / 256.0
)
// Flush flushes the command queue. // Flush flushes the command queue.
func (q *commandQueue) Flush() error { func (q *commandQueue) Flush() error {
if len(q.commands) == 0 { if len(q.commands) == 0 {
@ -236,22 +228,22 @@ func (q *commandQueue) Flush() error {
vs[i*graphics.VertexFloatNum+6] /= s.width vs[i*graphics.VertexFloatNum+6] /= s.width
vs[i*graphics.VertexFloatNum+7] /= s.height vs[i*graphics.VertexFloatNum+7] /= s.height
// Adjust the destination position to avoid jaggy (#929). // Avoid the center of the pixel, which is problematic (#929, #1171).
// This is not a perfect solution since texels on a texture can take a position on borders // Instead, align the vertices with about 1/3 pixels.
// which can cause jaggy. But adjusting only edges should work in most cases. for idx := 0; idx < 2; idx++ {
// The ideal solution is to fix shaders, but this makes the applications slow by adding 'if' x := vs[i*graphics.VertexFloatNum+idx]
// branches. int := float32(math.Floor(float64(x)))
switch f := fract(vs[i*graphics.VertexFloatNum+0]); { frac := x - int
case 0.5-dstAdjustmentFactor <= f && f < 0.5: switch {
vs[i*graphics.VertexFloatNum+0] -= f - (0.5 - dstAdjustmentFactor) case frac < 3.0/16.0:
case 0.5 <= f && f < 0.5+dstAdjustmentFactor: vs[i*graphics.VertexFloatNum+idx] = int
vs[i*graphics.VertexFloatNum+0] += (0.5 + dstAdjustmentFactor) - f case frac < 8.0/16.0:
vs[i*graphics.VertexFloatNum+idx] = int + 5.0/16.0
case frac < 13.0/16.0:
vs[i*graphics.VertexFloatNum+idx] = int + 11.0/16.0
default:
vs[i*graphics.VertexFloatNum+idx] = int + 16.0/16.0
} }
switch f := fract(vs[i*graphics.VertexFloatNum+1]); {
case 0.5-dstAdjustmentFactor <= f && f < 0.5:
vs[i*graphics.VertexFloatNum+1] -= f - (0.5 - dstAdjustmentFactor)
case 0.5 <= f && f < 0.5+dstAdjustmentFactor:
vs[i*graphics.VertexFloatNum+1] += (0.5 + dstAdjustmentFactor) - f
} }
} }
} else { } else {