From 69f87d5fd144d641ac6781067a09e994c7d0dab2 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 11 Aug 2020 01:11:19 +0900 Subject: [PATCH] ebiten: Add new shader builtin functions: image[N]TextureBoundAt Fixes #1287 --- examples/shader/chromaticaberration.go | 4 +- examples/shader/chromaticaberration_go.go | 2 +- examples/shader/radialblur.go | 4 +- examples/shader/radialblur_go.go | 2 +- image.go | 54 +++++++++++---- internal/driver/graphics.go | 2 +- internal/graphics/vertex.go | 8 ++- internal/graphicscommand/command.go | 12 ++-- internal/graphicsdriver/metal/graphics.go | 33 +++++++-- internal/graphicsdriver/opengl/graphics.go | 80 ++++++++++++++++------ internal/shareable/image.go | 6 +- internal/testing/shader.go | 10 ++- shader.go | 16 ++++- 13 files changed, 170 insertions(+), 63 deletions(-) diff --git a/examples/shader/chromaticaberration.go b/examples/shader/chromaticaberration.go index 48e3e1f5c..fd7e19e35 100644 --- a/examples/shader/chromaticaberration.go +++ b/examples/shader/chromaticaberration.go @@ -24,8 +24,8 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { center := ScreenSize / 2 amount := (center - Cursor) / image2TextureSize() / 10 var clr vec3 - clr.r = image2TextureAt(vec2(texCoord.x+amount.x, texCoord.y)).r + clr.r = image2TextureBoundsAt(texCoord + amount).r clr.g = image2TextureAt(texCoord).g - clr.b = image2TextureAt(vec2(texCoord.x-amount.x, texCoord.y)).b + clr.b = image2TextureBoundsAt(texCoord - amount).b return vec4(clr, 1.0) } diff --git a/examples/shader/chromaticaberration_go.go b/examples/shader/chromaticaberration_go.go index 926c8da23..f15d72e2c 100644 --- a/examples/shader/chromaticaberration_go.go +++ b/examples/shader/chromaticaberration_go.go @@ -3,4 +3,4 @@ package main -var chromaticaberration_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\nvar ScreenSize vec2\n\nfunc Fragment(position vec4, texCoord vec2, color vec4) vec4 {\n\tcenter := ScreenSize / 2\n\tamount := (center - Cursor) / image2TextureSize() / 10\n\tvar clr vec3\n\tclr.r = image2TextureAt(vec2(texCoord.x+amount.x, texCoord.y)).r\n\tclr.g = image2TextureAt(texCoord).g\n\tclr.b = image2TextureAt(vec2(texCoord.x-amount.x, texCoord.y)).b\n\treturn vec4(clr, 1.0)\n}\n") +var chromaticaberration_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\nvar ScreenSize vec2\n\nfunc Fragment(position vec4, texCoord vec2, color vec4) vec4 {\n\tcenter := ScreenSize / 2\n\tamount := (center - Cursor) / image2TextureSize() / 10\n\tvar clr vec3\n\tclr.r = image2TextureBoundsAt(texCoord + amount).r\n\tclr.g = image2TextureAt(texCoord).g\n\tclr.b = image2TextureBoundsAt(texCoord - amount).b\n\treturn vec4(clr, 1.0)\n}\n") diff --git a/examples/shader/radialblur.go b/examples/shader/radialblur.go index 0b683bcfb..4bfffc85f 100644 --- a/examples/shader/radialblur.go +++ b/examples/shader/radialblur.go @@ -30,8 +30,8 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { // TODO: Add len(samples) sum := clr for i := 0; i < 10; i++ { - // TODO: Consider the source region not to violate the region. - sum += image2TextureAt(texCoord + dir*samples[i]/image2TextureSize()) + pos := texCoord + dir*samples[i]/image2TextureSize() + sum += image2TextureBoundsAt(pos) } sum /= 10 + 1 diff --git a/examples/shader/radialblur_go.go b/examples/shader/radialblur_go.go index aae2afbef..59d49597c 100644 --- a/examples/shader/radialblur_go.go +++ b/examples/shader/radialblur_go.go @@ -3,4 +3,4 @@ package main -var radialblur_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\nvar ScreenSize vec2\n\nfunc Fragment(position vec4, texCoord vec2, color vec4) vec4 {\n\tdir := normalize(position.xy - Cursor)\n\tclr := image2TextureAt(texCoord)\n\n\tsamples := [10]float{\n\t\t-22, -14, -8, -4, -2, 2, 4, 8, 14, 22,\n\t}\n\t// TODO: Add len(samples)\n\tsum := clr\n\tfor i := 0; i < 10; i++ {\n\t\t// TODO: Consider the source region not to violate the region.\n\t\tsum += image2TextureAt(texCoord + dir*samples[i]/image2TextureSize())\n\t}\n\tsum /= 10 + 1\n\n\tdist := distance(position.xy, Cursor)\n\tt := clamp(dist/256, 0, 1)\n\treturn mix(clr, sum, t)\n}\n") +var radialblur_go = []byte("// Copyright 2020 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +build ignore\n\npackage main\n\nvar Time float\nvar Cursor vec2\nvar ScreenSize vec2\n\nfunc Fragment(position vec4, texCoord vec2, color vec4) vec4 {\n\tdir := normalize(position.xy - Cursor)\n\tclr := image2TextureAt(texCoord)\n\n\tsamples := [10]float{\n\t\t-22, -14, -8, -4, -2, 2, 4, 8, 14, 22,\n\t}\n\t// TODO: Add len(samples)\n\tsum := clr\n\tfor i := 0; i < 10; i++ {\n\t\tpos := texCoord + dir*samples[i]/image2TextureSize()\n\t\tsum += image2TextureBoundsAt(pos)\n\t}\n\tsum /= 10 + 1\n\n\tdist := distance(position.xy, Cursor)\n\tt := clamp(dist/256, 0, 1)\n\treturn mix(clr, sum, t)\n}\n") diff --git a/image.go b/image.go index 1bbc95ed6..ff2bb32c1 100644 --- a/image.go +++ b/image.go @@ -270,13 +270,21 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { vs := graphics.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, 1, 1, 1, 1, filter == driver.FilterScreen) is := graphics.QuadIndices() + var sr driver.Region + sr = driver.Region{ + X: float32(bounds.Min.X), + Y: float32(bounds.Min.Y), + Width: float32(bounds.Dx()), + Height: float32(bounds.Dy()), + } + srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap} if options.Shader == nil { - i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, driver.Region{}, nil, nil, canSkipMipmap(options.GeoM, filter)) + i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, sr, nil, nil, canSkipMipmap(options.GeoM, filter)) return nil } - i.mipmap.DrawTriangles(srcs, vs, is, nil, mode, filter, driver.AddressUnsafe, driver.Region{}, options.Shader.shader, options.Uniforms, canSkipMipmap(options.GeoM, filter)) + i.mipmap.DrawTriangles(srcs, vs, is, nil, mode, filter, driver.AddressUnsafe, sr, options.Shader.shader, options.Uniforms, canSkipMipmap(options.GeoM, filter)) return nil } @@ -421,14 +429,12 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o copy(is, indices) var sr driver.Region - if options.Shader == nil && options.Address != AddressUnsafe { - b := img.Bounds() - sr = driver.Region{ - X: float32(b.Min.X), - Y: float32(b.Min.Y), - Width: float32(b.Dx()), - Height: float32(b.Dy()), - } + b := img.Bounds() + sr = driver.Region{ + X: float32(b.Min.X), + Y: float32(b.Min.Y), + Width: float32(b.Dx()), + Height: float32(b.Dy()), } var srcs [graphics.ShaderImageNum]*mipmap.Mipmap @@ -440,7 +446,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address), sr, nil, nil, false) return } - i.mipmap.DrawTriangles(srcs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, options.Shader.shader, options.Uniforms, false) + i.mipmap.DrawTriangles(srcs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, sr, options.Shader.shader, options.Uniforms, false) } // DrawRectShaderOptions represents options for DrawRectShader @@ -517,7 +523,18 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR vs := graphics.QuadVertices(0, 0, float32(width), float32(height), a, b, c, d, tx, ty, 1, 1, 1, 1, false) is := graphics.QuadIndices() - i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, shader.shader, options.Uniforms, canSkipMipmap(options.GeoM, driver.FilterNearest)) + var sr driver.Region + if img := options.Images[0]; img != nil { + b := img.Bounds() + sr = driver.Region{ + X: float32(b.Min.X), + Y: float32(b.Min.Y), + Width: float32(b.Dx()), + Height: float32(b.Dy()), + } + } + + i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, sr, shader.shader, options.Uniforms, canSkipMipmap(options.GeoM, driver.FilterNearest)) } // DrawTrianglesShaderOptions represents options for DrawTrianglesShader @@ -613,7 +630,18 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader is := make([]uint16, len(indices)) copy(is, indices) - i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, shader.shader, options.Uniforms, false) + var sr driver.Region + if img := options.Images[0]; img != nil { + b := img.Bounds() + sr = driver.Region{ + X: float32(b.Min.X), + Y: float32(b.Min.Y), + Width: float32(b.Dx()), + Height: float32(b.Dy()), + } + } + + i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, sr, shader.shader, options.Uniforms, false) } // SubImage returns an image representing the portion of the image p visible through r. diff --git a/internal/driver/graphics.go b/internal/driver/graphics.go index a0de70dd9..1d35c378a 100644 --- a/internal/driver/graphics.go +++ b/internal/driver/graphics.go @@ -60,7 +60,7 @@ type Graphics interface { // // * float32 // * []float32 - DrawShader(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, uniforms []interface{}) error + DrawShader(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, sourceRegion Region, mode CompositeMode, uniforms []interface{}) error } // GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost. diff --git a/internal/graphics/vertex.go b/internal/graphics/vertex.go index b2d15376b..6cff70737 100644 --- a/internal/graphics/vertex.go +++ b/internal/graphics/vertex.go @@ -25,11 +25,15 @@ const ( // Any shaders in Ebiten must have these uniform variables. PreservedUniformVariablesNum = 1 + // the destination texture size 1 + // the texture sizes array - 1 // the offsets array of the second and the following images + 1 + // the offsets array of the second and the following images + 1 + // the texture source origin + 1 // the texture source sizes DestinationTextureSizeUniformVariableIndex = 0 TextureSizesUniformVariableIndex = 1 - TextureOffsetsUniformVariableIndex = 2 + TextureSourceOffsetsUniformVariableIndex = 2 + TextureSourceOriginUniformVariableIndex = 3 + TextureSourceSizesUniformVariableIndex = 4 ) const ( diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index adbfdcdfb..f39c3c168 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -163,12 +163,10 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh if srcs[0] != nil { w, h := srcs[0].InternalSize() - if address != driver.AddressUnsafe { - sourceRegion.X /= float32(w) - sourceRegion.Y /= float32(h) - sourceRegion.Width /= float32(w) - sourceRegion.Height /= float32(h) - } + sourceRegion.X /= float32(w) + sourceRegion.Y /= float32(h) + sourceRegion.Width /= float32(w) + sourceRegion.Height /= float32(h) // TODO: This doesn't work when the src image sizes are different. for i := range offsets { offsets[i][0] /= float32(w) @@ -416,7 +414,7 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error { imgs[i] = src.image.ID() } - return theGraphicsDriver.DrawShader(c.dst.image.ID(), imgs, c.offsets, c.shader.shader.ID(), c.nindices, indexOffset, c.mode, c.uniforms) + return theGraphicsDriver.DrawShader(c.dst.image.ID(), imgs, c.offsets, c.shader.shader.ID(), c.nindices, indexOffset, c.sourceRegion, c.mode, c.uniforms) } return theGraphicsDriver.Draw(c.dst.image.ID(), c.srcs[0].image.ID(), c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.sourceRegion) } diff --git a/internal/graphicsdriver/metal/graphics.go b/internal/graphicsdriver/metal/graphics.go index 110f356b9..6ee09888b 100644 --- a/internal/graphicsdriver/metal/graphics.go +++ b/internal/graphicsdriver/metal/graphics.go @@ -991,7 +991,7 @@ func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) { }) } -func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, uniforms []interface{}) error { +func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, sourceRegion driver.Region, mode driver.CompositeMode, uniforms []interface{}) error { dst := g.images[dstID] var srcs [graphics.ShaderImageNum]*Image for i, srcID := range srcIDs { @@ -1008,7 +1008,7 @@ func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImage // Set the destination texture size. dw, dh := dst.internalSize() - us[0] = []float32{float32(dw), float32(dh)} + us[graphics.DestinationTextureSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)} // Set the source texture sizes. usizes := make([]float32, 2*len(srcs)) @@ -1019,7 +1019,7 @@ func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImage usizes[2*i+1] = float32(h) } } - us[1] = usizes + us[graphics.TextureSizesUniformVariableIndex] = usizes // Set the source offsets. uoffsets := make([]float32, 2*len(offsets)) @@ -1027,7 +1027,32 @@ func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImage uoffsets[2*i] = offset[0] uoffsets[2*i+1] = offset[1] } - us[2] = uoffsets + us[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets + + // Set the source origin of the first image. + uorigin := []float32{float32(sourceRegion.X), float32(sourceRegion.Y)} + us[graphics.TextureSourceOriginUniformVariableIndex] = uorigin + + // Set the source sizes. + ussizes := make([]float32, 2*len(srcs)) + if srcs[0] != nil { + w, h := sourceRegion.Width, sourceRegion.Height + bw, bh := srcs[0].internalSize() + for i, src := range srcs { + if i == 0 { + ussizes[2*i] = float32(w) + ussizes[2*i+1] = float32(h) + continue + } + if src == nil { + continue + } + tw, th := src.internalSize() + ussizes[2*i] = float32(w) * float32(bw) / float32(tw) + ussizes[2*i+1] = float32(h) * float32(bh) / float32(th) + } + } + us[graphics.TextureSourceSizesUniformVariableIndex] = ussizes // Set the additional uniform variables. for i, v := range uniforms { diff --git a/internal/graphicsdriver/opengl/graphics.go b/internal/graphicsdriver/opengl/graphics.go index e791eaf1a..22be98f46 100644 --- a/internal/graphicsdriver/opengl/graphics.go +++ b/internal/graphicsdriver/opengl/graphics.go @@ -289,7 +289,7 @@ func (g *Graphics) removeShader(shader *Shader) { delete(g.shaders, shader.id) } -func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, uniforms []interface{}) error { +func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, sourceRegion driver.Region, mode driver.CompositeMode, uniforms []interface{}) error { d := g.images[dst] s := g.shaders[shader] @@ -301,38 +301,74 @@ func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum] g.context.blendFunc(mode) us := make([]uniformVariable, graphics.PreservedUniformVariablesNum+len(uniforms)) - vw, vh := d.framebufferSize() - us[0].name = "U0" - us[0].value = []float32{float32(vw), float32(vh)} - us[0].typ = s.ir.Uniforms[0] - - vsizes := make([]float32, 2*len(srcs)) - for i, src := range srcs { - if img := g.images[src]; img != nil { - w, h := img.framebufferSize() - vsizes[2*i] = float32(w) - vsizes[2*i+1] = float32(h) - } + { + const idx = graphics.DestinationTextureSizeUniformVariableIndex + w, h := d.framebufferSize() + us[idx].name = "U0" + us[idx].value = []float32{float32(w), float32(h)} + us[idx].typ = s.ir.Uniforms[0] } { - const idx = 1 + sizes := make([]float32, 2*len(srcs)) + for i, src := range srcs { + if img := g.images[src]; img != nil { + w, h := img.framebufferSize() + sizes[2*i] = float32(w) + sizes[2*i+1] = float32(h) + } + + } + const idx = graphics.TextureSizesUniformVariableIndex us[idx].name = fmt.Sprintf("U%d", idx) - us[idx].value = vsizes + us[idx].value = sizes us[idx].typ = s.ir.Uniforms[idx] } - - voffsets := make([]float32, 2*len(offsets)) - for i, o := range offsets { - voffsets[2*i] = o[0] - voffsets[2*i+1] = o[1] - } { - const idx = 1 + 1 + voffsets := make([]float32, 2*len(offsets)) + for i, o := range offsets { + voffsets[2*i] = o[0] + voffsets[2*i+1] = o[1] + } + const idx = graphics.TextureSourceOffsetsUniformVariableIndex us[idx].name = fmt.Sprintf("U%d", idx) us[idx].value = voffsets us[idx].typ = s.ir.Uniforms[idx] } + { + origin := make([]float32, 2) + origin[0] = sourceRegion.X + origin[1] = sourceRegion.Y + const idx = graphics.TextureSourceOriginUniformVariableIndex + us[idx].name = fmt.Sprintf("U%d", idx) + us[idx].value = origin + us[idx].typ = s.ir.Uniforms[idx] + } + { + sizes := make([]float32, 2*len(srcs)) + if baseimg := g.images[srcs[0]]; baseimg != nil { + w, h := sourceRegion.Width, sourceRegion.Height + bw, bh := baseimg.framebufferSize() + for i, src := range srcs { + if i == 0 { + sizes[2*i] = float32(w) + sizes[2*i+1] = float32(h) + continue + } + img := g.images[src] + if img == nil { + continue + } + tw, th := img.framebufferSize() + sizes[2*i] = float32(w) * float32(bw) / float32(tw) + sizes[2*i+1] = float32(h) * float32(bh) / float32(th) + } + } + const idx = graphics.TextureSourceSizesUniformVariableIndex + us[idx].name = fmt.Sprintf("U%d", idx) + us[idx].value = sizes + us[idx].typ = s.ir.Uniforms[idx] + } for i, v := range uniforms { const offset = graphics.PreservedUniformVariablesNum diff --git a/internal/shareable/image.go b/internal/shareable/image.go index a0dced479..4cb3fa6f6 100644 --- a/internal/shareable/image.go +++ b/internal/shareable/image.go @@ -337,10 +337,8 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f vertices[i*graphics.VertexFloatNum+2] += oxf vertices[i*graphics.VertexFloatNum+3] += oyf } - if address != driver.AddressUnsafe { - sourceRegion.X += oxf - sourceRegion.Y += oyf - } + sourceRegion.X += oxf + sourceRegion.Y += oyf } var offsets [graphics.ShaderImageNum - 1][2]float32 diff --git a/internal/testing/shader.go b/internal/testing/shader.go index 27574c707..077825fc5 100644 --- a/internal/testing/shader.go +++ b/internal/testing/shader.go @@ -239,6 +239,14 @@ func defaultProgram() shaderir.Program { Length: graphics.ShaderImageNum - 1, Sub: []shaderir.Type{{Main: shaderir.Vec2}}, } + // Source region origin + p.Uniforms[3] = shaderir.Type{Main: shaderir.Vec2} + // Source region sizes + p.Uniforms[4] = shaderir.Type{ + Main: shaderir.Array, + Length: graphics.ShaderImageNum, + Sub: []shaderir.Type{{Main: shaderir.Vec2}}, + } return p } @@ -364,7 +372,7 @@ func ShaderProgramImages(imageNum int) shaderir.Program { Exprs: []shaderir.Expr{ { Type: shaderir.UniformVariable, - Index: graphics.TextureOffsetsUniformVariableIndex, + Index: graphics.TextureSourceOffsetsUniformVariableIndex, }, { Type: shaderir.NumberExpr, diff --git a/shader.go b/shader.go index 54b6f74f5..ee7dd6cc3 100644 --- a/shader.go +++ b/shader.go @@ -49,19 +49,29 @@ func image%[1]dTextureSize() vec2 { } shaderSuffix += fmt.Sprintf(` -var __textureOffsets [%d]vec2 -`, graphics.ShaderImageNum-1) +var __textureSourceOffsets [%[1]d]vec2 +var __textureSourceOrigin vec2 +var __textureSourceSizes [%[2]d]vec2 +`, graphics.ShaderImageNum-1, graphics.ShaderImageNum) for i := 0; i < graphics.ShaderImageNum; i++ { var offset string if i >= 1 { - offset = fmt.Sprintf(" + __textureOffsets[%d]", i-1) + offset = fmt.Sprintf(" + __textureSourceOffsets[%d]", i-1) } // __t%d is a special variable for a texture variable. shaderSuffix += fmt.Sprintf(` func image%[1]dTextureAt(pos vec2) vec4 { return texture2D(__t%[1]d, pos%[2]s) } + +func image%[1]dTextureBoundsAt(pos vec2) vec4 { + return texture2D(__t%[1]d, pos%[2]s) * + step(__textureSourceOrigin.x, pos.x) * + (1 - step(__textureSourceOrigin.x + __textureSourceSizes[%[1]d].x, pos.x)) * + step(__textureSourceOrigin.y, pos.y) * + (1 - step(__textureSourceOrigin.y + __textureSourceSizes[%[1]d].y, pos.y)) +} `, i, offset) }