affine: Use float32 values for GeoM

float32 was slow on GopherJS, but not slow on Wasm.
This commit is contained in:
Hajime Hoshi 2018-06-17 02:15:40 +09:00
parent 4c2fc30311
commit 95561bbf6b
8 changed files with 88 additions and 70 deletions

77
geom.go
View File

@ -26,12 +26,12 @@ const GeoMDim = 3
// //
// The initial value is identity. // The initial value is identity.
type GeoM struct { type GeoM struct {
a_1 float64 // The actual 'a' value minus 1 a_1 float32 // The actual 'a' value minus 1
b float64 b float32
c float64 c float32
d_1 float64 // The actual 'd' value minus 1 d_1 float32 // The actual 'd' value minus 1
tx float64 tx float32
ty float64 ty float32
} }
// String returns a string representation of GeoM. // String returns a string representation of GeoM.
@ -52,7 +52,12 @@ func (g *GeoM) Reset() {
// Apply pre-multiplies a vector (x, y, 1) by the matrix. // Apply pre-multiplies a vector (x, y, 1) by the matrix.
// In other words, Apply calculates GeoM * (x, y, 1)^T. // In other words, Apply calculates GeoM * (x, y, 1)^T.
// The return value is x and y values of the result vector. // The return value is x and y values of the result vector.
func (g *GeoM) Apply(x, y float64) (x2, y2 float64) { func (g *GeoM) Apply(x, y float64) (float64, float64) {
x2, y2 := g.apply32(float32(x), float32(y))
return float64(x2), float64(y2)
}
func (g *GeoM) apply32(x, y float32) (x2, y2 float32) {
return (g.a_1+1)*x + g.b*y + g.tx, g.c*x + (g.d_1+1)*y + g.ty return (g.a_1+1)*x + g.b*y + g.tx, g.c*x + (g.d_1+1)*y + g.ty
} }
@ -60,17 +65,17 @@ func (g *GeoM) Apply(x, y float64) (x2, y2 float64) {
func (g *GeoM) Element(i, j int) float64 { func (g *GeoM) Element(i, j int) float64 {
switch { switch {
case i == 0 && j == 0: case i == 0 && j == 0:
return g.a_1 + 1 return float64(g.a_1) + 1
case i == 0 && j == 1: case i == 0 && j == 1:
return g.b return float64(g.b)
case i == 0 && j == 2: case i == 0 && j == 2:
return g.tx return float64(g.tx)
case i == 1 && j == 0: case i == 1 && j == 0:
return g.c return float64(g.c)
case i == 1 && j == 1: case i == 1 && j == 1:
return g.d_1 + 1 return float64(g.d_1) + 1
case i == 1 && j == 2: case i == 1 && j == 2:
return g.ty return float64(g.ty)
default: default:
panic("ebiten: i or j is out of index") panic("ebiten: i or j is out of index")
} }
@ -107,31 +112,32 @@ func (g *GeoM) Add(other GeoM) {
// Scale scales the matrix by (x, y). // Scale scales the matrix by (x, y).
func (g *GeoM) Scale(x, y float64) { func (g *GeoM) Scale(x, y float64) {
a := (g.a_1 + 1) * x a := (float64(g.a_1) + 1) * x
b := g.b * x b := float64(g.b) * x
tx := g.tx * x tx := float64(g.tx) * x
c := g.c * y c := float64(g.c) * y
d := (g.d_1 + 1) * y d := (float64(g.d_1) + 1) * y
ty := g.ty * y ty := float64(g.ty) * y
g.a_1 = a - 1 g.a_1 = float32(a) - 1
g.b = b g.b = float32(b)
g.c = c g.c = float32(c)
g.d_1 = d - 1 g.d_1 = float32(d) - 1
g.tx = tx g.tx = float32(tx)
g.ty = ty g.ty = float32(ty)
} }
// Translate translates the matrix by (tx, ty). // Translate translates the matrix by (tx, ty).
func (g *GeoM) Translate(tx, ty float64) { func (g *GeoM) Translate(tx, ty float64) {
g.tx += tx g.tx += float32(tx)
g.ty += ty g.ty += float32(ty)
} }
// Rotate rotates the matrix by theta. // Rotate rotates the matrix by theta.
// The unit is radian. // The unit is radian.
func (g *GeoM) Rotate(theta float64) { func (g *GeoM) Rotate(theta float64) {
sin, cos := math.Sincos(theta) sin64, cos64 := math.Sincos(theta)
sin, cos := float32(sin64), float32(cos64)
a := cos*(g.a_1+1) - sin*g.c a := cos*(g.a_1+1) - sin*g.c
b := cos*g.b - sin*(g.d_1+1) b := cos*g.b - sin*(g.d_1+1)
@ -148,7 +154,7 @@ func (g *GeoM) Rotate(theta float64) {
g.ty = ty g.ty = ty
} }
func (g *GeoM) det() float64 { func (g *GeoM) det() float32 {
return (g.a_1+1)*(g.d_1+1) - g.b*g.c return (g.a_1+1)*(g.d_1+1) - g.b*g.c
} }
@ -183,19 +189,20 @@ func (g *GeoM) Invert() {
// SetElement sets an element at (i, j). // SetElement sets an element at (i, j).
func (g *GeoM) SetElement(i, j int, element float64) { func (g *GeoM) SetElement(i, j int, element float64) {
e := float32(element)
switch { switch {
case i == 0 && j == 0: case i == 0 && j == 0:
g.a_1 = element - 1 g.a_1 = e - 1
case i == 0 && j == 1: case i == 0 && j == 1:
g.b = element g.b = e
case i == 0 && j == 2: case i == 0 && j == 2:
g.tx = element g.tx = e
case i == 1 && j == 0: case i == 1 && j == 0:
g.c = element g.c = e
case i == 1 && j == 1: case i == 1 && j == 1:
g.d_1 = element - 1 g.d_1 = e - 1
case i == 1 && j == 2: case i == 1 && j == 2:
g.ty = element g.ty = e
default: default:
panic("ebiten: i or j is out of index") panic("ebiten: i or j is out of index")
} }

View File

@ -258,7 +258,7 @@ func TestGeoMIsInvert(t *testing.T) {
}, },
} }
const delta = 0.00001 const delta = 0.001
for _, c := range cases { for _, c := range cases {
if c.GeoM.IsInvertible() != c.Invertible { if c.GeoM.IsInvertible() != c.Invertible {

View File

@ -126,6 +126,14 @@ func (i *Image) fill(r, g, b, a uint8) {
_ = i.DrawImage(emptyImage, op) _ = i.DrawImage(emptyImage, op)
} }
type geoM32 struct {
inner *GeoM
}
func (g geoM32) Apply(x, y float32) (x2, y2 float32) {
return g.inner.apply32(x, y)
}
// DrawImage draws the given image on the image i. // DrawImage draws the given image on the image i.
// //
// DrawImage accepts the options. For details, see the document of DrawImageOptions. // DrawImage accepts the options. For details, see the document of DrawImageOptions.
@ -211,7 +219,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
sy1 = r.Max.Y sy1 = r.Max.Y
} }
} }
geom := &options.GeoM geom := geoM32{&options.GeoM}
if sx0 < 0 || sy0 < 0 { if sx0 < 0 || sy0 < 0 {
dx := 0.0 dx := 0.0
dy := 0.0 dy := 0.0
@ -223,9 +231,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
dy = -float64(sy0) dy = -float64(sy0)
sy0 = 0 sy0 = 0
} }
geom = &GeoM{} g := &GeoM{}
geom.Translate(dx, dy) g.Translate(dx, dy)
geom.Concat(options.GeoM) g.Concat(options.GeoM)
geom = geoM32{g}
} }
mode := opengl.CompositeMode(options.CompositeMode) mode := opengl.CompositeMode(options.CompositeMode)

View File

@ -44,7 +44,7 @@ func (v *verticesBackend) sliceForOneQuad() []float32 {
} }
type GeoM interface { type GeoM interface {
Apply(x, y float64) (x2, y2 float64) Apply(x, y float32) (x2, y2 float32)
} }
func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, geom GeoM) []float32 { func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, geom GeoM) []float32 {
@ -57,8 +57,8 @@ func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, geom GeoM) []float3
vs := theVerticesBackend.sliceForOneQuad() vs := theVerticesBackend.sliceForOneQuad()
x0, y0 := 0.0, 0.0 x0, y0 := float32(0.0), float32(0.0)
x1, y1 := float64(sx1-sx0), float64(sy1-sy0) x1, y1 := float32(sx1-sx0), float32(sy1-sy0)
// it really feels like we should be able to cache this computation // it really feels like we should be able to cache this computation
// but it may not matter. // but it may not matter.
@ -73,11 +73,15 @@ func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, geom GeoM) []float3
wf := float32(w) wf := float32(w)
hf := float32(h) hf := float32(h)
u0, v0, u1, v1 := float32(sx0)/wf, float32(sy0)/hf, float32(sx1)/wf, float32(sy1)/hf u0, v0, u1, v1 := float32(sx0)/wf, float32(sy0)/hf, float32(sx1)/wf, float32(sy1)/hf
quadVerticesImpl(vs, wf, hf, u0, v0, u1, v1, x0, y0, x1, y1, geom)
return vs
}
func quadVerticesImpl(vs []float32, wf, hf, u0, v0, u1, v1, x0, y0, x1, y1 float32, geom GeoM) {
x, y := geom.Apply(x0, y0) x, y := geom.Apply(x0, y0)
// Vertex coordinates // Vertex coordinates
vs[0] = float32(x) vs[0] = x
vs[1] = float32(y) vs[1] = y
// Texture coordinates: first 2 values indicates the actual coodinate, and // Texture coordinates: first 2 values indicates the actual coodinate, and
// the second indicates diagonally opposite coodinates. // the second indicates diagonally opposite coodinates.
@ -89,28 +93,26 @@ func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, geom GeoM) []float3
// and the same for the other three coordinates // and the same for the other three coordinates
x, y = geom.Apply(x1, y0) x, y = geom.Apply(x1, y0)
vs[6] = float32(x) vs[6] = x
vs[7] = float32(y) vs[7] = y
vs[8] = u1 vs[8] = u1
vs[9] = v0 vs[9] = v0
vs[10] = u0 vs[10] = u0
vs[11] = v1 vs[11] = v1
x, y = geom.Apply(x0, y1) x, y = geom.Apply(x0, y1)
vs[12] = float32(x) vs[12] = x
vs[13] = float32(y) vs[13] = y
vs[14] = u0 vs[14] = u0
vs[15] = v1 vs[15] = v1
vs[16] = u1 vs[16] = u1
vs[17] = v0 vs[17] = v0
x, y = geom.Apply(x1, y1) x, y = geom.Apply(x1, y1)
vs[18] = float32(x) vs[18] = x
vs[19] = float32(y) vs[19] = y
vs[20] = u1 vs[20] = u1
vs[21] = v1 vs[21] = v1
vs[22] = u0 vs[22] = u0
vs[23] = v0 vs[23] = v0
return vs
} }

View File

@ -123,13 +123,13 @@ var (
) )
type geoM struct { type geoM struct {
scaleX float64 scaleX float32
scaleY float64 scaleY float32
tx float64 tx float32
ty float64 ty float32
} }
func (g *geoM) Apply(x, y float64) (float64, float64) { func (g *geoM) Apply(x, y float32) (float32, float32) {
return x*g.scaleX + g.tx, y*g.scaleY + g.ty return x*g.scaleX + g.tx, y*g.scaleY + g.ty
} }
@ -159,10 +159,10 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
w, h := dummyImage.Size() w, h := dummyImage.Size()
colorm := (*affine.ColorM)(nil).Scale(0, 0, 0, 0) colorm := (*affine.ColorM)(nil).Scale(0, 0, 0, 0)
vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, &geoM{ vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, &geoM{
scaleX: float64(width) / float64(w), scaleX: float32(width) / float32(w),
scaleY: float64(height) / float64(h), scaleY: float32(height) / float32(h),
tx: float64(x), tx: float32(x),
ty: float64(y), ty: float32(y),
}) })
i.image.DrawImage(dummyImage.image, vs, quadIndices, colorm, opengl.CompositeModeCopy, graphics.FilterNearest) i.image.DrawImage(dummyImage.image, vs, quadIndices, colorm, opengl.CompositeModeCopy, graphics.FilterNearest)
} }

View File

@ -107,7 +107,7 @@ var (
type idGeoM struct{} type idGeoM struct{}
func (idGeoM) Apply(x, y float64) (x2, y2 float64) { func (idGeoM) Apply(x, y float32) (x2, y2 float32) {
return x, y return x, y
} }
@ -263,11 +263,11 @@ func TestRestoreOverrideSource(t *testing.T) {
} }
type geoM struct { type geoM struct {
tx float64 tx float32
ty float64 ty float32
} }
func (g *geoM) Apply(x, y float64) (x2, y2 float64) { func (g *geoM) Apply(x, y float32) (x2, y2 float32) {
return x + g.tx, y + g.ty return x + g.tx, y + g.ty
} }

View File

@ -100,7 +100,7 @@ type Image struct {
type idGeoM struct{} type idGeoM struct{}
func (idGeoM) Apply(x, y float64) (x2, y2 float64) { func (idGeoM) Apply(x, y float32) (x2, y2 float32) {
return x, y return x, y
} }

View File

@ -46,11 +46,11 @@ func TestMain(m *testing.M) {
const bigSize = 2049 const bigSize = 2049
type geoM struct { type geoM struct {
tx float64 tx float32
ty float64 ty float32
} }
func (g *geoM) Apply(x, y float64) (x2, y2 float64) { func (g *geoM) Apply(x, y float32) (x2, y2 float32) {
return x + g.tx, y + g.ty return x + g.tx, y + g.ty
} }