diff --git a/image_test.go b/image_test.go index 6ed2f31c1..5d56d6c12 100644 --- a/image_test.go +++ b/image_test.go @@ -1318,6 +1318,87 @@ func TestImageAddressRepeat(t *testing.T) { } } +func TestImageAddressRepeatNegativePosition(t *testing.T) { + const w, h = 16, 16 + src := ebiten.NewImage(w, h) + dst := ebiten.NewImage(w, h) + pix := make([]byte, 4*w*h) + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + idx := 4 * (i + j*w) + if 4 <= i && i < 8 && 4 <= j && j < 8 { + pix[idx] = byte(i-4) * 0x10 + pix[idx+1] = byte(j-4) * 0x10 + pix[idx+2] = 0 + pix[idx+3] = 0xff + } else { + pix[idx] = 0 + pix[idx+1] = 0 + pix[idx+2] = 0xff + pix[idx+3] = 0xff + } + } + } + src.ReplacePixels(pix) + + vs := []ebiten.Vertex{ + { + DstX: 0, + DstY: 0, + SrcX: -w, + SrcY: -h, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: w, + DstY: 0, + SrcX: 0, + SrcY: -h, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: 0, + DstY: h, + SrcX: -w, + SrcY: 0, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: w, + DstY: h, + SrcX: 0, + SrcY: 0, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + } + is := []uint16{0, 1, 2, 1, 2, 3} + op := &ebiten.DrawTrianglesOptions{} + op.Address = ebiten.AddressRepeat + dst.DrawTriangles(vs, is, src.SubImage(image.Rect(4, 4, 8, 8)).(*ebiten.Image), op) + + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + got := dst.At(i, j).(color.RGBA) + want := color.RGBA{byte(i%4) * 0x10, byte(j%4) * 0x10, 0, 0xff} + if !sameColors(got, want, 1) { + t.Errorf("dst.At(%d, %d): got %v, want: %v", i, j, got, want) + } + } + } +} + func TestImageReplacePixelsAfterClear(t *testing.T) { const w, h = 256, 256 img := ebiten.NewImage(w, h) diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 8acf297e0..f79c59eec 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -92,10 +92,8 @@ vertex VertexOut VertexShader( return out; } -float FloorMod(float x, float y) { - if (x < 0.0) { - return y - (-x - y * floor(-x/y)); - } +float EuclideanMod(float x, float y) { + // Assume that y is always positive. return x - y * floor(x/y); } @@ -111,7 +109,7 @@ template<> inline float2 AdjustTexelByAddress(float2 p, float4 source_region) { float2 o = float2(source_region[0], source_region[1]); float2 size = float2(source_region[2] - source_region[0], source_region[3] - source_region[1]); - return float2(FloorMod((p.x - o.x), size.x) + o.x, FloorMod((p.y - o.y), size.y) + o.y); + return float2(EuclideanMod((p.x - o.x), size.x) + o.x, EuclideanMod((p.y - o.y), size.y) + o.y); } template diff --git a/internal/graphicsdriver/opengl/defaultshader.go b/internal/graphicsdriver/opengl/defaultshader.go index cd999caf6..b63d18259 100644 --- a/internal/graphicsdriver/opengl/defaultshader.go +++ b/internal/graphicsdriver/opengl/defaultshader.go @@ -158,13 +158,6 @@ uniform highp float scale; varying highp vec2 varying_tex; varying highp vec4 varying_color_scale; -highp float floorMod(highp float x, highp float y) { - if (x < 0.0) { - return y - (-x - y * floor(-x/y)); - } - return x - y * floor(x/y); -} - highp vec2 adjustTexelByAddress(highp vec2 p, highp vec4 source_region) { #if defined(ADDRESS_CLAMP_TO_ZERO) return p; @@ -173,7 +166,7 @@ highp vec2 adjustTexelByAddress(highp vec2 p, highp vec4 source_region) { #if defined(ADDRESS_REPEAT) highp vec2 o = vec2(source_region[0], source_region[1]); highp vec2 size = vec2(source_region[2] - source_region[0], source_region[3] - source_region[1]); - return vec2(floorMod((p.x - o.x), size.x) + o.x, floorMod((p.y - o.y), size.y) + o.y); + return vec2(mod((p.x - o.x), size.x) + o.x, mod((p.y - o.y), size.y) + o.y); #endif #if defined(ADDRESS_UNSAFE)