From dfcd9fc30c28c9ac2351e73f7649624de1cb413c Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 20 Jan 2017 00:37:51 +0900 Subject: [PATCH] graphics: Make matrices faster --- colorm.go | 2 +- geom.go | 2 +- internal/affine/affine.go | 33 ------- internal/affine/affine_js.go | 54 ------------ internal/affine/affine_notjs.go | 70 --------------- internal/affine/colorm.go | 152 ++++++++++++++++++-------------- internal/affine/geom.go | 100 +++++++++++---------- internal/graphics/program.go | 2 +- vertices_js.go | 2 +- vertices_notjs.go | 2 +- 10 files changed, 144 insertions(+), 275 deletions(-) delete mode 100644 internal/affine/affine_js.go delete mode 100644 internal/affine/affine_notjs.go diff --git a/colorm.go b/colorm.go index 5c70f461f..cd6a56810 100644 --- a/colorm.go +++ b/colorm.go @@ -72,7 +72,7 @@ func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale // Element returns a value of a matrix at (i, j). func (c *ColorM) Element(i, j int) float64 { - return c.impl.Elements()[i*affine.ColorMDim+j] + return c.impl.UnsafeElements()[i*affine.ColorMDim+j] } // SetElement sets an element at (i, j). diff --git a/geom.go b/geom.go index f02329230..75f68c0b3 100644 --- a/geom.go +++ b/geom.go @@ -30,7 +30,7 @@ type GeoM struct { // Element returns a value of a matrix at (i, j). func (g *GeoM) Element(i, j int) float64 { - return g.impl.Elements()[i*affine.GeoMDim+j] + return g.impl.UnsafeElements()[i*affine.GeoMDim+j] } // Concat multiplies a geometry matrix with the other geometry matrix. diff --git a/internal/affine/affine.go b/internal/affine/affine.go index 5881278a9..69a103f01 100644 --- a/internal/affine/affine.go +++ b/internal/affine/affine.go @@ -14,39 +14,6 @@ package affine -import ( - "github.com/hajimehoshi/ebiten/internal/endian" -) - -var identityValues = map[int]string{ - ColorMDim: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {1, 0, 0, 0, 0}, - {0, 1, 0, 0, 0}, - {0, 0, 1, 0, 0}, - {0, 0, 0, 1, 0}, - }), - GeoMDim: geoMValueString([GeoMDim - 1][GeoMDim]float64{ - {1, 0, 0}, - {0, 1, 0}, - }), -} - -func uint64ToBytes(value uint64) []uint8 { - result := make([]uint8, 8) - if endian.IsLittle() { - for i := 0; i < 8; i++ { - result[i] = uint8(value) - value >>= 8 - } - } else { - for i := 7; 0 <= i; i-- { - result[i] = uint8(value) - value >>= 8 - } - } - return result -} - type affine interface { dim() int element(i, j int) float64 diff --git a/internal/affine/affine_js.go b/internal/affine/affine_js.go deleted file mode 100644 index 018938198..000000000 --- a/internal/affine/affine_js.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build js - -package affine - -import ( - "github.com/gopherjs/gopherjs/js" -) - -func elements(values string, dim int) []float64 { - if values == "" { - result := make([]float64, dim*(dim-1)) - for i := 0; i < dim-1; i++ { - result[i*dim+i] = 1 - } - return result - } - a := js.NewArrayBuffer([]uint8(values)) - return js.Global.Get("Float64Array").New(a).Interface().([]float64) -} - -func setElements(values []float64, dim int) string { - a := js.NewArrayBuffer(make([]uint8, len(values)*8)) - a8 := js.Global.Get("Uint8Array").New(a) - af64 := js.Global.Get("Float64Array").New(a) - for i, v := range values { - af64.SetIndex(i, v) - } - return string(a8.Interface().([]uint8)) -} - -func setElement(values string, dim int, i, j int, value float64) string { - if values == "" { - values = identityValues[dim] - } - a := js.NewArrayBuffer([]uint8(values)) - a8 := js.Global.Get("Uint8Array").New(a) - af64 := js.Global.Get("Float64Array").New(a) - af64.SetIndex(i*dim+j, value) - return string(a8.Interface().([]uint8)) -} diff --git a/internal/affine/affine_notjs.go b/internal/affine/affine_notjs.go deleted file mode 100644 index b41a5305b..000000000 --- a/internal/affine/affine_notjs.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !js - -package affine - -import ( - "math" - - "github.com/hajimehoshi/ebiten/internal/endian" -) - -func elements(values string, dim int) []float64 { - result := make([]float64, dim*(dim-1)) - if values == "" { - for i := 0; i < dim-1; i++ { - result[i*dim+i] = 1 - } - return result - } - if endian.IsLittle() { - for i := 0; i < len(values)/8; i++ { - v := uint64(0) - for k := 7; 0 <= k; k-- { - v <<= 8 - v += uint64(values[i*8+k]) - } - result[i] = math.Float64frombits(v) - } - } else { - for i := 0; i < len(values)/8; i++ { - v := uint64(0) - for k := 0; k < 8; k++ { - v <<= 8 - v += uint64(values[i*8+k]) - } - result[i] = math.Float64frombits(v) - } - } - return result -} - -func setElements(values []float64, dim int) string { - result := make([]uint8, len(values)*8) - for i, v := range values { - copy(result[i*8:(i+1)*8], uint64ToBytes(math.Float64bits(v))) - } - return string(result) -} - -func setElement(values string, dim int, i, j int, value float64) string { - if values == "" { - values = identityValues[dim] - } - b := uint64ToBytes(math.Float64bits(value)) - offset := 8 * (i*dim + j) - return values[:offset] + string(b) + values[offset+8:] -} diff --git a/internal/affine/colorm.go b/internal/affine/colorm.go index 3187c8863..e77703f1b 100644 --- a/internal/affine/colorm.go +++ b/internal/affine/colorm.go @@ -21,15 +21,14 @@ import ( // ColorMDim is a dimension of a ColorM. const ColorMDim = 5 -func colorMValueString(values [ColorMDim - 1][ColorMDim]float64) string { - b := make([]uint8, 0, (ColorMDim-1)*(ColorMDim)*8) - for i := 0; i < ColorMDim-1; i++ { - for j := 0; j < ColorMDim; j++ { - b = append(b, uint64ToBytes(math.Float64bits(values[i][j]))...) - } +var ( + colorMIdentityElements = []float64{ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, } - return string(b) -} +) // A ColorM represents a matrix to transform coloring when rendering an image. // @@ -40,35 +39,56 @@ func colorMValueString(values [ColorMDim - 1][ColorMDim]float64) string { // // The initial value is identity. type ColorM struct { - // When values is empty, this matrix is identity. - values string + // When elements is nil, this matrix is identity. + // elements is immutable and a new array must be created when updating. + elements []float64 } func (c *ColorM) dim() int { return ColorMDim } -func (c *ColorM) Elements() []float64 { - return elements(c.values, ColorMDim) +func (c *ColorM) UnsafeElements() []float64 { + if c.elements == nil { + c.elements = colorMIdentityElements + } + return c.elements } func (c *ColorM) element(i, j int) float64 { - return c.Elements()[i*ColorMDim+j] + if c.elements == nil { + c.elements = colorMIdentityElements + } + return c.elements[i*ColorMDim+j] } // SetElement sets an element at (i, j). func (c *ColorM) SetElement(i, j int, element float64) { - c.values = setElement(c.values, ColorMDim, i, j, element) + if c.elements == nil { + c.elements = colorMIdentityElements + } + es := make([]float64, len(c.elements)) + copy(es, c.elements) + es[i*ColorMDim+j] = element + c.elements = es } func (c *ColorM) Equals(other *ColorM) bool { - if c.values == "" { - c.values = identityValues[ColorMDim] + if c.elements == nil { + if other.elements == nil { + return true + } + c.elements = colorMIdentityElements } - if other.values == "" { - other.values = identityValues[ColorMDim] + if other.elements == nil { + other.elements = colorMIdentityElements } - return c.values == other.values + for i := range c.elements { + if c.elements[i] != other.elements[i] { + return false + } + } + return true } // Concat multiplies a color matrix with the other color matrix. @@ -88,24 +108,32 @@ func (c *ColorM) Add(other ColorM) { // Scale scales the matrix by (r, g, b, a). func (c *ColorM) Scale(r, g, b, a float64) { - v := elements(c.values, ColorMDim) - for i := 0; i < ColorMDim; i++ { - v[i] *= r - v[i+ColorMDim] *= g - v[i+ColorMDim*2] *= b - v[i+ColorMDim*3] *= a + if c.elements == nil { + c.elements = colorMIdentityElements } - c.values = setElements(v, ColorMDim) + es := make([]float64, len(c.elements)) + copy(es, c.elements) + for i := 0; i < ColorMDim; i++ { + es[i] *= r + es[i+ColorMDim] *= g + es[i+ColorMDim*2] *= b + es[i+ColorMDim*3] *= a + } + c.elements = es } // Translate translates the matrix by (r, g, b, a). func (c *ColorM) Translate(r, g, b, a float64) { - v := elements(c.values, ColorMDim) - v[4] += r - v[4+ColorMDim] += g - v[4+ColorMDim*2] += b - v[4+ColorMDim*3] += a - c.values = setElements(v, ColorMDim) + if c.elements == nil { + c.elements = colorMIdentityElements + } + es := make([]float64, len(c.elements)) + copy(es, c.elements) + es[4] += r + es[4+ColorMDim] += g + es[4+ColorMDim*2] += b + es[4+ColorMDim*3] += a + c.elements = es } // RotateHue rotates the hue. @@ -120,24 +148,24 @@ var ( // Cr: [-0.5 - 0.5] rgbToYCbCr = ColorM{ - values: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {0.2990, 0.5870, 0.1140, 0, 0}, - {-0.1687, -0.3313, 0.5000, 0, 0}, - {0.5000, -0.4187, -0.0813, 0, 0}, - {0, 0, 0, 1, 0}, - }), + elements: []float64{ + 0.2990, 0.5870, 0.1140, 0, 0, + -0.1687, -0.3313, 0.5000, 0, 0, + 0.5000, -0.4187, -0.0813, 0, 0, + 0, 0, 0, 1, 0, + }, } yCbCrToRgb = ColorM{ - values: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {1, 0, 1.40200, 0, 0}, - {1, -0.34414, -0.71414, 0, 0}, - {1, 1.77200, 0, 0, 0}, - {0, 0, 0, 1, 0}, - }), + elements: []float64{ + 1, 0, 1.40200, 0, 0, + 1, -0.34414, -0.71414, 0, 0, + 1, 1.77200, 0, 0, 0, + 0, 0, 0, 1, 0, + }, } ) -// ChangeHSV changes HSV (Hue-Saturation-Value) values. +// ChangeHSV changes HSV (Hue-Saturation-Value) elements. // hueTheta is a radian value to ratate hue. // saturationScale is a value to scale saturation. // valueScale is a value to scale value (a.k.a. brightness). @@ -147,12 +175,12 @@ func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale sin, cos := math.Sincos(hueTheta) c.Concat(rgbToYCbCr) c.Concat(ColorM{ - values: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {1, 0, 0, 0, 0}, - {0, cos, -sin, 0, 0}, - {0, sin, cos, 0, 0}, - {0, 0, 0, 1, 0}, - }), + elements: []float64{ + 1, 0, 0, 0, 0, + 0, cos, -sin, 0, 0, + 0, sin, cos, 0, 0, + 0, 0, 0, 1, 0, + }, }) s := saturationScale v := valueScale @@ -173,26 +201,16 @@ func Monochrome() ColorM { // ScaleColor is deprecated as of 1.2.0-alpha. Use Scale instead. func ScaleColor(r, g, b, a float64) ColorM { - return ColorM{ - values: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {r, 0, 0, 0, 0}, - {0, g, 0, 0, 0}, - {0, 0, b, 0, 0}, - {0, 0, 0, a, 0}, - }), - } + c := ColorM{} + c.Scale(r, g, b, a) + return c } // TranslateColor is deprecated as of 1.2.0-alpha. Use Translate instead. func TranslateColor(r, g, b, a float64) ColorM { - return ColorM{ - values: colorMValueString([ColorMDim - 1][ColorMDim]float64{ - {1, 0, 0, 0, r}, - {0, 1, 0, 0, g}, - {0, 0, 1, 0, b}, - {0, 0, 0, 1, a}, - }), - } + c := ColorM{} + c.Translate(r, g, b, a) + return c } // RotateHue is deprecated as of 1.2.0-alpha. Use RotateHue member function instead. diff --git a/internal/affine/geom.go b/internal/affine/geom.go index 7099b4439..1bf651d02 100644 --- a/internal/affine/geom.go +++ b/internal/affine/geom.go @@ -21,39 +21,49 @@ import ( // GeoMDim is a dimension of a GeoM. const GeoMDim = 3 -func geoMValueString(values [GeoMDim - 1][GeoMDim]float64) string { - b := make([]uint8, 0, (GeoMDim-1)*(GeoMDim)*8) - for i := 0; i < GeoMDim-1; i++ { - for j := 0; j < GeoMDim; j++ { - b = append(b, uint64ToBytes(math.Float64bits(values[i][j]))...) - } +var ( + geoMIdentityElements = []float64{ + 1, 0, 0, + 0, 1, 0, } - return string(b) -} +) // A GeoM represents a matrix to transform geometry when rendering an image. // // The initial value is identity. type GeoM struct { - // When values is empty, this matrix is identity. - values string + // When elements is empty, this matrix is identity. + // elements is immutable and a new array must be created when updating. + elements []float64 } func (g *GeoM) dim() int { return GeoMDim } -func (g *GeoM) Elements() []float64 { - return elements(g.values, GeoMDim) +func (g *GeoM) UnsafeElements() []float64 { + if g.elements == nil { + g.elements = geoMIdentityElements + } + return g.elements } func (g *GeoM) element(i, j int) float64 { - return g.Elements()[i*GeoMDim+j] + if g.elements == nil { + g.elements = geoMIdentityElements + } + return g.elements[i*GeoMDim+j] } // SetElement sets an element at (i, j). func (g *GeoM) SetElement(i, j int, element float64) { - g.values = setElement(g.values, GeoMDim, i, j, element) + if g.elements == nil { + g.elements = geoMIdentityElements + } + es := make([]float64, len(g.elements)) + copy(es, g.elements) + es[i*GeoMDim+j] = element + g.elements = es } // Concat multiplies a geometry matrix with the other geometry matrix. @@ -73,60 +83,58 @@ func (g *GeoM) Add(other GeoM) { // Scale scales the matrix by (x, y). func (g *GeoM) Scale(x, y float64) { - v := elements(g.values, GeoMDim) - for i := 0; i < GeoMDim; i++ { - v[i] *= x - v[i+GeoMDim] *= y + if g.elements == nil { + g.elements = geoMIdentityElements } - g.values = setElements(v, GeoMDim) + es := make([]float64, len(g.elements)) + copy(es, g.elements) + for i := 0; i < GeoMDim; i++ { + es[i] *= x + es[i+GeoMDim] *= y + } + g.elements = es } // Translate translates the matrix by (x, y). func (g *GeoM) Translate(tx, ty float64) { - v := elements(g.values, GeoMDim) - v[2] += tx - v[2+GeoMDim] += ty - g.values = setElements(v, GeoMDim) + if g.elements == nil { + g.elements = geoMIdentityElements + } + es := make([]float64, len(g.elements)) + copy(es, g.elements) + es[2] += tx + es[2+GeoMDim] += ty + g.elements = es } // Rotate rotates the matrix by theta. func (g *GeoM) Rotate(theta float64) { sin, cos := math.Sincos(theta) g.Concat(GeoM{ - values: geoMValueString([GeoMDim - 1][GeoMDim]float64{ - {cos, -sin, 0}, - {sin, cos, 0}, - }), + elements: []float64{ + cos, -sin, 0, + sin, cos, 0, + }, }) } // ScaleGeo is deprecated as of 1.2.0-alpha. Use Scale instead. func ScaleGeo(x, y float64) GeoM { - return GeoM{ - values: geoMValueString([GeoMDim - 1][GeoMDim]float64{ - {x, 0, 0}, - {0, y, 0}, - }), - } + g := GeoM{} + g.Scale(x, y) + return g } // TranslateGeo is deprecated as of 1.2.0-alpha. Use Translate instead. func TranslateGeo(tx, ty float64) GeoM { - return GeoM{ - values: geoMValueString([GeoMDim - 1][GeoMDim]float64{ - {1, 0, tx}, - {0, 1, ty}, - }), - } + g := GeoM{} + g.Translate(tx, ty) + return g } // RotateGeo is deprecated as of 1.2.0-alpha. Use Rotate instead. func RotateGeo(theta float64) GeoM { - sin, cos := math.Sincos(theta) - return GeoM{ - values: geoMValueString([GeoMDim - 1][GeoMDim]float64{ - {cos, -sin, 0}, - {sin, cos, 0}, - }), - } + g := GeoM{} + g.Rotate(theta) + return g } diff --git a/internal/graphics/program.go b/internal/graphics/program.go index 7232a0b6c..b5a25f3a5 100644 --- a/internal/graphics/program.go +++ b/internal/graphics/program.go @@ -231,7 +231,7 @@ func (p *programContext) begin() error { } e := [4][5]float32{} - es := p.colorM.Elements() + es := p.colorM.UnsafeElements() for i := 0; i < 4; i++ { for j := 0; j < 5; j++ { e[i][j] = float32(es[i*affine.ColorMDim+j]) diff --git a/vertices_js.go b/vertices_js.go index 0b8977fe7..d7b9ce78a 100644 --- a/vertices_js.go +++ b/vertices_js.go @@ -28,7 +28,7 @@ func vertices(parts ImageParts, width, height int, geo *affine.GeoM) []float32 { totalSize := graphics.QuadVertexSizeInBytes() / 4 l := parts.Len() vs := js.Global.Get("Float32Array").New(l * totalSize) - g := geo.Elements() + g := geo.UnsafeElements() g0 := g[0] g1 := g[1] g2 := g[3] diff --git a/vertices_notjs.go b/vertices_notjs.go index 2689321e6..44fbc10a9 100644 --- a/vertices_notjs.go +++ b/vertices_notjs.go @@ -26,7 +26,7 @@ func vertices(parts ImageParts, width, height int, geo *affine.GeoM) []float32 { totalSize := graphics.QuadVertexSizeInBytes() / 4 l := parts.Len() vs := make([]float32, l*totalSize) - g := geo.Elements() + g := geo.UnsafeElements() g0 := float32(g[0]) g1 := float32(g[1]) g2 := float32(g[3])