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 (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
@ -2067,63 +2068,61 @@ func BenchmarkImageDrawOver(b *testing.B) {
}
// Issue #1171
func Disabled_TestImageFloatTranslate(t *testing.T) {
const w, h = 16, 16
func TestImageFloatTranslate(t *testing.T) {
const w, h = 32, 32
dst, _ := NewImage(320, 240, FilterDefault)
src, _ := NewImage(w, h, FilterDefault)
pix := make([]byte, 4*w*h)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
pix[4*(j*w+i)] = byte(j)
pix[4*(j*w+i)+3] = 0xff
}
}
src.ReplacePixels(pix)
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(2, 2)
op.GeoM.Translate(0, 0.501)
dst.DrawImage(src, op)
op := &DrawImageOptions{}
op.GeoM.Scale(float64(s), float64(s))
op.GeoM.Translate(0, 0.501)
dst.DrawImage(src, 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)
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)
}
}
}
}
}
}
}
// 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)
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)
}
}
t.Run("image", func(t *testing.T) {
src, _ := NewImage(w, h, FilterDefault)
pix := make([]byte, 4*w*h)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
pix[4*(j*w+i)] = byte(j)
pix[4*(j*w+i)+3] = 0xff
}
}
src.ReplacePixels(pix)
check(src)
})
t.Run("subimage", func(t *testing.T) {
src, _ := NewImage(w*s, h*s, FilterDefault)
pix := make([]byte, 4*(w*s)*(h*s))
for j := 0; j < h*s; j++ {
for i := 0; i < w*s; i++ {
pix[4*(j*(w*s)+i)] = byte(j)
pix[4*(j*(w*s)+i)+3] = 0xff
}
}
src.ReplacePixels(pix)
check(src.SubImage(image.Rect(0, 0, w, h)).(*Image))
})
})
}
}

View File

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