shader: Enable to get pixels from multiple images

Updates #1193
This commit is contained in:
Hajime Hoshi 2020-07-18 19:56:22 +09:00
parent 5dd073fcbb
commit 36e9803cea
15 changed files with 199 additions and 80 deletions

View File

@ -60,7 +60,7 @@ type Graphics interface {
//
// * float32
// * []float32
DrawShader(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, 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, mode CompositeMode, uniforms []interface{}) error
}
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.

View File

@ -18,7 +18,18 @@ import (
"github.com/hajimehoshi/ebiten/internal/web"
)
const ShaderImageNum = 4
const (
ShaderImageNum = 4
// PreservedUniformVariablesNum represents the number of preserved uniform variables.
// Any shaders in Ebiten must have these uniform variables.
//
// The first one is for the viewport size.
// The second and the followings are for offsets of the second and the following images.
//
// All the preversed uniform variables are vec2 so far.
PreservedUniformVariablesNum = 1 + (ShaderImageNum - 1)
)
const (
IndicesNum = (1 << 16) / 3 * 3 // Adjust num for triangles.

View File

@ -144,7 +144,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
}
// EnqueueDrawTrianglesCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
if len(indices) > graphics.IndicesNum {
panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum))
}
@ -171,6 +171,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
// TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) {
// TODO: Pass offsets and uniforms when merging considers the shader.
if last := q.commands[len(q.commands)-1]; last.CanMergeWithDrawTrianglesCommand(dst, srcs, color, mode, filter, address, sourceRegion, shader) {
last.AddNumVertices(len(vertices))
last.AddNumIndices(len(indices))
@ -178,17 +179,24 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
}
}
if firstSrc != nil && address != driver.AddressUnsafe {
if firstSrc != nil {
w, h := firstSrc.InternalSize()
if address != driver.AddressUnsafe {
sourceRegion.X /= float32(w)
sourceRegion.Y /= float32(h)
sourceRegion.Width /= float32(w)
sourceRegion.Height /= float32(h)
}
for i := range offsets {
offsets[i][0] /= float32(w)
offsets[i][1] /= float32(h)
}
}
c := &drawTrianglesCommand{
dst: dst,
srcs: srcs,
offsets: offsets,
nvertices: len(vertices),
nindices: len(indices),
color: color,
@ -315,6 +323,7 @@ func FlushCommands() error {
type drawTrianglesCommand struct {
dst *Image
srcs [graphics.ShaderImageNum]*Image
offsets [graphics.ShaderImageNum - 1][2]float32
nvertices int
nindices int
color *affine.ColorM
@ -424,7 +433,7 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error {
imgs[i] = src.image.ID()
}
return theGraphicsDriver.DrawShader(c.dst.image.ID(), imgs, 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.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

@ -147,7 +147,7 @@ func (i *Image) InternalSize() (int, int) {
//
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
// elements for the source image are not used.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
if i.lastCommand == lastCommandNone {
if !i.screen && mode != driver.CompositeModeClear {
panic("graphicscommand: the image must be cleared first")
@ -165,7 +165,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
}
i.resolveBufferedReplacePixels()
theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, vertices, indices, clr, mode, filter, address, sourceRegion, shader, uniforms)
theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, offsets, vertices, indices, clr, mode, filter, address, sourceRegion, shader, uniforms)
if i.lastCommand == lastCommandNone && !i.screen {
i.lastCommand = lastCommandClear

View File

@ -44,7 +44,7 @@ func TestClear(t *testing.T) {
vs := quadVertices(w/2, h/2)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
pix, err := dst.Pixels()
if err != nil {
@ -74,8 +74,8 @@ func TestReplacePixelsPartAfterDrawTriangles(t *testing.T) {
dst := NewImage(w, h)
vs := quadVertices(w/2, h/2)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
}
@ -89,11 +89,11 @@ func TestShader(t *testing.T) {
dst := NewImage(w, h)
vs := quadVertices(w, h)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
pix, err := dst.Pixels()
if err != nil {

View File

@ -802,7 +802,7 @@ func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) {
panic("metal: NewShader is not implemented")
}
func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]driver.ImageID, 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, mode driver.CompositeMode, uniforms []interface{}) error {
panic("metal: DrawShader is not implemented")
}

View File

@ -287,7 +287,7 @@ func (g *Graphics) removeShader(shader *Shader) {
delete(g.shaders, shader.id)
}
func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]driver.ImageID, 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, mode driver.CompositeMode, uniforms []interface{}) error {
d := g.images[dst]
s := g.shaders[shader]
@ -298,14 +298,22 @@ func (g *Graphics) DrawShader(dst driver.ImageID, srcs [graphics.ShaderImageNum]
}
g.context.blendFunc(mode)
us := make([]uniformVariable, 1+len(uniforms))
us := make([]uniformVariable, graphics.PreservedUniformVariablesNum+len(uniforms))
vw := graphics.InternalImageSize(d.width)
vh := graphics.InternalImageSize(d.height)
us[0].name = "U0"
us[0].value = []float32{float32(vw), float32(vh)}
for k, v := range uniforms {
us[k+1].name = fmt.Sprintf("U%d", k+1)
us[k+1].value = v
for i, o := range offsets {
o := o
us[i+1].name = fmt.Sprintf("U%d", i+1)
us[i+1].value = o[:]
}
for i, v := range uniforms {
const offset = graphics.PreservedUniformVariablesNum
us[i+offset].name = fmt.Sprintf("U%d", i+offset)
us[i+offset].value = v
}
var ts [graphics.ShaderImageNum]textureVariable

View File

@ -262,6 +262,8 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
for _, u := range uniforms {
switch v := u.value.(type) {
case nil:
// Do nothing.
case float32:
cached, ok := g.state.lastUniforms[u.name].(float32)
if ok && cached == v {

View File

@ -70,6 +70,7 @@ func (p *Pixels) At(i, j int) (byte, byte, byte, byte) {
// drawTrianglesHistoryItem is an item for history of draw-image commands.
type drawTrianglesHistoryItem struct {
images [graphics.ShaderImageNum]*Image
offsets [graphics.ShaderImageNum - 1][2]float32
vertices []float32
indices []uint16
colorm *affine.ColorM
@ -259,7 +260,9 @@ func fillImage(i *graphicscommand.Image, clr color.RGBA) {
// Add 1 pixels for paddings.
vs := quadVertices(0, 0, float32(dw), float32(dh), 1, 1, float32(sw-1), float32(sh-1), rf, gf, bf, af)
is := graphics.QuadIndices()
i.DrawTriangles([graphics.ShaderImageNum]*graphicscommand.Image{emptyImage.image}, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
srcs := [graphics.ShaderImageNum]*graphicscommand.Image{emptyImage.image}
var offsets [graphics.ShaderImageNum - 1][2]float32
i.DrawTriangles(srcs, offsets, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
}
// BasePixelsForTesting returns the image's basePixels for testing.
@ -351,7 +354,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image")
}
@ -375,7 +378,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
if srcstale || i.screen || !needsRestoring() || i.volatile {
i.makeStale()
} else {
i.appendDrawTrianglesHistory(srcs, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms)
i.appendDrawTrianglesHistory(srcs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms)
}
var s *graphicscommand.Shader
if shader != nil {
@ -389,11 +392,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
}
imgs[i] = src.image
}
i.image.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
i.image.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
}
// appendDrawTrianglesHistory appends a draw-image history item to the image.
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
if i.stale || i.volatile || i.screen {
return
}
@ -413,6 +416,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image,
item := &drawTrianglesHistoryItem{
images: srcs,
offsets: offsets,
vertices: vs,
indices: is,
colorm: colorm,
@ -602,7 +606,7 @@ func (i *Image) restore() error {
}
imgs[i] = img.image
}
gimg.DrawTriangles(imgs, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.sourceRegion, s, c.uniforms)
gimg.DrawTriangles(imgs, c.offsets, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.sourceRegion, s, c.uniforms)
}
if len(i.drawTrianglesHistory) > 0 {

View File

@ -131,7 +131,7 @@ func TestRestoreChain(t *testing.T) {
for i := 0; i < num-1; i++ {
vs := quadVertices(1, 1, 0, 0)
is := graphics.QuadIndices()
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
}
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
@ -173,10 +173,10 @@ func TestRestoreChain2(t *testing.T) {
imgs[8].ReplacePixels([]byte{clr8.R, clr8.G, clr8.B, clr8.A}, 0, 0, w, h)
is := graphics.QuadIndices()
imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
for i := 0; i < 7; i++ {
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
}
if err := ResolveStaleImages(); err != nil {
@ -216,10 +216,10 @@ func TestRestoreOverrideSource(t *testing.T) {
clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff}
img1.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, w, h)
is := graphics.QuadIndices()
img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -298,23 +298,24 @@ func TestRestoreComplexGraph(t *testing.T) {
}()
vs := quadVertices(w, h, 0, 0)
is := graphics.QuadIndices()
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
var offsets [graphics.ShaderImageNum - 1][2]float32
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 1, 0)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 1, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 2, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 0, 0)
img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 0, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 1, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 0, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
vs = quadVertices(w, h, 2, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -406,8 +407,8 @@ func TestRestoreRecursive(t *testing.T) {
img0.Dispose()
}()
is := graphics.QuadIndices()
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
@ -501,7 +502,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) {
vs := quadVertices(1, 1, 0, 0)
is := graphics.QuadIndices()
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
if err := ResolveStaleImages(); err != nil {
@ -538,8 +539,8 @@ func TestDispose(t *testing.T) {
defer img2.Dispose()
is := graphics.QuadIndices()
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.Dispose()
if err := ResolveStaleImages(); err != nil {
@ -647,7 +648,7 @@ func TestReplacePixelsOnly(t *testing.T) {
vs := quadVertices(1, 1, 0, 0)
is := graphics.QuadIndices()
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1)
// BasePixelsForTesting is available without GPU accessing.
@ -700,7 +701,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
src.ReplacePixels(pix, 0, 0, w, h)
vs := quadVertices(1, 1, 0, 0)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
// stale.
@ -721,7 +722,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) {
vs := quadVertices(w, h, 0, 0)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h)
// ReplacePixels for a whole image doesn't panic.
}
@ -739,7 +740,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) {
vs := quadVertices(w, h, 0, 0)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
}
@ -828,7 +829,7 @@ func TestFill2(t *testing.T) {
dst := NewImage(w, h, false)
vs := quadVertices(w, h, 0, 0)
is := graphics.QuadIndices()
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
// Fill src with a different color. This should not affect dst.
src.Fill(color.RGBA{0, 0xff, 0, 0xff})
@ -867,7 +868,7 @@ func TestMutateSlices(t *testing.T) {
vs := quadVertices(w, h, 0, 0)
is := make([]uint16, len(graphics.QuadIndices()))
copy(is, graphics.QuadIndices())
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
for i := range vs {
vs[i] = 0
}

View File

@ -35,7 +35,7 @@ func TestShader(t *testing.T) {
ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
@ -69,7 +69,7 @@ func TestShaderChain(t *testing.T) {
ir := etesting.ShaderProgramImages(1)
s := NewShader(&ir)
for i := 0; i < num-1; i++ {
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
}
if err := ResolveStaleImages(); err != nil {
@ -105,7 +105,48 @@ func TestShaderMultipleSources(t *testing.T) {
ir := etesting.ShaderProgramImages(3)
s := NewShader(&ir)
dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
var offsets [graphics.ShaderImageNum - 1][2]float32
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
// Clear one of the sources after DrawTriangles. dst should not be affected.
srcs[0].Fill(color.RGBA{})
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
if err := RestoreIfNeeded(); err != nil {
t.Fatal(err)
}
want := color.RGBA{0x40, 0x80, 0xc0, 0xff}
got := pixelsToColor(dst.BasePixelsForTesting(), 0, 0)
if !sameColors(got, want, 1) {
t.Errorf("got %v, want %v", got, want)
}
}
func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
if !graphicscommand.IsShaderAvailable() {
t.Skip("shader is not available on this environment")
}
var srcs [graphics.ShaderImageNum]*Image
srcs[0] = NewImage(3, 1, false)
srcs[0].ReplacePixels([]byte{
0x40, 0, 0, 0xff,
0, 0x80, 0, 0xff,
0, 0, 0xc0, 0xff,
}, 0, 0, 3, 1)
dst := NewImage(1, 1, false)
ir := etesting.ShaderProgramImages(3)
s := NewShader(&ir)
offsets := [graphics.ShaderImageNum - 1][2]float32{
{1, 0},
{2, 0},
}
dst.DrawTriangles(srcs, offsets, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
// Clear one of the sources after DrawTriangles. dst should not be affected.
srcs[0].Fill(color.RGBA{})
@ -134,7 +175,7 @@ func TestShaderDispose(t *testing.T) {
ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
img.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, nil)
// Dispose the shader. This should invalidates all the images using this shader i.e., all the images become
// stale.

View File

@ -181,6 +181,12 @@ func (cs *compileState) parse(f *ast.File) {
utypes = append(utypes, cs.ir.Uniforms[i])
}
}
// TODO: Check len(unames) == graphics.PreservedUniformVariablesNum. Unfortunately this is not true on tests.
for _, t := range utypes {
if got, want := t.Main, shaderir.Vec2; got != want {
panic(fmt.Sprintf("shader: all the preserved uniform variables' types must be %v but %v", want, got))
}
}
for i, u := range cs.uniforms {
if !strings.HasPrefix(u, "__") {
unames = append(unames, u)

View File

@ -220,7 +220,9 @@ func (i *Image) ensureNotShared() {
dx1, dy1, sx1, sy1, 1, 1, 1, 1,
}
is := graphics.QuadIndices()
newImg.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{i.backend.restorable}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
srcs := [graphics.ShaderImageNum]*restorable.Image{i.backend.restorable}
var offsets [graphics.ShaderImageNum - 1][2]float32
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
i.dispose(false)
i.backend = &backend{
@ -322,7 +324,6 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
dy = paddingSize
}
// TODO: Pass the offsets as uniform variables for second and following images.
var oxf, oyf float32
if srcs[0] != nil {
ox, oy, _, _ := srcs[0].regionWithPadding()
@ -342,6 +343,19 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
}
}
var offsets [graphics.ShaderImageNum - 1][2]float32
for i := range offsets {
src := srcs[i+1]
if src == nil {
continue
}
ox, oy, _, _ := src.regionWithPadding()
ox += paddingSize
oy += paddingSize
offsets[i][0] = float32(ox) - oxf
offsets[i][1] = float32(oy) - oyf
}
var s *restorable.Shader
if shader != nil {
s = shader.shader
@ -355,7 +369,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
imgs[i] = src.backend.restorable
}
i.backend.restorable.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
i.nonUpdatedCount = 0
delete(imagesToMakeShared, i)

View File

@ -17,6 +17,7 @@ package testing
import (
"go/constant"
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/shaderir"
)
@ -210,10 +211,7 @@ var (
)
func defaultProgram() shaderir.Program {
return shaderir.Program{
Uniforms: []shaderir.Type{
{Main: shaderir.Vec2}, // Viewport size. This must be the first uniform variable, and the values are set at graphicscommand driver.
},
p := shaderir.Program{
Attributes: []shaderir.Type{
{Main: shaderir.Vec2}, // Local var (0) in the vertex shader
{Main: shaderir.Vec2}, // Local var (1) in the vertex shader
@ -225,6 +223,12 @@ func defaultProgram() shaderir.Program {
},
VertexFunc: defaultVertexFunc,
}
p.Uniforms = make([]shaderir.Type, graphics.PreservedUniformVariablesNum)
for i := range p.Uniforms {
p.Uniforms[i] = shaderir.Type{Main: shaderir.Vec2}
}
return p
}
// ShaderProgramFill returns a shader intermediate representation to fill the frambuffer.
@ -340,6 +344,17 @@ func ShaderProgramImages(imageNum int) shaderir.Program {
},
}
} else {
texPos2 := shaderir.Expr{
Type: shaderir.Binary,
Op: shaderir.Add,
Exprs: []shaderir.Expr{
texPos,
{
Type: shaderir.UniformVariable,
Index: 1 + i - 1,
},
},
}
rhs = shaderir.Expr{
Type: shaderir.Binary,
Op: shaderir.Add,
@ -356,7 +371,7 @@ func ShaderProgramImages(imageNum int) shaderir.Program {
Type: shaderir.TextureVariable,
Index: i,
},
texPos,
texPos2,
},
},
},

View File

@ -19,14 +19,16 @@ import (
"fmt"
"go/parser"
"go/token"
"strings"
"github.com/hajimehoshi/ebiten/internal/buffered"
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/shader"
)
var shaderSuffix = `
var shaderSuffix string
func init() {
shaderSuffix = `
var __viewportSize vec2
func viewportSize() vec2 {
@ -34,18 +36,24 @@ func viewportSize() vec2 {
}
`
func init() {
// __t%d is a special variable for a texture variable.
// TODO: Add appropriate offsets for second and following images.
for i := 1; i < graphics.ShaderImageNum; i++ {
shaderSuffix += fmt.Sprintf(`
var __offset%d vec2
`, i)
}
var fs []string
for i := 0; i < graphics.ShaderImageNum; i++ {
fs = append(fs, fmt.Sprintf(`func texture%[1]dAt(pos vec2) vec4 {
return texture2D(__t%[1]d, pos)
var offset string
if i >= 1 {
offset = fmt.Sprintf(" + __offset%d", i)
}
`, i))
// __t%d is a special variable for a texture variable.
shaderSuffix += fmt.Sprintf(`
func texture%[1]dAt(pos vec2) vec4 {
return texture2D(__t%[1]d, pos%[2]s)
}
`, i, offset)
}
shaderSuffix += "\n" + strings.Join(fs, "\n")
}
type Shader struct {