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,63 +2068,61 @@ 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++ {
src, _ := NewImage(w, h, FilterDefault) s := s
pix := make([]byte, 4*w*h) t.Run(fmt.Sprintf("scale%d", s), func(t *testing.T) {
for j := 0; j < h; j++ { check := func(src *Image) {
for i := 0; i < w; i++ { dst, _ := NewImage(w*(s+1), h*(s+1), FilterDefault)
pix[4*(j*w+i)] = byte(j) dst.Fill(color.RGBA{0xff, 0, 0, 0xff})
pix[4*(j*w+i)+3] = 0xff
}
}
src.ReplacePixels(pix)
op := &DrawImageOptions{} op := &DrawImageOptions{}
op.GeoM.Scale(2, 2) op.GeoM.Scale(float64(s), float64(s))
op.GeoM.Translate(0, 0.501) op.GeoM.Translate(0, 0.501)
dst.DrawImage(src, op) dst.DrawImage(src, op)
for j := 1; j < h*2+1; j++ { for j := 0; j < h*s+1; j++ {
for i := 0; i < w*2; i++ { for i := 0; i < w*s; i++ {
got := dst.At(i, j) got := dst.At(i, j)
want := color.RGBA{(byte(j) - 1) / 2, 0, 0, 0xff} x := byte(0xff)
if got != want { if j > 0 {
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) 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)
pix := make([]byte, 4*w*h)
// Issue #1171 for j := 0; j < h; j++ {
func Disabled_TestImageSubImageFloatTranslate(t *testing.T) { for i := 0; i < w; i++ {
const w, h = 16, 16 pix[4*(j*w+i)] = byte(j)
pix[4*(j*w+i)+3] = 0xff
dst, _ := NewImage(320, 240, FilterDefault) }
src, _ := NewImage(w*2, h*2, FilterDefault) }
pix := make([]byte, 4*(w*2)*(h*2)) src.ReplacePixels(pix)
for j := 0; j < h*2; j++ { check(src)
for i := 0; i < w*2; i++ { })
pix[4*(j*(w*2)+i)] = byte(j)
pix[4*(j*(w*2)+i)+3] = 0xff t.Run("subimage", func(t *testing.T) {
} src, _ := NewImage(w*s, h*s, FilterDefault)
} pix := make([]byte, 4*(w*s)*(h*s))
src.ReplacePixels(pix) for j := 0; j < h*s; j++ {
for i := 0; i < w*s; i++ {
op := &DrawImageOptions{} pix[4*(j*(w*s)+i)] = byte(j)
op.GeoM.Scale(2, 2) pix[4*(j*(w*s)+i)+3] = 0xff
op.GeoM.Translate(0, 0.501) }
dst.DrawImage(src.SubImage(image.Rect(0, 0, w, h)).(*Image), op) }
src.ReplacePixels(pix)
for j := 1; j < h*2+1; j++ { check(src.SubImage(image.Rect(0, 0, w, h)).(*Image))
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
switch f := fract(vs[i*graphics.VertexFloatNum+1]); { case frac < 13.0/16.0:
case 0.5-dstAdjustmentFactor <= f && f < 0.5: vs[i*graphics.VertexFloatNum+idx] = int + 11.0/16.0
vs[i*graphics.VertexFloatNum+1] -= f - (0.5 - dstAdjustmentFactor) default:
case 0.5 <= f && f < 0.5+dstAdjustmentFactor: vs[i*graphics.VertexFloatNum+idx] = int + 16.0/16.0
vs[i*graphics.VertexFloatNum+1] += (0.5 + dstAdjustmentFactor) - f }
} }
} }
} else { } else {