From f1582c2d73bbccf83f1a6f050da30af7fec62dc5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 24 Dec 2018 03:00:00 +0900 Subject: [PATCH] graphics: Add Address representing a sampler address mode Fixes #761 --- examples/address/main.go | 124 ++++++++++++++++++++++ image.go | 23 +++- image_test.go | 81 ++++++++++++++ internal/graphics/filter.go | 7 ++ internal/graphicscommand/command.go | 29 ++--- internal/graphicscommand/image.go | 4 +- internal/graphicscommand/image_test.go | 2 +- internal/graphicsdriver/graphicsdriver.go | 2 +- internal/graphicsdriver/metal/driver.go | 59 ++++++++-- internal/graphicsdriver/opengl/driver.go | 4 +- internal/graphicsdriver/opengl/program.go | 8 +- internal/graphicsdriver/opengl/shader.go | 34 +++++- internal/restorable/image.go | 14 +-- internal/restorable/images_test.go | 44 ++++---- internal/shareable/shareable.go | 6 +- internal/shareable/shareable_test.go | 14 +-- 16 files changed, 380 insertions(+), 75 deletions(-) create mode 100644 examples/address/main.go diff --git a/examples/address/main.go b/examples/address/main.go new file mode 100644 index 000000000..e8554e8bb --- /dev/null +++ b/examples/address/main.go @@ -0,0 +1,124 @@ +// Copyright 2018 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build example jsgo + +package main + +import ( + "bytes" + "image" + _ "image/png" + "log" + + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/ebitenutil" + "github.com/hajimehoshi/ebiten/examples/resources/images" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +var ebitenImage *ebiten.Image + +func init() { + // Decode image from a byte slice instead of a file so that + // this example works in any working directory. + // If you want to use a file, there are some options: + // 1) Use os.Open and pass the file to the image decoder. + // This is a very regular way, but doesn't work on browsers. + // 2) Use ebitenutil.OpenFile and pass the file to the image decoder. + // This works even on browsers. + // 3) Use ebitenutil.NewImageFromFile to create an ebiten.Image directly from a file. + // This also works on browsers. + img, _, err := image.Decode(bytes.NewReader(images.Ebiten_png)) + if err != nil { + log.Fatal(err) + } + ebitenImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault) +} + +func drawRect(screen *ebiten.Image, img *ebiten.Image, x, y, width, height float32, address ebiten.Address, msg string) { + sx, sy := -width/2, -height/2 + vs := []ebiten.Vertex{ + { + DstX: x, + DstY: y, + SrcX: sx, + SrcY: sy, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: x + width, + DstY: y, + SrcX: sx + width, + SrcY: sy, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: x, + DstY: y + height, + SrcX: sx, + SrcY: sy + height, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: x + width, + DstY: y + height, + SrcX: sx + width, + SrcY: sy + height, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + } + op := &ebiten.DrawTrianglesOptions{} + op.Address = address + screen.DrawTriangles(vs, []uint16{0, 1, 2, 1, 2, 3}, img, op) + + ebitenutil.DebugPrintAt(screen, msg, int(x), int(y)-16) +} + +func update(screen *ebiten.Image) error { + if ebiten.IsDrawingSkipped() { + return nil + } + + const ox, oy = 40, 60 + drawRect(screen, ebitenImage, ox, oy, 200, 100, ebiten.AddressClampToZero, "Regular") + drawRect(screen, ebitenImage, 220+ox, oy, 200, 100, ebiten.AddressRepeat, "Regular, Repeat") + + subImage := ebitenImage.SubImage(image.Rect(10, 5, 20, 30)).(*ebiten.Image) + drawRect(screen, subImage, ox, 200+oy, 200, 100, ebiten.AddressClampToZero, "Subimage") + drawRect(screen, subImage, 220+ox, 200+oy, 200, 100, ebiten.AddressRepeat, "Subimage, Repeat") + return nil +} + +func main() { + if err := ebiten.Run(update, screenWidth, screenHeight, 1, "Sampler Address (Ebiten Demo)"); err != nil { + log.Fatal(err) + } +} diff --git a/image.go b/image.go index 5f3e3e8f2..599d64285 100644 --- a/image.go +++ b/image.go @@ -91,7 +91,7 @@ func (m *mipmap) level(r image.Rectangle, level int) *shareable.Image { vs = src.QuadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1) } is := graphics.QuadIndices() - s.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterLinear) + s.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterLinear, graphics.AddressClampToZero) imgs = append(imgs, s) w = w2 h = h2 @@ -387,7 +387,7 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) { src := img.mipmap.original() vs := src.QuadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca) is := graphics.QuadIndices() - i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter) + i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter, graphics.AddressClampToZero) } else if src := img.mipmap.level(bounds, level); src != nil { w, h := src.Size() s := 1 << uint(level) @@ -397,7 +397,7 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) { d *= float32(s) vs := src.QuadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca) is := graphics.QuadIndices() - i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter) + i.mipmap.original().DrawImage(src, vs, is, colorm, mode, filter, graphics.AddressClampToZero) } i.disposeMipmaps() } @@ -425,6 +425,17 @@ type Vertex struct { ColorA float32 } +// Address represents a sampler address mode. +type Address int + +const ( + // AddressClampToZero means that out-of-range texture coordinates return 0 (transparent). + AddressClampToZero = Address(graphics.AddressClampToZero) + + // AddressRepeat means that texture coordinates wrap to the other side of the texture. + AddressRepeat = Address(graphics.AddressRepeat) +) + // DrawTrianglesOptions represents options to render triangles on an image. // // Note that this API is experimental. @@ -441,6 +452,10 @@ type DrawTrianglesOptions struct { // Filter is a type of texture filter. // The default (zero) value is FilterDefault. Filter Filter + + // Address is a sampler address mode. + // The default (zero) value is AddressClampToZero. + Address Address } // MaxIndicesNum is the maximum number of indices for DrawTriangles. @@ -499,7 +514,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o float32(r.Min.X), float32(r.Min.Y), float32(r.Max.X), float32(r.Max.Y), v.ColorR, v.ColorG, v.ColorB, v.ColorA) } - i.mipmap.original().DrawImage(img.mipmap.original(), vs, indices, options.ColorM.impl, mode, filter) + i.mipmap.original().DrawImage(img.mipmap.original(), vs, indices, options.ColorM.impl, mode, filter, graphics.Address(options.Address)) i.disposeMipmaps() } diff --git a/image_test.go b/image_test.go index 83c4a4f3b..c3bd3334a 100644 --- a/image_test.go +++ b/image_test.go @@ -1258,3 +1258,84 @@ func TestImageLinearFilterGlitch(t *testing.T) { } } } + +func TestImageAddressRepeat(t *testing.T) { + const w, h = 16, 16 + src, _ := NewImage(w, h, FilterDefault) + dst, _ := NewImage(w, h, FilterDefault) + 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 := []Vertex{ + { + DstX: 0, + DstY: 0, + SrcX: 0, + SrcY: 0, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: w, + DstY: 0, + SrcX: w, + SrcY: 0, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: 0, + DstY: h, + SrcX: 0, + SrcY: h, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + { + DstX: w, + DstY: h, + SrcX: w, + SrcY: h, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }, + } + is := []uint16{0, 1, 2, 1, 2, 3} + op := &DrawTrianglesOptions{} + op.Address = AddressRepeat + dst.DrawTriangles(vs, is, src.SubImage(image.Rect(4, 4, 8, 8)).(*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) + } + } + } +} diff --git a/internal/graphics/filter.go b/internal/graphics/filter.go index 37e0bb97a..9e2954e3f 100644 --- a/internal/graphics/filter.go +++ b/internal/graphics/filter.go @@ -22,3 +22,10 @@ const ( FilterLinear FilterScreen ) + +type Address int + +const ( + AddressClampToZero Address = iota + AddressRepeat +) diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index f77700a4c..5e382d356 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -35,7 +35,7 @@ type command interface { NumIndices() int AddNumVertices(n int) AddNumIndices(n int) - CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool + CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool } // commandQueue is a command queue for drawing commands. @@ -87,12 +87,12 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) { q.nindices += len(indices) } -func (q *commandQueue) doEnqueueDrawImageCommand(dst, src *Image, nvertices, nindices int, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, forceNewCommand bool) { +func (q *commandQueue) doEnqueueDrawImageCommand(dst, src *Image, nvertices, nindices int, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address, forceNewCommand bool) { if nindices > graphics.IndicesNum { panic("not reached") } if !forceNewCommand && 0 < len(q.commands) { - if last := q.commands[len(q.commands)-1]; last.CanMerge(dst, src, color, mode, filter) { + if last := q.commands[len(q.commands)-1]; last.CanMerge(dst, src, color, mode, filter, address) { last.AddNumVertices(nvertices) last.AddNumIndices(nindices) return @@ -106,12 +106,13 @@ func (q *commandQueue) doEnqueueDrawImageCommand(dst, src *Image, nvertices, nin color: color, mode: mode, filter: filter, + address: address, } q.commands = append(q.commands, c) } // EnqueueDrawImageCommand enqueues a drawing-image command. -func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, indices []uint16, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) { +func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, indices []uint16, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { if len(indices) > graphics.IndicesNum { panic("not reached") } @@ -128,7 +129,7 @@ func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float q.nextIndex += len(vertices) / graphics.VertexFloatNum q.tmpNumIndices += len(indices) - q.doEnqueueDrawImageCommand(dst, src, len(vertices), len(indices), color, mode, filter, split) + q.doEnqueueDrawImageCommand(dst, src, len(vertices), len(indices), color, mode, filter, address, split) } // Enqueue enqueues a drawing command other than a draw-image command. @@ -215,6 +216,7 @@ type drawImageCommand struct { color *affine.ColorM mode graphics.CompositeMode filter graphics.Filter + address graphics.Address } func (c *drawImageCommand) String() string { @@ -230,7 +232,7 @@ func (c *drawImageCommand) Exec(indexOffset int) error { c.dst.image.SetAsDestination() c.src.image.SetAsSource() - if err := Driver().Draw(c.nindices, indexOffset, c.mode, c.color, c.filter); err != nil { + if err := Driver().Draw(c.nindices, indexOffset, c.mode, c.color, c.filter, c.address); err != nil { return err } return nil @@ -254,7 +256,7 @@ func (c *drawImageCommand) AddNumIndices(n int) { // CanMerge returns a boolean value indicating whether the other drawImageCommand can be merged // with the drawImageCommand c. -func (c *drawImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *drawImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { if c.dst != dst { return false } @@ -270,6 +272,9 @@ func (c *drawImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode if c.filter != filter { return false } + if c.address != address { + return false + } return true } @@ -307,7 +312,7 @@ func (c *replacePixelsCommand) AddNumVertices(n int) { func (c *replacePixelsCommand) AddNumIndices(n int) { } -func (c *replacePixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *replacePixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { return false } @@ -344,7 +349,7 @@ func (c *pixelsCommand) AddNumVertices(n int) { func (c *pixelsCommand) AddNumIndices(n int) { } -func (c *pixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *pixelsCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { return false } @@ -377,7 +382,7 @@ func (c *disposeCommand) AddNumVertices(n int) { func (c *disposeCommand) AddNumIndices(n int) { } -func (c *disposeCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *disposeCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { return false } @@ -416,7 +421,7 @@ func (c *newImageCommand) AddNumVertices(n int) { func (c *newImageCommand) AddNumIndices(n int) { } -func (c *newImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *newImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { return false } @@ -452,7 +457,7 @@ func (c *newScreenFramebufferImageCommand) AddNumVertices(n int) { func (c *newScreenFramebufferImageCommand) AddNumIndices(n int) { } -func (c *newScreenFramebufferImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) bool { +func (c *newScreenFramebufferImageCommand) CanMerge(dst, src *Image, color *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) bool { return false } diff --git a/internal/graphicscommand/image.go b/internal/graphicscommand/image.go index 7e5eb323a..0818388ec 100644 --- a/internal/graphicscommand/image.go +++ b/internal/graphicscommand/image.go @@ -70,8 +70,8 @@ func (i *Image) Size() (int, int) { return i.width, i.height } -func (i *Image) DrawImage(src *Image, vertices []float32, indices []uint16, clr *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) { - theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, indices, clr, mode, filter) +func (i *Image) DrawImage(src *Image, vertices []float32, indices []uint16, clr *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { + theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, indices, clr, mode, filter, address) } // Pixels returns the image's pixels. diff --git a/internal/graphicscommand/image_test.go b/internal/graphicscommand/image_test.go index 1c345bbc4..2566687fb 100644 --- a/internal/graphicscommand/image_test.go +++ b/internal/graphicscommand/image_test.go @@ -49,7 +49,7 @@ func TestClear(t *testing.T) { vs := graphics.QuadVertices(w/2, h/2, 0, 0, w/2, h/2, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - dst.DrawImage(src, vs, is, nil, graphics.CompositeModeClear, graphics.FilterNearest) + dst.DrawImage(src, vs, is, nil, graphics.CompositeModeClear, graphics.FilterNearest, graphics.AddressClampToZero) pix := dst.Pixels() for j := 0; j < h/2; j++ { diff --git a/internal/graphicsdriver/graphicsdriver.go b/internal/graphicsdriver/graphicsdriver.go index a3f8dc210..987074239 100644 --- a/internal/graphicsdriver/graphicsdriver.go +++ b/internal/graphicsdriver/graphicsdriver.go @@ -26,7 +26,7 @@ type GraphicsDriver interface { NewImage(width, height int) (Image, error) NewScreenFramebufferImage(width, height int) (Image, error) Reset() error - Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error + Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter, address graphics.Address) error SetVsyncEnabled(enabled bool) VDirection() VDirection IsGL() bool diff --git a/internal/graphicsdriver/metal/driver.go b/internal/graphicsdriver/metal/driver.go index e6cad38db..6ae8f3a22 100644 --- a/internal/graphicsdriver/metal/driver.go +++ b/internal/graphicsdriver/metal/driver.go @@ -36,6 +36,9 @@ const source = `#include #define FILTER_LINEAR ({{.FilterLinear}}) #define FILTER_SCREEN ({{.FilterScreen}}) +#define ADDRESS_CLAMP_TO_ZERO ({{.AddressClampToZero}}) +#define ADDRESS_REPEAT ({{.AddressRepeat}}) + using namespace metal; struct VertexIn { @@ -89,12 +92,37 @@ float2 AdjustTexel(float2 source_size, float2 p0, float2 p1) { return p1; } +float Mod(float x, float y) { + if (x < 0.0) { + return y - (-x - y * floor(-x/y)); + } + return x - y * floor(x/y); +} + +float2 AdjustTexelByAddress(float2 p, float4 tex_region, uint8_t address) { + switch (address) { + case ADDRESS_CLAMP_TO_ZERO: { + return p; + } + case ADDRESS_REPEAT: { + float2 o = float2(tex_region[0], tex_region[1]); + float2 size = float2(tex_region[2] - tex_region[0], tex_region[3] - tex_region[1]); + return float2(Mod((p.x - o.x), size.x) + o.x, Mod((p.y - o.y), size.y) + o.y); + } + default: + // Not reached. + break; + } + return 0.0; +} + fragment float4 FragmentShader(VertexOut v [[stage_in]], texture2d texture [[texture(0)]], constant float4x4& color_matrix_body [[buffer(2)]], constant float4& color_matrix_translation [[buffer(3)]], constant uint8_t& filter [[buffer(4)]], - constant float& scale [[buffer(5)]]) { + constant uint8_t& address [[buffer(5)]], + constant float& scale [[buffer(6)]]) { constexpr sampler texture_sampler(filter::nearest); float2 source_size = 1; while (source_size.x < texture.get_width()) { @@ -109,11 +137,12 @@ fragment float4 FragmentShader(VertexOut v [[stage_in]], switch (filter) { case FILTER_NEAREST: { - c = texture.sample(texture_sampler, v.tex); - if (v.tex.x < v.tex_region[0] || - v.tex.y < v.tex_region[1] || - (v.tex_region[2] - texel_size.x / 512.0) <= v.tex.x || - (v.tex_region[3] - texel_size.y / 512.0) <= v.tex.y) { + float2 p = AdjustTexelByAddress(v.tex, v.tex_region, address); + c = texture.sample(texture_sampler, p); + if (p.x < v.tex_region[0] || + p.y < v.tex_region[1] || + (v.tex_region[2] - texel_size.x / 512.0) <= p.x || + (v.tex_region[3] - texel_size.y / 512.0) <= p.y) { c = 0; } break; @@ -123,6 +152,8 @@ fragment float4 FragmentShader(VertexOut v [[stage_in]], float2 p0 = v.tex - texel_size / 2.0; float2 p1 = v.tex + texel_size / 2.0; p1 = AdjustTexel(source_size, p0, p1); + p0 = AdjustTexelByAddress(p0, v.tex_region, address); + p1 = AdjustTexelByAddress(p1, v.tex_region, address); float4 c0 = texture.sample(texture_sampler, p0); float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y)); @@ -367,9 +398,11 @@ func (d *Driver) Reset() error { d.ml.SetDisplaySyncEnabled(true) replaces := map[string]string{ - "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), - "{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), - "{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), + "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), + "{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), + "{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), + "{{.AddressClampToZero}}": fmt.Sprintf("%d", graphics.AddressClampToZero), + "{{.AddressRepeat}}": fmt.Sprintf("%d", graphics.AddressRepeat), } src := source for k, v := range replaces { @@ -452,7 +485,8 @@ func (d *Driver) Reset() error { return nil } -func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error { +func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter, address graphics.Address) error { + // TODO: Use address if err := mainthread.Run(func() error { // NSView can be changed anytime (probably). Set this everyframe. cocoaWindow := ns.NewWindow(unsafe.Pointer(d.window)) @@ -509,8 +543,11 @@ func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode f := uint8(filter) rce.SetFragmentBytes(unsafe.Pointer(&f), 1, 4) + a := uint8(address) + rce.SetFragmentBytes(unsafe.Pointer(&a), 1, 5) + scale := float32(d.dst.width) / float32(d.src.width) - rce.SetFragmentBytes(unsafe.Pointer(&scale), unsafe.Sizeof(scale), 5) + rce.SetFragmentBytes(unsafe.Pointer(&scale), unsafe.Sizeof(scale), 6) if d.src != nil { rce.SetFragmentTexture(d.src.texture, 0) diff --git a/internal/graphicsdriver/opengl/driver.go b/internal/graphicsdriver/opengl/driver.go index 494b0433a..a9eb2f4ac 100644 --- a/internal/graphicsdriver/opengl/driver.go +++ b/internal/graphicsdriver/opengl/driver.go @@ -94,8 +94,8 @@ func (d *Driver) SetVertices(vertices []float32, indices []uint16) { d.context.elementArrayBufferSubData(indices) } -func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error { - if err := d.useProgram(mode, colorM, filter); err != nil { +func (d *Driver) Draw(indexLen int, indexOffset int, mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter, address graphics.Address) error { + if err := d.useProgram(mode, colorM, filter, address); err != nil { return err } d.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes diff --git a/internal/graphicsdriver/opengl/program.go b/internal/graphicsdriver/opengl/program.go index 0fdb41b74..2dce346a8 100644 --- a/internal/graphicsdriver/opengl/program.go +++ b/internal/graphicsdriver/opengl/program.go @@ -130,6 +130,7 @@ type openGLState struct { lastSourceWidth int lastSourceHeight int lastFilter *graphics.Filter + lastAddress *graphics.Address source *Image destination *Image @@ -159,6 +160,7 @@ func (s *openGLState) reset(context *context) error { s.lastSourceWidth = 0 s.lastSourceHeight = 0 s.lastFilter = nil + s.lastAddress = nil // When context lost happens, deleting programs or buffers is not necessary. // However, it is not assumed that reset is called only when context lost happens. @@ -222,7 +224,7 @@ func areSameFloat32Array(a, b []float32) bool { } // useProgram uses the program (programTexture). -func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter) error { +func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM, filter graphics.Filter, address graphics.Address) error { destination := d.state.destination if destination == nil { panic("destination image is not set") @@ -297,6 +299,10 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM, d.context.uniformInt(program, "filter", int(filter)) d.state.lastFilter = &filter } + if d.state.lastAddress == nil || *d.state.lastAddress != address { + d.context.uniformInt(program, "address", int(address)) + d.state.lastAddress = &address + } if filter == graphics.FilterScreen { scale := float32(dstW) / float32(srcW) diff --git a/internal/graphicsdriver/opengl/shader.go b/internal/graphicsdriver/opengl/shader.go index bde2c7daa..10277979c 100644 --- a/internal/graphicsdriver/opengl/shader.go +++ b/internal/graphicsdriver/opengl/shader.go @@ -34,9 +34,11 @@ func shaderStr(id shaderID) string { return shaderStrVertex case shaderFragmentColorMatrix: replaces := map[string]string{ - "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), - "{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), - "{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), + "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), + "{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), + "{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), + "{{.AddressClampToZero}}": fmt.Sprintf("%d", graphics.AddressClampToZero), + "{{.AddressRepeat}}": fmt.Sprintf("%d", graphics.AddressRepeat), } src := shaderStrFragment for k, v := range replaces { @@ -85,6 +87,8 @@ precision mediump float; #define FILTER_NEAREST ({{.FilterNearest}}) #define FILTER_LINEAR ({{.FilterLinear}}) #define FILTER_SCREEN ({{.FilterScreen}}) +#define ADDRESS_CLAMP_TO_ZERO ({{.AddressClampToZero}}) +#define ADDRESS_REPEAT ({{.AddressRepeat}}) uniform sampler2D texture; uniform mat4 color_matrix_body; @@ -92,6 +96,7 @@ uniform vec4 color_matrix_translation; uniform int filter; uniform highp vec2 source_size; +uniform int address; #if defined(FILTER_SCREEN) uniform highp float scale; @@ -115,6 +120,26 @@ highp vec2 adjustTexel(highp vec2 p0, highp vec2 p1) { return p1; } +highp float mod(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 tex_region, int address) { + if (address == ADDRESS_CLAMP_TO_ZERO) { + return p; + } + if (address == ADDRESS_REPEAT) { + highp vec2 o = vec2(tex_region[0], tex_region[1]); + highp vec2 size = vec2(tex_region[2] - tex_region[0], tex_region[3] - tex_region[1]); + return vec2(mod((p.x - o.x), size.x) + o.x, mod((p.y - o.y), size.y) + o.y); + } + // Not reached. + return vec2(0.0); +} + void main(void) { highp vec2 pos = varying_tex; highp vec2 texel_size = 1.0 / source_size; @@ -122,6 +147,7 @@ void main(void) { vec4 color; if (filter == FILTER_NEAREST) { + pos = adjustTexelByAddress(pos, varying_tex_region, address); color = texture2D(texture, pos); if (pos.x < varying_tex_region[0] || pos.y < varying_tex_region[1] || @@ -134,6 +160,8 @@ void main(void) { highp vec2 p1 = pos + texel_size / 2.0; p1 = adjustTexel(p0, p1); + p0 = adjustTexelByAddress(p0, varying_tex_region, address); + p1 = adjustTexelByAddress(p1, varying_tex_region, address); vec4 c0 = texture2D(texture, p0); vec4 c1 = texture2D(texture, vec2(p1.x, p0.y)); diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 05bf9a49a..f3cf551ef 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -32,6 +32,7 @@ type drawImageHistoryItem struct { colorm *affine.ColorM mode graphics.CompositeMode filter graphics.Filter + address graphics.Address } // Image represents an image that can be restored when GL context is lost. @@ -170,7 +171,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { float32(x), float32(y), 1, 1, 1, 1) is := graphics.QuadIndices() - i.image.DrawImage(dummyImage.image, vs, is, nil, graphics.CompositeModeClear, graphics.FilterNearest) + i.image.DrawImage(dummyImage.image, vs, is, nil, graphics.CompositeModeClear, graphics.FilterNearest, graphics.AddressClampToZero) } if x == 0 && y == 0 && width == w && height == h { @@ -213,7 +214,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { } // DrawImage draws a given image img to the image. -func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) { +func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { if len(vertices) == 0 { return } @@ -222,13 +223,13 @@ func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colo if img.stale || img.volatile || i.screen || !IsRestoringEnabled() { i.makeStale() } else { - i.appendDrawImageHistory(img, vertices, indices, colorm, mode, filter) + i.appendDrawImageHistory(img, vertices, indices, colorm, mode, filter, address) } - i.image.DrawImage(img.image, vertices, indices, colorm, mode, filter) + i.image.DrawImage(img.image, vertices, indices, colorm, mode, filter, address) } // appendDrawImageHistory appends a draw-image history item to the image. -func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) { +func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { if i.stale || i.volatile || i.screen { return } @@ -246,6 +247,7 @@ func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, indices colorm: colorm, mode: mode, filter: filter, + address: address, } i.drawImageHistory = append(i.drawImageHistory, item) } @@ -378,7 +380,7 @@ func (i *Image) restore() error { if c.image.hasDependency() { panic("not reached") } - gimg.DrawImage(c.image.image, c.vertices, c.indices, c.colorm, c.mode, c.filter) + gimg.DrawImage(c.image.image, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address) } i.image = gimg diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 86b89d4be..67664d086 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -136,7 +136,7 @@ func TestRestoreChain(t *testing.T) { w, h := imgs[i].Size() vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - imgs[i+1].DrawImage(imgs[i], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + imgs[i+1].DrawImage(imgs[i], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) } ResolveStaleImages() if err := Restore(); err != nil { @@ -178,10 +178,10 @@ func TestRestoreChain2(t *testing.T) { vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - imgs[8].DrawImage(imgs[7], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) - imgs[9].DrawImage(imgs[8], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + 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) for i := 0; i < 7; i++ { - imgs[i+1].DrawImage(imgs[i], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + imgs[i+1].DrawImage(imgs[i], vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) } ResolveStaleImages() @@ -224,10 +224,10 @@ func TestRestoreOverrideSource(t *testing.T) { fill(img1, 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) is := graphics.QuadIndices() - img2.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) - img3.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + img2.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) + img3.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) fill(img0, clr1.R, clr1.G, clr1.B, clr1.A) - img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) ResolveStaleImages() if err := Restore(); err != nil { t.Fatal(err) @@ -309,23 +309,23 @@ func TestRestoreComplexGraph(t *testing.T) { }() vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img3.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img3.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img4.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img4.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img5.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img6.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img6.DrawImage(img4, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img7.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + 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) - img7.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + img7.DrawImage(img3, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) ResolveStaleImages() if err := Restore(); err != nil { t.Fatal(err) @@ -417,8 +417,8 @@ func TestRestoreRecursive(t *testing.T) { }() vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) - img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest) + img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) + img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero) ResolveStaleImages() if err := Restore(); err != nil { t.Fatal(err) @@ -505,7 +505,7 @@ func TestDrawImageAndReplacePixels(t *testing.T) { vs := graphics.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff}, 1, 0, 1, 1) ResolveStaleImages() @@ -537,8 +537,8 @@ func TestDispose(t *testing.T) { vs := graphics.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img1.DrawImage(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) - img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img1.DrawImage(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) + img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) img1.Dispose() ResolveStaleImages() @@ -565,7 +565,7 @@ func TestDoubleResolve(t *testing.T) { vs := graphics.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) img0.ReplacePixels([]uint8{0x00, 0xff, 0x00, 0xff}, 1, 1, 1, 1) // Now img0 is stale. ResolveStaleImages() diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index 2db911881..ac4493f19 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -135,7 +135,7 @@ func (i *Image) ensureNotShared() { vw, vh := i.backend.restorable.Size() vs := graphics.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - newImg.DrawImage(i.backend.restorable, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + newImg.DrawImage(i.backend.restorable, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) i.dispose(false) i.backend = &backend{ @@ -218,7 +218,7 @@ func (i *Image) PutVertex(dest []float32, dx, dy, sx, sy float32, bx0, by0, bx1, const MaxCountForShare = 10 -func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter) { +func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { backendsM.Lock() defer backendsM.Unlock() @@ -240,7 +240,7 @@ func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colo panic("shareable: Image.DrawImage: img must be different from the receiver") } - i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, colorm, mode, filter) + i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, colorm, mode, filter, address) i.countForShare = 0 diff --git a/internal/shareable/shareable_test.go b/internal/shareable/shareable_test.go index 6a11fb845..072f5797c 100644 --- a/internal/shareable/shareable_test.go +++ b/internal/shareable/shareable_test.go @@ -86,7 +86,7 @@ func TestEnsureNotShared(t *testing.T) { // img4.ensureNotShared() should be called. vs := img3.QuadVertices(0, 0, size/2, size/2, 1, 0, 0, 1, size/4, size/4, 1, 1, 1, 1) is := graphics.QuadIndices() - img4.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img4.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) want := false if got := img4.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -108,7 +108,7 @@ func TestEnsureNotShared(t *testing.T) { // Check further drawing doesn't cause panic. // This bug was fixed by 03dcd948. - img4.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img4.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) } func Disabled_TestReshared(t *testing.T) { @@ -150,7 +150,7 @@ func Disabled_TestReshared(t *testing.T) { // Use img1 as a render target. vs := img2.QuadVertices(0, 0, size, size, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - img1.DrawImage(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img1.DrawImage(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) want = false if got := img1.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -158,7 +158,7 @@ func Disabled_TestReshared(t *testing.T) { // Use img1 as a render source. for i := 0; i < MaxCountForShare-1; i++ { - img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) want := false if got := img1.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -175,7 +175,7 @@ func Disabled_TestReshared(t *testing.T) { } } - img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img0.DrawImage(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) want = true if got := img1.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -193,7 +193,7 @@ func Disabled_TestReshared(t *testing.T) { // Use img3 as a render source. img3 never uses a shared texture. for i := 0; i < MaxCountForShare*2; i++ { - img0.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + img0.DrawImage(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) want := false if got := img3.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -269,7 +269,7 @@ func TestReplacePixelsAfterDrawImage(t *testing.T) { vs := src.QuadVertices(0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() - dst.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest) + dst.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) dst.ReplacePixels(pix) for j := 0; j < h; j++ {