diff --git a/internal/graphicscommand/image.go b/internal/graphicscommand/image.go index cba698dfc..a9ed3c291 100644 --- a/internal/graphicscommand/image.go +++ b/internal/graphicscommand/image.go @@ -105,6 +105,22 @@ func (i *Image) InternalSize() (int, int) { return i.internalWidth, i.internalHeight } +// DrawTriangles draws triangles with the given image. +// +// The vertex floats are: +// +// 0: Destination X in pixels +// 1: Destination Y in pixels +// 2: Source X in texels +// 3: Source Y in texels +// 4: Bounds of the source min X in texels +// 5: Bounds of the source min Y in texels +// 6: Bounds of the source max X in texels +// 7: Bounds of the source max Y in texels +// 8: Color R [0.0-1.0] +// 9: Color G +// 10: Color B +// 11: Color Y func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) { if src.screen { panic("graphicscommand: the screen image cannot be the rendering source") diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 200454be3..b58052002 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -205,6 +205,16 @@ func (i *Image) Clear() { i.clear() } +// quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image. +func quadVertices(dx0, dy0, dx1, dy1, u0, v0, u1, v1, cr, cg, cb, ca float32) []float32 { + return []float32{ + dx0, dy0, u0, v0, u0, v0, u1, v1, cr, cg, cb, ca, + dx1, dy0, u1, v0, u0, v0, u1, v1, cr, cg, cb, ca, + dx0, dy1, u0, v1, u0, v0, u1, v1, cr, cg, cb, ca, + dx1, dy1, u1, v1, u0, v0, u1, v1, cr, cg, cb, ca, + } +} + // clearImage clears a graphicscommand.Image. // This does nothing to do with a restorable.Image's rendering state. func clearImage(img *graphicscommand.Image) { @@ -218,11 +228,7 @@ func clearImage(img *graphicscommand.Image) { // The rendering target size needs to be its 'internal' size instead of the exposed size to avoid glitches on // mobile platforms (See the change 1e1f309a). dw, dh := img.InternalSize() - sw, sh := emptyImage.Size() - vs := make([]float32, 4*graphics.VertexFloatNum) - graphics.PutQuadVertices(vs, emptyImage, 0, 0, sw, sh, - float32(dw)/float32(sw), 0, 0, float32(dh)/float32(sh), 0, 0, - 0, 0, 0, 0) + vs := quadVertices(0, 0, float32(dw), float32(dh), 0, 0, 1, 1, 0, 0, 0, 0) is := graphics.QuadIndices() // The first DrawTriangles must be clear mode for initialization. // TODO: Can the graphicscommand package hide this knowledge? @@ -275,11 +281,7 @@ func fillImage(i *graphicscommand.Image, clr color.RGBA) { // TODO: Integrate with clearColor dw, dh := i.InternalSize() - sw, sh := emptyImage.Size() - vs := make([]float32, 4*graphics.VertexFloatNum) - graphics.PutQuadVertices(vs, emptyImage, 0, 0, sw, sh, - float32(dw)/float32(sw), 0, 0, float32(dh)/float32(sh), 0, 0, - rf, gf, bf, af) + vs := quadVertices(0, 0, float32(dw), float32(dh), 0, 0, 1, 1, rf, gf, bf, af) is := graphics.QuadIndices() i.DrawTriangles(emptyImage.image, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressClampToZero) @@ -300,29 +302,6 @@ func (i *Image) Size() (int, int) { return i.width, i.height } -func (i *Image) PutVertex(vs []float32, dx, dy, sx, sy float32, bx0, by0, bx1, by1 float32, cr, cg, cb, ca float32) { - // Specifying a range explicitly here is redundant but this helps optimization - // to eliminate boundary checks. - // - // VertexFloatNum is better than 12 in terms of code maintenanceability, but in GopherJS, optimization - // might not work. - vs = vs[0:12] - - w, h := i.image.InternalSize() - vs[0] = dx - vs[1] = dy - vs[2] = sx / float32(w) - vs[3] = sy / float32(h) - vs[4] = bx0 / float32(w) - vs[5] = by0 / float32(h) - vs[6] = bx1 / float32(w) - vs[7] = by1 / float32(h) - vs[8] = cr - vs[9] = cg - vs[10] = cb - vs[11] = ca -} - // makeStale makes the image stale. func (i *Image) makeStale() { i.basePixels = Pixels{} @@ -395,7 +374,22 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { } } -// DrawTriangles draws a given image img to the image. +// DrawTriangles draws triangles with the given image. +// +// The vertex floats are: +// +// 0: Destination X in pixels +// 1: Destination Y in pixels +// 2: Source X in pixels (not texels!) +// 3: Source Y in pixels +// 4: Bounds of the source min X in pixels +// 5: Bounds of the source min Y in pixels +// 6: Bounds of the source max X in pixels +// 7: Bounds of the source max Y in pixels +// 8: Color R [0.0-1.0] +// 9: Color G +// 10: Color B +// 11: Color Y func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) { if i.priority { panic("restorable: DrawTriangles cannot be called on a priority image") @@ -405,6 +399,16 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, } theImages.makeStaleIfDependingOn(i) + w, h := img.image.InternalSize() + for i := 0; i < len(vertices)/graphics.VertexFloatNum; i++ { + vertices[i*graphics.VertexFloatNum+2] /= float32(w) + vertices[i*graphics.VertexFloatNum+3] /= float32(h) + vertices[i*graphics.VertexFloatNum+4] /= float32(w) + vertices[i*graphics.VertexFloatNum+5] /= float32(h) + vertices[i*graphics.VertexFloatNum+6] /= float32(w) + vertices[i*graphics.VertexFloatNum+7] /= float32(h) + } + if img.stale || img.volatile || i.screen || !needsRestoring() || i.volatile { i.makeStale() } else { diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 6d5f0e298..40f096a75 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -109,11 +109,20 @@ func TestRestoreWithoutDraw(t *testing.T) { } func quadVertices(src *Image, sw, sh, x, y int) []float32 { - vs := make([]float32, 4*graphics.VertexFloatNum) - graphics.PutQuadVertices(vs, src, 0, 0, sw, sh, - 1, 0, 0, 1, float32(x), float32(y), - 1, 1, 1, 1) - return vs + dx0 := float32(x) + dy0 := float32(y) + dx1 := float32(x + sw) + dy1 := float32(y + sh) + sx0 := float32(0) + sy0 := float32(0) + sx1 := float32(sw) + sy1 := float32(sh) + return []float32{ + dx0, dy0, sx0, sy0, sx0, sy0, sx1, sy1, 1, 1, 1, 1, + dx1, dy0, sx1, sy0, sx0, sy0, sx1, sy1, 1, 1, 1, 1, + dx0, dy1, sx0, sy1, sx0, sy0, sx1, sy1, 1, 1, 1, 1, + dx1, dy1, sx1, sy1, sx0, sy0, sx1, sy1, 1, 1, 1, 1, + } } func TestRestoreChain(t *testing.T) { diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index ebce24950..c08cc8e94 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -282,7 +282,26 @@ func (i *Image) putVertex(dst []float32, dx, dy, sx, sy float32, bx0, by0, bx1, ox, oy, _, _ := i.region() oxf, oyf := float32(ox), float32(oy) - i.backend.restorable.PutVertex(dst, dx, dy, sx+oxf, sy+oyf, bx0+oxf, by0+oyf, bx1+oxf, by1+oyf, cr, cg, cb, ca) + + // Specifying a range explicitly here is redundant but this helps optimization + // to eliminate boundary checks. + // + // VertexFloatNum is better than 12 in terms of code maintenanceability, but in GopherJS, optimization + // might not work. + vs := dst[0:12] + + vs[0] = dx + vs[1] = dy + vs[2] = sx + oxf + vs[3] = sy + oyf + vs[4] = bx0 + oxf + vs[5] = by0 + oyf + vs[6] = bx1 + oxf + vs[7] = by1 + oyf + vs[8] = cr + vs[9] = cg + vs[10] = cb + vs[11] = ca } func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) {