graphics: Add Address representing a sampler address mode

Fixes #761
This commit is contained in:
Hajime Hoshi 2018-12-24 03:00:00 +09:00
parent 7e50ae39c9
commit f1582c2d73
16 changed files with 380 additions and 75 deletions

124
examples/address/main.go Normal file
View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}
}
}
}

View File

@ -22,3 +22,10 @@ const (
FilterLinear
FilterScreen
)
type Address int
const (
AddressClampToZero Address = iota
AddressRepeat
)

View File

@ -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
}

View File

@ -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.

View File

@ -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++ {

View File

@ -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

View File

@ -36,6 +36,9 @@ const source = `#include <metal_stdlib>
#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<float> 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));
@ -370,6 +401,8 @@ func (d *Driver) Reset() error {
"{{.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)

View File

@ -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

View File

@ -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)

View File

@ -37,6 +37,8 @@ func shaderStr(id shaderID) string {
"{{.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));

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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++ {