ebiten: Add new shader builtin functions: image[N]TextureBoundAt

Fixes #1287
This commit is contained in:
Hajime Hoshi 2020-08-11 01:11:19 +09:00
parent 523dc6f2a0
commit 69f87d5fd1
13 changed files with 170 additions and 63 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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