From e0d5763a60460d1f7c894c5d1ca46a58043a68f3 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 18 Jul 2020 01:09:58 +0900 Subject: [PATCH] shader: Use the fixed number of images for shaders This changes uses arrays rather than slices in order to avoid heap allocations. Updates #1193 --- examples/shader/main.go | 4 +- image.go | 25 +++++-- internal/buffered/image.go | 36 ++++------ internal/driver/graphics.go | 4 +- internal/graphics/vertex.go | 2 + internal/graphicscommand/command.go | 78 ++++++++++++--------- internal/graphicscommand/image.go | 24 +++---- internal/graphicscommand/image_test.go | 10 +-- internal/graphicsdriver/metal/graphics.go | 6 +- internal/graphicsdriver/opengl/graphics.go | 26 +++++-- internal/graphicsdriver/opengl/program.go | 12 +++- internal/mipmap/mipmap.go | 44 ++++++------ internal/restorable/image.go | 80 ++++++++++------------ internal/restorable/images_test.go | 54 +++++++-------- internal/restorable/shader_test.go | 10 +-- internal/shareable/image.go | 54 +++++++-------- internal/shareable/image_test.go | 18 ++--- shader.go | 5 +- 18 files changed, 261 insertions(+), 231 deletions(-) diff --git a/examples/shader/main.go b/examples/shader/main.go index fe99061db..d4297329a 100644 --- a/examples/shader/main.go +++ b/examples/shader/main.go @@ -78,7 +78,7 @@ func (g *Game) Update(screen *ebiten.Image) error { g.shaders = map[int]*ebiten.Shader{} } if _, ok := g.shaders[g.idx]; !ok { - s, err := ebiten.NewShader([]byte(shaderSrcs[g.idx]), 1) + s, err := ebiten.NewShader([]byte(shaderSrcs[g.idx])) if err != nil { return err } @@ -130,7 +130,7 @@ func (g *Game) Draw(screen *ebiten.Image) { []float32{float32(cx), float32(cy)}, // Cursor } if g.idx != 0 { - op.Images = append(op.Images, gophersImage) + op.Images[0] = gophersImage } screen.DrawTrianglesWithShader(vs, is, s, op) diff --git a/image.go b/image.go index a7eef89bc..c1901d68a 100644 --- a/image.go +++ b/image.go @@ -15,6 +15,7 @@ package ebiten import ( + "fmt" "image" "image/color" @@ -205,7 +206,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { sy1 := float32(bounds.Max.Y) vs := graphics.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, 1, 1, 1, 1, filter == driver.FilterScreen) is := graphics.QuadIndices() - i.buffered.DrawTriangles(img.buffered, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + i.buffered.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{img.buffered}, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, driver.Region{}, nil, nil) return nil } @@ -342,15 +343,22 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o } } - i.buffered.DrawTriangles(img.buffered, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address), sr, nil, nil, nil) + i.buffered.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{img.buffered}, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address), sr, nil, nil) } type DrawTrianglesWithShaderOptions struct { Uniforms []interface{} - Images []*Image + Images [4]*Image CompositeMode CompositeMode } +func init() { + var op DrawTrianglesWithShaderOptions + if got, want := len(op.Images), graphics.ShaderImageNum; got != want { + panic(fmt.Sprintf("ebiten: len((DrawTrianglesWithShaderOptions{}).Images) must be %d but %d", want, got)) + } +} + // TODO: Add comments and tests func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesWithShaderOptions) { i.copyCheck() @@ -381,8 +389,11 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha us := append([]interface{}{[]float32{0, 0}}, options.Uniforms...) var imgw, imgh int - var imgs []*buffered.Image - for _, img := range options.Images { + var imgs [graphics.ShaderImageNum]*buffered.Image + for i, img := range options.Images { + if img == nil { + continue + } if img.isDisposed() { panic("ebiten: the given image to DrawTriangles must not be disposed") } @@ -391,7 +402,7 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha } else if w, h := img.Size(); imgw != w || imgh != h { panic("ebiten: all the source images must be the same size") } - imgs = append(imgs, img.buffered) + imgs[i] = img.buffered } vs := make([]float32, len(vertices)*graphics.VertexFloatNum) @@ -408,7 +419,7 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha is := make([]uint16, len(indices)) copy(is, indices) - i.buffered.DrawTriangles(nil, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, shader.shader, us, imgs) + i.buffered.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, shader.shader, us) } // SubImage returns an image representing the portion of the image p visible through r. diff --git a/internal/buffered/image.go b/internal/buffered/image.go index e6265c76c..2adaf67ee 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -21,6 +21,7 @@ import ( "github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/driver" + "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/mipmap" "github.com/hajimehoshi/ebiten/internal/shaderir" ) @@ -235,13 +236,8 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) { // DrawTriangles draws the src image with the given vertices. // // Copying vertices and indices is the caller's responsibility. -func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Image) { - if src != nil { - if i == src { - panic("buffered: Image.DrawTriangles: src must be different from the receiver") - } - } - for _, src := range images { +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{}) { + for _, src := range srcs { if i == src { panic("buffered: Image.DrawTriangles: source images must be different from the receiver") } @@ -250,17 +246,17 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, if maybeCanAddDelayedCommand() { if tryAddDelayedCommand(func() error { // Arguments are not copied. Copying is the caller's responsibility. - i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms, images) + i.DrawTriangles(srcs, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms) return nil }) { return } } - if src != nil { - src.resolvePendingPixels(true) - } - for _, src := range images { + for _, src := range srcs { + if src == nil { + continue + } src.resolvePendingPixels(true) } i.resolvePendingPixels(false) @@ -270,17 +266,15 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, s = shader.shader } - var srcImg *mipmap.Mipmap - if src != nil { - srcImg = src.img + var imgs [graphics.ShaderImageNum]*mipmap.Mipmap + for i, img := range srcs { + if img == nil { + continue + } + imgs[i] = img.img } - var imgs []*mipmap.Mipmap - for _, img := range images { - imgs = append(imgs, img.img) - } - - i.img.DrawTriangles(srcImg, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, imgs) + i.img.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms) i.invalidatePendingPixels() } diff --git a/internal/driver/graphics.go b/internal/driver/graphics.go index d03da260c..f1540c1ff 100644 --- a/internal/driver/graphics.go +++ b/internal/driver/graphics.go @@ -18,6 +18,7 @@ import ( "errors" "github.com/hajimehoshi/ebiten/internal/affine" + "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/shaderir" "github.com/hajimehoshi/ebiten/internal/thread" ) @@ -44,6 +45,7 @@ type Graphics interface { IsGL() bool HasHighPrecisionFloat() bool MaxImageSize() int + InvalidImageID() ImageID NewShader(program *shaderir.Program) (Shader, error) @@ -58,7 +60,7 @@ type Graphics interface { // // * float32 // * []float32 - DrawShader(dst ImageID, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, uniforms []interface{}, srcs []ImageID) error + DrawShader(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, 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. diff --git a/internal/graphics/vertex.go b/internal/graphics/vertex.go index 98dda8e57..872a5cf50 100644 --- a/internal/graphics/vertex.go +++ b/internal/graphics/vertex.go @@ -18,6 +18,8 @@ import ( "github.com/hajimehoshi/ebiten/internal/web" ) +const ShaderImageNum = 4 + const ( IndicesNum = (1 << 16) / 3 * 3 // Adjust num for triangles. VertexFloatNum = 8 diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index d2d499a8d..65e5a44e2 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -17,6 +17,7 @@ package graphicscommand import ( "fmt" "math" + "strings" "github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/driver" @@ -65,7 +66,7 @@ type command interface { NumIndices() int AddNumVertices(n int) AddNumIndices(n int) - CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool + CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool } type size struct { @@ -143,7 +144,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) { } // EnqueueDrawTrianglesCommand enqueues a drawing-image command. -func (q *commandQueue) EnqueueDrawTrianglesCommand(dst, src *Image, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Image) { +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{}) { 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)) } @@ -155,28 +156,30 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst, src *Image, vertices []f split = true } - if src != nil { - q.appendVertices(vertices, src) - } else if len(images) > 0 { - q.appendVertices(vertices, images[0]) - } else { - q.appendVertices(vertices, nil) + // Assume that all the image sizes are same. + var firstSrc *Image + for _, src := range srcs { + if src != nil { + firstSrc = src + break + } } + q.appendVertices(vertices, firstSrc) q.appendIndices(indices, uint16(q.nextIndex)) q.nextIndex += len(vertices) / graphics.VertexFloatNum q.tmpNumIndices += len(indices) // TODO: If dst is the screen, reorder the command to be the last. if !split && 0 < len(q.commands) { - if last := q.commands[len(q.commands)-1]; last.CanMergeWithDrawTrianglesCommand(dst, src, color, mode, filter, address, sourceRegion, 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)) return } } - if address != driver.AddressUnsafe { - w, h := src.InternalSize() + if firstSrc != nil && address != driver.AddressUnsafe { + w, h := firstSrc.InternalSize() sourceRegion.X /= float32(w) sourceRegion.Y /= float32(h) sourceRegion.Width /= float32(w) @@ -185,7 +188,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst, src *Image, vertices []f c := &drawTrianglesCommand{ dst: dst, - src: src, + srcs: srcs, nvertices: len(vertices), nindices: len(indices), color: color, @@ -195,7 +198,6 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst, src *Image, vertices []f sourceRegion: sourceRegion, shader: shader, uniforms: uniforms, - images: images, } q.commands = append(q.commands, c) } @@ -312,7 +314,7 @@ func FlushCommands() error { // drawTrianglesCommand represents a drawing command to draw an image on another image. type drawTrianglesCommand struct { dst *Image - src *Image + srcs [graphics.ShaderImageNum]*Image nvertices int nindices int color *affine.ColorM @@ -322,7 +324,6 @@ type drawTrianglesCommand struct { sourceRegion driver.Region shader *Shader uniforms []interface{} - images []*Image } func (c *drawTrianglesCommand) String() string { @@ -391,12 +392,19 @@ func (c *drawTrianglesCommand) String() string { panic(fmt.Sprintf("graphicscommand: invalid address: %d", c.address)) } - src := fmt.Sprintf("%d", c.src.id) - if c.src.screen { - src += " (screen)" + var srcstrs [graphics.ShaderImageNum]string + for i, src := range c.srcs { + if src == nil { + srcstrs[i] = "(nil)" + continue + } + srcstrs[i] = fmt.Sprintf("%d", src.id) + if src.screen { + srcstrs[i] += " (screen)" + } } - return fmt.Sprintf("draw-triangles: dst: %s <- src: %s, num of indices: %d, colorm: %v, mode %s, filter: %s, address: %s", dst, src, c.nindices, c.color, mode, filter, address) + return fmt.Sprintf("draw-triangles: dst: %s <- src: [%s], num of indices: %d, colorm: %v, mode %s, filter: %s, address: %s", dst, strings.Join(srcstrs[:], ", "), c.nindices, c.color, mode, filter, address) } // Exec executes the drawTrianglesCommand. @@ -407,9 +415,13 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error { } if c.shader != nil { - var imgs []driver.ImageID - for _, img := range c.images { - imgs = append(imgs, img.image.ID()) + var imgs [graphics.ShaderImageNum]driver.ImageID + for i, src := range c.srcs { + if src == nil { + imgs[i] = theGraphicsDriver.InvalidImageID() + continue + } + imgs[i] = src.image.ID() } // The last uniform variables are added at /shader.go and represents a viewport size. @@ -418,9 +430,9 @@ func (c *drawTrianglesCommand) Exec(indexOffset int) error { viewport[0] = float32(w) viewport[1] = float32(h) - return theGraphicsDriver.DrawShader(c.dst.image.ID(), c.shader.shader.ID(), c.nindices, indexOffset, c.mode, c.uniforms, imgs) + return theGraphicsDriver.DrawShader(c.dst.image.ID(), imgs, c.shader.shader.ID(), c.nindices, indexOffset, c.mode, c.uniforms) } - return theGraphicsDriver.Draw(c.dst.image.ID(), c.src.image.ID(), c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.sourceRegion) + 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) } func (c *drawTrianglesCommand) NumVertices() int { @@ -441,7 +453,7 @@ func (c *drawTrianglesCommand) AddNumIndices(n int) { // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged // with the drawTrianglesCommand c. -func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { // If a shader is used, commands are not merged. // // TODO: Merge shader commands considering uniform variables. @@ -451,7 +463,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, if c.dst != dst { return false } - if c.src != src { + if c.srcs != srcs { return false } if !c.color.Equals(color) { @@ -502,7 +514,7 @@ func (c *replacePixelsCommand) AddNumVertices(n int) { func (c *replacePixelsCommand) AddNumIndices(n int) { } -func (c *replacePixelsCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *replacePixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -539,7 +551,7 @@ func (c *pixelsCommand) AddNumVertices(n int) { func (c *pixelsCommand) AddNumIndices(n int) { } -func (c *pixelsCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *pixelsCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -572,7 +584,7 @@ func (c *disposeImageCommand) AddNumVertices(n int) { func (c *disposeImageCommand) AddNumIndices(n int) { } -func (c *disposeImageCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *disposeImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -605,7 +617,7 @@ func (c *disposeShaderCommand) AddNumVertices(n int) { func (c *disposeShaderCommand) AddNumIndices(n int) { } -func (c *disposeShaderCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *disposeShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -644,7 +656,7 @@ func (c *newImageCommand) AddNumVertices(n int) { func (c *newImageCommand) AddNumIndices(n int) { } -func (c *newImageCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *newImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -680,7 +692,7 @@ func (c *newScreenFramebufferImageCommand) AddNumVertices(n int) { func (c *newScreenFramebufferImageCommand) AddNumIndices(n int) { } -func (c *newScreenFramebufferImageCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *newScreenFramebufferImageCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } @@ -715,7 +727,7 @@ func (c *newShaderCommand) AddNumVertices(n int) { func (c *newShaderCommand) AddNumIndices(n int) { } -func (c *newShaderCommand) CanMergeWithDrawTrianglesCommand(dst, src *Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { +func (c *newShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [graphics.ShaderImageNum]*Image, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader) bool { return false } diff --git a/internal/graphicscommand/image.go b/internal/graphicscommand/image.go index 336e93d54..ae33da91d 100644 --- a/internal/graphicscommand/image.go +++ b/internal/graphicscommand/image.go @@ -126,13 +126,6 @@ func (i *Image) InternalSize() (int, int) { return i.internalWidth, i.internalHeight } -func processSrc(src *Image) { - if src.screen { - panic("graphicscommand: the screen image cannot be the rendering source") - } - src.resolveBufferedReplacePixels() -} - // DrawTriangles draws triangles with the given image. // // The vertex floats are: @@ -154,22 +147,25 @@ func processSrc(src *Image) { // // 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(src *Image, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Image) { +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{}) { if i.lastCommand == lastCommandNone { if !i.screen && mode != driver.CompositeModeClear { panic("graphicscommand: the image must be cleared first") } } - if src != nil { - processSrc(src) - } - for _, src := range images { - processSrc(src) + for _, src := range srcs { + if src == nil { + continue + } + if src.screen { + panic("graphicscommand: the screen image cannot be the rendering source") + } + src.resolveBufferedReplacePixels() } i.resolveBufferedReplacePixels() - theCommandQueue.EnqueueDrawTrianglesCommand(i, src, vertices, indices, clr, mode, filter, address, sourceRegion, shader, uniforms, images) + theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, vertices, indices, clr, mode, filter, address, sourceRegion, shader, uniforms) if i.lastCommand == lastCommandNone && !i.screen { i.lastCommand = lastCommandClear diff --git a/internal/graphicscommand/image_test.go b/internal/graphicscommand/image_test.go index e3c8ef0a0..e0c811bb2 100644 --- a/internal/graphicscommand/image_test.go +++ b/internal/graphicscommand/image_test.go @@ -44,7 +44,7 @@ func TestClear(t *testing.T) { vs := quadVertices(w/2, h/2) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, 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(clr, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) } @@ -89,14 +89,14 @@ func TestShader(t *testing.T) { dst := NewImage(w, h) vs := quadVertices(w, h) is := graphics.QuadIndices() - dst.DrawTriangles(clr, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff) s := NewShader(&ir) us := []interface{}{ []float32{0, 0}, } - dst.DrawTriangles(nil, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us) pix, err := dst.Pixels() if err != nil { diff --git a/internal/graphicsdriver/metal/graphics.go b/internal/graphicsdriver/metal/graphics.go index 62676ad78..71dde5bc5 100644 --- a/internal/graphicsdriver/metal/graphics.go +++ b/internal/graphicsdriver/metal/graphics.go @@ -432,6 +432,10 @@ func (g *Graphics) genNextImageID() driver.ImageID { return id } +func (g *Graphics) InvalidImageID() driver.ImageID { + return -1 +} + func (g *Graphics) NewImage(width, height int) (driver.Image, error) { g.checkSize(width, height) td := mtl.TextureDescriptor{ @@ -796,7 +800,7 @@ func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) { panic("metal: NewShader is not implemented") } -func (g *Graphics) DrawShader(dst driver.ImageID, shader driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, uniforms []interface{}, srcs []driver.ImageID) error { +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 { panic("metal: DrawShader is not implemented") } diff --git a/internal/graphicsdriver/opengl/graphics.go b/internal/graphicsdriver/opengl/graphics.go index 2feac0095..deda61ad7 100644 --- a/internal/graphicsdriver/opengl/graphics.go +++ b/internal/graphicsdriver/opengl/graphics.go @@ -84,6 +84,10 @@ func (g *Graphics) genNextImageID() driver.ImageID { return id } +func (g *Graphics) InvalidImageID() driver.ImageID { + return -1 +} + func (g *Graphics) genNextShaderID() driver.ShaderID { id := g.nextShaderID g.nextShaderID++ @@ -215,7 +219,15 @@ func (g *Graphics) Draw(dst, src driver.ImageID, indexLen int, indexOffset int, }) } - if err := g.useProgram(program, uniforms, []textureNative{source.textureNative}); err != nil { + var imgs [graphics.ShaderImageNum]textureVariable + for i := range imgs { + if i == 0 { + imgs[i].valid = true + imgs[i].native = source.textureNative + } + } + + if err := g.useProgram(program, uniforms, imgs); err != nil { return err } @@ -275,7 +287,7 @@ func (g *Graphics) removeShader(shader *Shader) { delete(g.shaders, shader.id) } -func (g *Graphics) DrawShader(dst driver.ImageID, shader driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, uniforms []interface{}, srcs []driver.ImageID) error { +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 { d := g.images[dst] s := g.shaders[shader] @@ -292,9 +304,13 @@ func (g *Graphics) DrawShader(dst driver.ImageID, shader driver.ShaderID, indexL us[k].value = v } - ts := make([]textureNative, len(srcs)) - for k, v := range srcs { - ts[k] = g.images[v].textureNative + var ts [graphics.ShaderImageNum]textureVariable + for i, src := range srcs { + if src == g.InvalidImageID() { + continue + } + ts[i].valid = true + ts[i].native = g.images[src].textureNative } if err := g.useProgram(s.p, us, ts); err != nil { diff --git a/internal/graphicsdriver/opengl/program.go b/internal/graphicsdriver/opengl/program.go index c61ea8a4e..13c32f62e 100644 --- a/internal/graphicsdriver/opengl/program.go +++ b/internal/graphicsdriver/opengl/program.go @@ -239,8 +239,13 @@ type uniformVariable struct { value interface{} } +type textureVariable struct { + valid bool + native textureNative +} + // useProgram uses the program (programTexture). -func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures []textureNative) error { +func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderImageNum]textureVariable) error { if !g.state.lastProgram.equal(program) { g.context.useProgram(program) if g.state.lastProgram.equal(zeroProgram) { @@ -278,13 +283,16 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu } for i, t := range textures { + if !t.valid { + continue + } g.context.uniformInt(program, fmt.Sprintf("T%d", i), i) if g.state.lastActiveTexture != i { g.context.activeTexture(i) g.state.lastActiveTexture = i } // Apparently, a texture must be bound every time. The cache is not used here. - g.context.bindTexture(t) + g.context.bindTexture(t.native) } return nil diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index e1ff33677..a271250ea 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -87,9 +87,10 @@ func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) { return m.orig.Pixels(x, y, width, height) } -func (m *Mipmap) DrawTriangles(src *Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Mipmap) { +func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) { level := 0 - if src != nil && !src.volatile && filter != driver.FilterScreen { + // TODO: Do we need to check all the sources' states of being volatile? + if srcs[0] != nil && !srcs[0].volatile && filter != driver.FilterScreen { level = math.MaxInt32 for i := 0; i < len(indices)/3; i++ { const n = graphics.VertexFloatNum @@ -138,30 +139,27 @@ func (m *Mipmap) DrawTriangles(src *Mipmap, vertices []float32, indices []uint16 s = shader.shader } - var srcimg *shareable.Image - if src != nil { - srcimg = src.orig - } - - if level != 0 { - if img := src.level(level); img != nil { - srcimg = img - const n = graphics.VertexFloatNum - s := float32(pow2(level)) - for i := 0; i < len(vertices)/n; i++ { - vertices[i*n+2] /= s - vertices[i*n+3] /= s + var imgs [graphics.ShaderImageNum]*shareable.Image + for i, src := range srcs { + if src == nil { + continue + } + if level != 0 { + if img := src.level(level); img != nil { + const n = graphics.VertexFloatNum + s := float32(pow2(level)) + for i := 0; i < len(vertices)/n; i++ { + vertices[i*n+2] /= s + vertices[i*n+3] /= s + } + imgs[i] = img + continue } } + imgs[i] = src.orig } - // TODO: Do we need to consider mipmaps here? - var imgs []*shareable.Image - for _, img := range images { - imgs = append(imgs, img.orig) - } - - m.orig.DrawTriangles(srcimg, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, imgs) + m.orig.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms) m.disposeMipmaps() } @@ -222,7 +220,7 @@ func (m *Mipmap) level(level int) *shareable.Image { return nil } s := shareable.NewImage(w2, h2, m.volatile) - s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + s.DrawTriangles([graphics.ShaderImageNum]*shareable.Image{src}, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, driver.Region{}, nil, nil) m.imgs[level] = s return m.imgs[level] diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 89889fdb9..6ee44202d 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -69,7 +69,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 { - image *Image + images [graphics.ShaderImageNum]*Image vertices []float32 indices []uint16 colorm *affine.ColorM @@ -79,7 +79,6 @@ type drawTrianglesHistoryItem struct { sourceRegion driver.Region shader *Shader uniforms []interface{} - images []*Image } // Image represents an image that can be restored when GL context is lost. @@ -260,7 +259,7 @@ 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(emptyImage.image, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + i.DrawTriangles([graphics.ShaderImageNum]*graphicscommand.Image{emptyImage.image}, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) } // BasePixelsForTesting returns the image's basePixels for testing. @@ -352,7 +351,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(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Image) { +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{}) { if i.priority { panic("restorable: DrawTriangles cannot be called on a priority image") } @@ -363,41 +362,38 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, // TODO: Add tests to confirm this logic. var srcstale bool - if img != nil && (img.stale || img.volatile) { - srcstale = true - } - if !srcstale { - for _, t := range images { - if t.stale || t.volatile { - srcstale = true - break - } + for _, src := range srcs { + if src == nil { + continue + } + if src.stale || src.volatile { + srcstale = true + break } } if srcstale || i.screen || !needsRestoring() || i.volatile { i.makeStale() } else { - i.appendDrawTrianglesHistory(img, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms, images) + i.appendDrawTrianglesHistory(srcs, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms) } var s *graphicscommand.Shader if shader != nil { s = shader.shader } - var gimg *graphicscommand.Image - if img != nil { - gimg = img.image - } - var ts []*graphicscommand.Image - for _, t := range images { - ts = append(ts, t.image) + var imgs [graphics.ShaderImageNum]*graphicscommand.Image + for i, src := range srcs { + if src == nil { + continue + } + imgs[i] = src.image } - i.image.DrawTriangles(gimg, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, ts) + i.image.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms) } // appendDrawTrianglesHistory appends a draw-image history item to the image. -func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*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{}) { if i.stale || i.volatile || i.screen { return } @@ -416,7 +412,7 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind copy(is, indices) item := &drawTrianglesHistoryItem{ - image: image, + images: srcs, vertices: vs, indices: is, colorm: colorm, @@ -426,7 +422,6 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind sourceRegion: sourceRegion, shader: shader, uniforms: uniforms, - images: images, } i.drawTrianglesHistory = append(i.drawTrianglesHistory, item) } @@ -515,10 +510,10 @@ func (i *Image) resolveStale() error { // dependsOn reports whether the image depends on target. func (i *Image) dependsOn(target *Image) bool { for _, c := range i.drawTrianglesHistory { - if c.image == target { - return true - } for _, img := range c.images { + if img == nil { + continue + } if img == target { return true } @@ -541,10 +536,10 @@ func (i *Image) dependsOnShader(shader *Shader) bool { func (i *Image) dependingImages() map[*Image]struct{} { r := map[*Image]struct{}{} for _, c := range i.drawTrianglesHistory { - if c.image != nil { - r[c.image] = struct{}{} - } for _, img := range c.images { + if img == nil { + continue + } r[img] = struct{}{} } } @@ -592,23 +587,22 @@ func (i *Image) restore() error { i.basePixels.Apply(gimg) for _, c := range i.drawTrianglesHistory { - if c.image != nil && c.image.hasDependency() { - panic("restorable: all dependencies must be already resolved but not") - } - // TODO: Check the uniform variable's images. - var img *graphicscommand.Image - if c.image != nil { - img = c.image.image - } var s *graphicscommand.Shader if c.shader != nil { s = c.shader.shader } - var imgs []*graphicscommand.Image - for _, img := range c.images { - imgs = append(imgs, img.image) + + var imgs [graphics.ShaderImageNum]*graphicscommand.Image + for i, img := range c.images { + if img == nil { + continue + } + if img.hasDependency() { + panic("restorable: all dependencies must be already resolved but not") + } + imgs[i] = img.image } - gimg.DrawTriangles(img, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.sourceRegion, s, c.uniforms, imgs) + gimg.DrawTriangles(imgs, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.sourceRegion, s, c.uniforms) } if len(i.drawTrianglesHistory) > 0 { diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 1b9f79673..9b7ad1b78 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -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(imgs[i], vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, 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(imgs[7], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) - imgs[9].DrawTriangles(imgs[8], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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) for i := 0; i < 7; i++ { - imgs[i+1].DrawTriangles(imgs[i], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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) } 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(img1, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) - img3.DrawTriangles(img2, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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) img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h) - img1.DrawTriangles(img0, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, 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,23 @@ func TestRestoreComplexGraph(t *testing.T) { }() vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - img3.DrawTriangles(img0, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 1, 0) - img3.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 1, 0) - img4.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 2, 0) - img4.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 0, 0) - img5.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 0, 0) - img6.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 1, 0) - img6.DrawTriangles(img4, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 0, 0) - img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) vs = quadVertices(w, h, 2, 0) - img7.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -406,8 +406,8 @@ func TestRestoreRecursive(t *testing.T) { img0.Dispose() }() is := graphics.QuadIndices() - img1.DrawTriangles(img0, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) - img0.DrawTriangles(img1, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -501,7 +501,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, 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 +538,8 @@ func TestDispose(t *testing.T) { defer img2.Dispose() is := graphics.QuadIndices() - img1.DrawTriangles(img2, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) - img0.DrawTriangles(img1, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + 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.Dispose() if err := ResolveStaleImages(); err != nil { @@ -647,7 +647,7 @@ func TestReplacePixelsOnly(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, 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 +700,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) { src.ReplacePixels(pix, 0, 0, w, h) vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, 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 +721,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, 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 +739,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) } @@ -828,7 +828,7 @@ func TestFill2(t *testing.T) { dst := NewImage(w, h, false) vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, 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 +867,7 @@ func TestMutateSlices(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := make([]uint16, len(graphics.QuadIndices())) copy(is, graphics.QuadIndices()) - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) for i := range vs { vs[i] = 0 } diff --git a/internal/restorable/shader_test.go b/internal/restorable/shader_test.go index 295b638db..53f2329e5 100644 --- a/internal/restorable/shader_test.go +++ b/internal/restorable/shader_test.go @@ -38,7 +38,7 @@ func TestShader(t *testing.T) { us := []interface{}{ []float32{0, 0}, } - img.DrawTriangles(nil, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us, nil) + img.DrawTriangles([graphics.ShaderImageNum]*Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us) if err := ResolveStaleImages(); err != nil { t.Fatal(err) @@ -75,7 +75,7 @@ func TestShaderChain(t *testing.T) { us := []interface{}{ []float32{0, 0}, } - imgs[i+1].DrawTriangles(nil, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us, imgs[i:i+1]) + 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, us) } if err := ResolveStaleImages(); err != nil { @@ -99,7 +99,7 @@ func TestShaderMultipleSources(t *testing.T) { t.Skip("shader is not available on this environment") } - srcs := make([]*Image, 3) + var srcs [graphics.ShaderImageNum]*Image for i := range srcs { srcs[i] = NewImage(1, 1, false) } @@ -114,7 +114,7 @@ func TestShaderMultipleSources(t *testing.T) { us := []interface{}{ []float32{0, 0}, } - dst.DrawTriangles(nil, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us, srcs) + dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us) // Clear one of the sources after DrawTriangles. dst should not be affected. srcs[0].Fill(color.RGBA{}) @@ -146,7 +146,7 @@ func TestShaderDispose(t *testing.T) { us := []interface{}{ []float32{0, 0}, } - img.DrawTriangles(nil, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us, nil) + img.DrawTriangles([graphics.ShaderImageNum]*Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, s, us) // Dispose the shader. This should invalidates all the images using this shader i.e., all the images become // stale. diff --git a/internal/shareable/image.go b/internal/shareable/image.go index f0e7393bc..a1e009f4f 100644 --- a/internal/shareable/image.go +++ b/internal/shareable/image.go @@ -220,7 +220,7 @@ func (i *Image) ensureNotShared() { dx1, dy1, sx1, sy1, 1, 1, 1, 1, } is := graphics.QuadIndices() - newImg.DrawTriangles(i.backend.restorable, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + newImg.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{i.backend.restorable}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) i.dispose(false) i.backend = &backend{ @@ -273,6 +273,9 @@ func (i *Image) regionWithPadding() (x, y, width, height int) { } func (i *Image) processSrc(src *Image) { + if src == nil { + return + } if src.disposed { panic("shareable: the drawing source image must not be disposed (DrawTriangles)") } @@ -287,12 +290,6 @@ func (i *Image) processSrc(src *Image) { } } -func makeSharedIfNeeded(src *Image) { - if !src.isShared() && src.shareable() { - imagesToMakeShared[src] = struct{}{} - } -} - // DrawTriangles draws triangles with the given image. // // The vertex floats are: @@ -305,7 +302,7 @@ func makeSharedIfNeeded(src *Image) { // 5: Color G // 6: Color B // 7: Color Y -func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Image) { +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{}) { backendsM.Lock() // Do not use defer for performance. @@ -314,14 +311,7 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, } i.ensureNotShared() - if img != nil { - i.processSrc(img) - } - firstImg := img - for _, src := range images { - if firstImg == nil { - firstImg = src - } + for _, src := range srcs { i.processSrc(src) } @@ -332,9 +322,10 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, dy = paddingSize } + // TODO: Pass the offsets as uniform variables for second and following images. var oxf, oyf float32 - if firstImg != nil { - ox, oy, _, _ := firstImg.regionWithPadding() + if srcs[0] != nil { + ox, oy, _, _ := srcs[0].regionWithPadding() ox += paddingSize oy += paddingSize oxf, oyf = float32(ox), float32(oy) @@ -356,25 +347,26 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, s = shader.shader } - var imgs []*restorable.Image - for _, img := range images { - imgs = append(imgs, img.backend.restorable) + var imgs [graphics.ShaderImageNum]*restorable.Image + for i, src := range srcs { + if src == nil { + continue + } + imgs[i] = src.backend.restorable } - var r *restorable.Image - if img != nil { - r = img.backend.restorable - } - i.backend.restorable.DrawTriangles(r, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, imgs) + i.backend.restorable.DrawTriangles(imgs, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms) i.nonUpdatedCount = 0 delete(imagesToMakeShared, i) - if img != nil { - makeSharedIfNeeded(img) - } - for _, src := range images { - makeSharedIfNeeded(src) + for _, src := range srcs { + if src == nil { + continue + } + if !src.isShared() && src.shareable() { + imagesToMakeShared[src] = struct{}{} + } } backendsM.Unlock() diff --git a/internal/shareable/image_test.go b/internal/shareable/image_test.go index 8cb3c823e..650fbaba7 100644 --- a/internal/shareable/image_test.go +++ b/internal/shareable/image_test.go @@ -96,7 +96,7 @@ func TestEnsureNotShared(t *testing.T) { // img4.ensureNotShared() should be called. vs := quadVertices(size/2, size/2, size/4, size/4, 1) is := graphics.QuadIndices() - img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) want := false if got := img4.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -126,7 +126,7 @@ func TestEnsureNotShared(t *testing.T) { // Check further drawing doesn't cause panic. // This bug was fixed by 03dcd948. - img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) } func TestReshared(t *testing.T) { @@ -166,7 +166,7 @@ func TestReshared(t *testing.T) { // Use img1 as a render target. vs := quadVertices(size, size, 0, 0, 1) is := graphics.QuadIndices() - img1.DrawTriangles(img2, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -176,7 +176,7 @@ func TestReshared(t *testing.T) { if err := MakeImagesSharedForTesting(); err != nil { t.Fatal(err) } - img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -203,7 +203,7 @@ func TestReshared(t *testing.T) { } } - img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) if got, want := img1.IsSharedForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -231,7 +231,7 @@ func TestReshared(t *testing.T) { if err := MakeImagesSharedForTesting(); err != nil { t.Fatal(err) } - img0.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) if got, want := img3.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -326,7 +326,7 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) dst.ReplacePixels(pix) pix, err := dst.Pixels(0, 0, w, h) @@ -368,7 +368,7 @@ func TestSmallImages(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) pix, err := dst.Pixels(0, 0, w, h) if err != nil { @@ -410,7 +410,7 @@ func TestLongImages(t *testing.T) { const scale = 120 vs := quadVertices(w, h, 0, 0, scale) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil, nil) + dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) pix, err := dst.Pixels(0, 0, dstW, dstH) if err != nil { diff --git a/shader.go b/shader.go index 6807435b8..2e5e721ff 100644 --- a/shader.go +++ b/shader.go @@ -20,6 +20,7 @@ import ( "go/token" "github.com/hajimehoshi/ebiten/internal/buffered" + "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/shader" ) @@ -35,7 +36,7 @@ type Shader struct { shader *buffered.Shader } -func NewShader(src []byte, textureNum int) (*Shader, error) { +func NewShader(src []byte) (*Shader, error) { var buf bytes.Buffer buf.Write(src) buf.WriteString(shaderSuffix) @@ -47,7 +48,7 @@ func NewShader(src []byte, textureNum int) (*Shader, error) { } // TODO: Create a pseudo vertex entrypoint to treat the attribute values correctly. - s, err := shader.Compile(fs, f, "Vertex", "Fragment", textureNum) + s, err := shader.Compile(fs, f, "Vertex", "Fragment", graphics.ShaderImageNum) if err != nil { return nil, err }