internal/affine: Refactoring: Make ColorM interface

This commit is contained in:
Hajime Hoshi 2021-07-27 10:37:14 +09:00
parent 5f03f4f195
commit 21aa96f9f5
17 changed files with 337 additions and 368 deletions

View File

@ -32,42 +32,49 @@ const ColorMDim = affine.ColorMDim
// //
// The initial value is identity. // The initial value is identity.
type ColorM struct { type ColorM struct {
impl *affine.ColorM impl affine.ColorM
_ [0]func() // Marks as non-comparable. _ [0]func() // Marks as non-comparable.
} }
func (c *ColorM) affineColorM() affine.ColorM {
if c.impl != nil {
return c.impl
}
return affine.ColorMIdentity{}
}
// String returns a string representation of ColorM. // String returns a string representation of ColorM.
func (c *ColorM) String() string { func (c *ColorM) String() string {
return c.impl.String() return affine.ColorMString(c.affineColorM())
} }
// Reset resets the ColorM as identity. // Reset resets the ColorM as identity.
func (c *ColorM) Reset() { func (c *ColorM) Reset() {
c.impl = nil c.impl = affine.ColorMIdentity{}
} }
// Apply pre-multiplies a vector (r, g, b, a, 1) by the matrix // Apply pre-multiplies a vector (r, g, b, a, 1) by the matrix
// where r, g, b, and a are clr's values in straight-alpha format. // where r, g, b, and a are clr's values in straight-alpha format.
// In other words, Apply calculates ColorM * (r, g, b, a, 1)^T. // In other words, Apply calculates ColorM * (r, g, b, a, 1)^T.
func (c *ColorM) Apply(clr color.Color) color.Color { func (c *ColorM) Apply(clr color.Color) color.Color {
return c.impl.Apply(clr) return c.affineColorM().Apply(clr)
} }
// Concat multiplies a color matrix with the other color matrix. // Concat multiplies a color matrix with the other color matrix.
// This is same as muptiplying the matrix other and the matrix c in this order. // This is same as muptiplying the matrix other and the matrix c in this order.
func (c *ColorM) Concat(other ColorM) { func (c *ColorM) Concat(other ColorM) {
c.impl = c.impl.Concat(other.impl) c.impl = c.affineColorM().Concat(other.impl)
} }
// Scale scales the matrix by (r, g, b, a). // Scale scales the matrix by (r, g, b, a).
func (c *ColorM) Scale(r, g, b, a float64) { func (c *ColorM) Scale(r, g, b, a float64) {
c.impl = c.impl.Scale(float32(r), float32(g), float32(b), float32(a)) c.impl = c.affineColorM().Scale(float32(r), float32(g), float32(b), float32(a))
} }
// Translate translates the matrix by (r, g, b, a). // Translate translates the matrix by (r, g, b, a).
func (c *ColorM) Translate(r, g, b, a float64) { func (c *ColorM) Translate(r, g, b, a float64) {
c.impl = c.impl.Translate(float32(r), float32(g), float32(b), float32(a)) c.impl = c.affineColorM().Translate(float32(r), float32(g), float32(b), float32(a))
} }
// RotateHue rotates the hue. // RotateHue rotates the hue.
@ -83,27 +90,27 @@ func (c *ColorM) RotateHue(theta float64) {
// //
// This conversion uses RGB to/from YCrCb conversion. // This conversion uses RGB to/from YCrCb conversion.
func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale float64) { func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale float64) {
c.impl = c.impl.ChangeHSV(hueTheta, float32(saturationScale), float32(valueScale)) c.impl = affine.ChangeHSV(c.affineColorM(), hueTheta, float32(saturationScale), float32(valueScale))
} }
// Element returns a value of a matrix at (i, j). // Element returns a value of a matrix at (i, j).
func (c *ColorM) Element(i, j int) float64 { func (c *ColorM) Element(i, j int) float64 {
return float64(c.impl.Element(i, j)) return float64(affine.ColorMElement(c.affineColorM(), i, j))
} }
// SetElement sets an element at (i, j). // SetElement sets an element at (i, j).
func (c *ColorM) SetElement(i, j int, element float64) { func (c *ColorM) SetElement(i, j int, element float64) {
c.impl = c.impl.SetElement(i, j, float32(element)) c.impl = affine.ColorMSetElement(c.affineColorM(), i, j, float32(element))
} }
// IsInvertible returns a boolean value indicating // IsInvertible returns a boolean value indicating
// whether the matrix c is invertible or not. // whether the matrix c is invertible or not.
func (c *ColorM) IsInvertible() bool { func (c *ColorM) IsInvertible() bool {
return c.impl.IsInvertible() return c.affineColorM().IsInvertible()
} }
// Invert inverts the matrix. // Invert inverts the matrix.
// If c is not invertible, Invert panics. // If c is not invertible, Invert panics.
func (c *ColorM) Invert() { func (c *ColorM) Invert() {
c.impl = c.impl.Invert() c.impl = c.affineColorM().Invert()
} }

View File

@ -19,6 +19,7 @@ import (
"image" "image"
"image/color" "image/color"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/mipmap" "github.com/hajimehoshi/ebiten/v2/internal/mipmap"
@ -213,7 +214,8 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
is := graphics.QuadIndices() is := graphics.QuadIndices()
srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap} srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, canSkipMipmap(options.GeoM, filter))
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.affineColorM(), mode, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, canSkipMipmap(options.GeoM, filter))
} }
// Vertex represents a vertex passed to DrawTriangles. // Vertex represents a vertex passed to DrawTriangles.
@ -362,7 +364,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap} srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, address, dstRegion, sr, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, options.EvenOdd, false) i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.affineColorM(), mode, filter, address, dstRegion, sr, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, options.EvenOdd, false)
} }
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader. // DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
@ -511,7 +513,8 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
} }
us := shader.convertUniforms(options.Uniforms) us := shader.convertUniforms(options.Uniforms)
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, options.EvenOdd, false)
i.mipmap.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, options.EvenOdd, false)
} }
// DrawRectShaderOptions represents options for DrawRectShader. // DrawRectShaderOptions represents options for DrawRectShader.
@ -623,7 +626,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
} }
us := shader.convertUniforms(options.Uniforms) us := shader.convertUniforms(options.Uniforms)
i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, false, canSkipMipmap(options.GeoM, driver.FilterNearest)) i.mipmap.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, driver.FilterNearest, driver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, us, false, canSkipMipmap(options.GeoM, driver.FilterNearest))
} }
// SubImage returns an image representing the portion of the image p visible through r. // SubImage returns an image representing the portion of the image p visible through r.

View File

@ -36,33 +36,41 @@ var (
} }
) )
// A ColorM represents a matrix to transform coloring when rendering an image. // ColorM represents a matrix to transform coloring when rendering an image.
// //
// A ColorM is applied to the source alpha color // ColorM is applied to the source alpha color
// while an Image's pixels' format is alpha premultiplied. // while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix, // Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again. // the color is multiplied again.
// type ColorM interface {
// The nil and initial value is identity.
type ColorM struct {
impl colorMImpl
}
type colorMImpl interface {
IsIdentity() bool IsIdentity() bool
ScaleOnly() bool ScaleOnly() bool
UnsafeScaleElements() *[4]float32 UnsafeScaleElements() *[4]float32
UnsafeElements() (*[16]float32, *[4]float32) UnsafeElements() (*[16]float32, *[4]float32)
Apply(r, g, b, a float32) color.Color Apply(clr color.Color) color.Color
// IsInvertible returns a boolean value indicating
// whether the matrix c is invertible or not.
IsInvertible() bool IsInvertible() bool
Invert() *ColorM
Equals(other *ColorM) bool // Invert inverts the matrix.
Concat(other *ColorM) *ColorM // If c is not invertible, Invert panics.
Scale(r, g, b, a float32) *ColorM Invert() ColorM
Translate(r, g, b, a float32) *ColorM
Equals(other ColorM) bool
// Concat multiplies a color matrix with the other color matrix.
// This is same as muptiplying the matrix other and the matrix c in this order.
Concat(other ColorM) ColorM
// Scale scales the matrix by (r, g, b, a).
Scale(r, g, b, a float32) ColorM
// Translate translates the matrix by (r, g, b, a).
Translate(r, g, b, a float32) ColorM
} }
func (c *ColorM) String() string { func ColorMString(c ColorM) string {
b, t := c.UnsafeElements() b, t := c.UnsafeElements()
return fmt.Sprintf("[[%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f]]", return fmt.Sprintf("[[%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f]]",
b[0], b[4], b[8], b[12], t[0], b[0], b[4], b[8], b[12], t[0],
@ -71,6 +79,8 @@ func (c *ColorM) String() string {
b[3], b[7], b[11], b[15], t[3]) b[3], b[7], b[11], b[15], t[3])
} }
type ColorMIdentity struct{}
type colorMImplScale struct { type colorMImplScale struct {
scale [4]float32 scale [4]float32
} }
@ -90,15 +100,9 @@ func clamp(x float32) float32 {
return x return x
} }
func (c *ColorM) isIdentity() bool { func (c ColorMIdentity) IsIdentity() bool {
if c == nil {
return true return true
} }
if c.impl == nil {
return true
}
return c.impl.IsIdentity()
}
func (c *colorMImplScale) IsIdentity() bool { func (c *colorMImplScale) IsIdentity() bool {
return c.scale == [4]float32{1, 1, 1, 1} return c.scale == [4]float32{1, 1, 1, 1}
@ -108,12 +112,9 @@ func (c *colorMImplBodyTranslate) IsIdentity() bool {
return c.body == colorMIdentityBody && c.translate == colorMIdentityTranslate return c.body == colorMIdentityBody && c.translate == colorMIdentityTranslate
} }
func (c *ColorM) ScaleOnly() bool { func (c ColorMIdentity) ScaleOnly() bool {
if c.isIdentity() {
return true return true
} }
return c.impl.ScaleOnly()
}
func (c *colorMImplScale) ScaleOnly() bool { func (c *colorMImplScale) ScaleOnly() bool {
return true return true
@ -164,6 +165,10 @@ func (c *colorMImplBodyTranslate) ScaleOnly() bool {
return true return true
} }
func (c ColorMIdentity) UnsafeScaleElements() *[4]float32 {
return &[...]float32{1, 1, 1, 1}
}
func (c *colorMImplScale) UnsafeScaleElements() *[4]float32 { func (c *colorMImplScale) UnsafeScaleElements() *[4]float32 {
return &c.scale return &c.scale
} }
@ -172,11 +177,7 @@ func (c *colorMImplBodyTranslate) UnsafeScaleElements() *[4]float32 {
return &[...]float32{c.body[0], c.body[5], c.body[10], c.body[15]} return &[...]float32{c.body[0], c.body[5], c.body[10], c.body[15]}
} }
func (c *ColorM) Apply(clr color.Color) color.Color { func colorToFloat32s(clr color.Color) (float32, float32, float32, float32) {
if c.isIdentity() {
return clr
}
r, g, b, a := clr.RGBA() r, g, b, a := clr.RGBA()
rf, gf, bf, af := float32(0.0), float32(0.0), float32(0.0), float32(0.0) rf, gf, bf, af := float32(0.0), float32(0.0), float32(0.0), float32(0.0)
// Unmultiply alpha // Unmultiply alpha
@ -186,10 +187,21 @@ func (c *ColorM) Apply(clr color.Color) color.Color {
bf = float32(b) / float32(a) bf = float32(b) / float32(a)
af = float32(a) / 0xffff af = float32(a) / 0xffff
} }
return c.impl.Apply(rf, gf, bf, af) return rf, gf, bf, af
} }
func (c *colorMImplScale) Apply(rf, gf, bf, af float32) color.Color { func (c ColorMIdentity) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
return color.NRGBA64{
R: uint16(rf * 0xffff),
G: uint16(gf * 0xffff),
B: uint16(bf * 0xffff),
A: uint16(af * 0xffff),
}
}
func (c *colorMImplScale) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
rf *= c.scale[0] rf *= c.scale[0]
gf *= c.scale[1] gf *= c.scale[1]
bf *= c.scale[2] bf *= c.scale[2]
@ -206,7 +218,8 @@ func (c *colorMImplScale) Apply(rf, gf, bf, af float32) color.Color {
} }
} }
func (c *colorMImplBodyTranslate) Apply(rf, gf, bf, af float32) color.Color { func (c *colorMImplBodyTranslate) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
eb := &c.body eb := &c.body
et := &c.translate et := &c.translate
rf2 := eb[0]*rf + eb[4]*gf + eb[8]*bf + eb[12]*af + et[0] rf2 := eb[0]*rf + eb[4]*gf + eb[8]*bf + eb[12]*af + et[0]
@ -225,12 +238,9 @@ func (c *colorMImplBodyTranslate) Apply(rf, gf, bf, af float32) color.Color {
} }
} }
func (c *ColorM) UnsafeElements() (*[16]float32, *[4]float32) { func (c ColorMIdentity) UnsafeElements() (*[16]float32, *[4]float32) {
if c.isIdentity() {
return &colorMIdentityBody, &colorMIdentityTranslate return &colorMIdentityBody, &colorMIdentityTranslate
} }
return c.impl.UnsafeElements()
}
func (c *colorMImplScale) UnsafeElements() (*[16]float32, *[4]float32) { func (c *colorMImplScale) UnsafeElements() (*[16]float32, *[4]float32) {
return &[...]float32{ return &[...]float32{
@ -276,14 +286,9 @@ func (c *colorMImplBodyTranslate) det() float32 {
m03*(m10*b124234-m11*b024234+m12*b014234) m03*(m10*b124234-m11*b024234+m12*b014234)
} }
// IsInvertible returns a boolean value indicating func (c ColorMIdentity) IsInvertible() bool {
// whether the matrix c is invertible or not.
func (c *ColorM) IsInvertible() bool {
if c.isIdentity() {
return true return true
} }
return c.impl.IsInvertible()
}
func (c *colorMImplScale) IsInvertible() bool { func (c *colorMImplScale) IsInvertible() bool {
return c.scale[0] != 0 && c.scale[1] != 0 && c.scale[2] != 0 && c.scale[3] != 0 return c.scale[0] != 0 && c.scale[1] != 0 && c.scale[2] != 0 && c.scale[3] != 0
@ -293,29 +298,22 @@ func (c *colorMImplBodyTranslate) IsInvertible() bool {
return c.det() != 0 return c.det() != 0
} }
// Invert inverts the matrix. func (c ColorMIdentity) Invert() ColorM {
// If c is not invertible, Invert panics. return c
func (c *ColorM) Invert() *ColorM {
if c.isIdentity() {
return nil
}
return c.impl.Invert()
} }
func (c *colorMImplScale) Invert() *ColorM { func (c *colorMImplScale) Invert() ColorM {
return &ColorM{ return &colorMImplScale{
impl: &colorMImplScale{
scale: [4]float32{ scale: [4]float32{
1 / c.scale[0], 1 / c.scale[0],
1 / c.scale[1], 1 / c.scale[1],
1 / c.scale[2], 1 / c.scale[2],
1 / c.scale[3], 1 / c.scale[3],
}, },
},
} }
} }
func (c *colorMImplBodyTranslate) Invert() *ColorM { func (c *colorMImplBodyTranslate) Invert() ColorM {
det := c.det() det := c.det()
if det == 0 { if det == 0 {
panic("affine: c is not invertible") panic("affine: c is not invertible")
@ -417,13 +415,11 @@ func (c *colorMImplBodyTranslate) Invert() *ColorM {
m.translate[2] = idet * (m00*b123134 - m01*b023134 + m02*b013134 - m03*b012134) m.translate[2] = idet * (m00*b123134 - m01*b023134 + m02*b013134 - m03*b012134)
m.translate[3] = idet * -(m00*b123124 - m01*b023124 + m02*b013124 - m03*b012124) m.translate[3] = idet * -(m00*b123124 - m01*b023124 + m02*b013124 - m03*b012124)
return &ColorM{ return m
impl: m,
}
} }
// Element returns a value of a matrix at (i, j). // ColorMElement returns a value of a matrix at (i, j).
func (c *ColorM) Element(i, j int) float32 { func ColorMElement(c ColorM, i, j int) float32 {
b, t := c.UnsafeElements() b, t := c.UnsafeElements()
if j < ColorMDim-1 { if j < ColorMDim-1 {
return b[i+j*(ColorMDim-1)] return b[i+j*(ColorMDim-1)]
@ -431,13 +427,13 @@ func (c *ColorM) Element(i, j int) float32 {
return t[i] return t[i]
} }
// SetElement sets an element at (i, j). // ColorMSetElement sets an element at (i, j).
func (c *ColorM) SetElement(i, j int, element float32) *ColorM { func ColorMSetElement(c ColorM, i, j int, element float32) ColorM {
newImpl := &colorMImplBodyTranslate{ newImpl := &colorMImplBodyTranslate{
body: colorMIdentityBody, body: colorMIdentityBody,
} }
if !c.isIdentity() { if !c.IsIdentity() {
b, t := c.impl.UnsafeElements() b, t := c.UnsafeElements()
newImpl.body = *b newImpl.body = *b
newImpl.translate = *t newImpl.translate = *t
} }
@ -446,26 +442,18 @@ func (c *ColorM) SetElement(i, j int, element float32) *ColorM {
} else { } else {
newImpl.translate[i] = element newImpl.translate[i] = element
} }
return &ColorM{ return newImpl
impl: newImpl,
}
} }
func (c *ColorM) Equals(other *ColorM) bool { func (c ColorMIdentity) Equals(other ColorM) bool {
if c.isIdentity() { return other.IsIdentity()
return other.isIdentity()
}
if other.isIdentity() {
return false
}
return c.impl.Equals(other)
} }
func (c *colorMImplScale) Equals(other *ColorM) bool { func (c *colorMImplScale) Equals(other ColorM) bool {
if !other.impl.ScaleOnly() { if !other.ScaleOnly() {
return false return false
} }
for i, s := range other.impl.UnsafeScaleElements() { for i, s := range other.UnsafeScaleElements() {
if c.scale[i] != s { if c.scale[i] != s {
return false return false
} }
@ -473,35 +461,30 @@ func (c *colorMImplScale) Equals(other *ColorM) bool {
return true return true
} }
func (c *colorMImplBodyTranslate) Equals(other *ColorM) bool { func (c *colorMImplBodyTranslate) Equals(other ColorM) bool {
lhsb, lhst := other.impl.UnsafeElements() lhsb, lhst := other.UnsafeElements()
rhsb := &c.body rhsb := &c.body
rhst := &c.translate rhst := &c.translate
return *lhsb == *rhsb && *lhst == *rhst return *lhsb == *rhsb && *lhst == *rhst
} }
// Concat multiplies a color matrix with the other color matrix. func (c ColorMIdentity) Concat(other ColorM) ColorM {
// This is same as muptiplying the matrix other and the matrix c in this order.
func (c *ColorM) Concat(other *ColorM) *ColorM {
if c.isIdentity() {
return other return other
} }
if other.isIdentity() {
func (c *colorMImplScale) Concat(other ColorM) ColorM {
if other.IsIdentity() {
return c return c
} }
return c.impl.Concat(other)
}
func (c *colorMImplScale) Concat(other *ColorM) *ColorM {
if other.ScaleOnly() { if other.ScaleOnly() {
s := other.impl.UnsafeScaleElements() s := other.UnsafeScaleElements()
return c.Scale(s[0], s[1], s[2], s[3]) return c.Scale(s[0], s[1], s[2], s[3])
} }
lhsb, lhst := other.impl.UnsafeElements() lhsb, lhst := other.UnsafeElements()
s := &c.scale s := &c.scale
return &ColorM{ return &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: [...]float32{ body: [...]float32{
lhsb[0] * s[0], lhsb[1] * s[0], lhsb[2] * s[0], lhsb[3] * s[0], lhsb[0] * s[0], lhsb[1] * s[0], lhsb[2] * s[0], lhsb[3] * s[0],
lhsb[4] * s[1], lhsb[5] * s[1], lhsb[6] * s[1], lhsb[7] * s[1], lhsb[4] * s[1], lhsb[5] * s[1], lhsb[6] * s[1], lhsb[7] * s[1],
@ -509,17 +492,19 @@ func (c *colorMImplScale) Concat(other *ColorM) *ColorM {
lhsb[12] * s[3], lhsb[13] * s[3], lhsb[14] * s[3], lhsb[15] * s[3], lhsb[12] * s[3], lhsb[13] * s[3], lhsb[14] * s[3], lhsb[15] * s[3],
}, },
translate: *lhst, translate: *lhst,
},
} }
} }
func (c *colorMImplBodyTranslate) Concat(other *ColorM) *ColorM { func (c *colorMImplBodyTranslate) Concat(other ColorM) ColorM {
lhsb, lhst := other.impl.UnsafeElements() if other.IsIdentity() {
return c
}
lhsb, lhst := other.UnsafeElements()
rhsb := &c.body rhsb := &c.body
rhst := &c.translate rhst := &c.translate
return &ColorM{ return &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
// TODO: This is a temporary hack to calculate multiply of transposed matrices. // TODO: This is a temporary hack to calculate multiply of transposed matrices.
// Fix mulSquare implmentation and swap the arguments. // Fix mulSquare implmentation and swap the arguments.
body: mulSquare(rhsb, lhsb, ColorMDim-1), body: mulSquare(rhsb, lhsb, ColorMDim-1),
@ -529,36 +514,30 @@ func (c *colorMImplBodyTranslate) Concat(other *ColorM) *ColorM {
lhsb[2]*rhst[0] + lhsb[6]*rhst[1] + lhsb[10]*rhst[2] + lhsb[14]*rhst[3] + lhst[2], lhsb[2]*rhst[0] + lhsb[6]*rhst[1] + lhsb[10]*rhst[2] + lhsb[14]*rhst[3] + lhst[2],
lhsb[3]*rhst[0] + lhsb[7]*rhst[1] + lhsb[11]*rhst[2] + lhsb[15]*rhst[3] + lhst[3], lhsb[3]*rhst[0] + lhsb[7]*rhst[1] + lhsb[11]*rhst[2] + lhsb[15]*rhst[3] + lhst[3],
}, },
},
} }
} }
// Scale scales the matrix by (r, g, b, a). func (c ColorMIdentity) Scale(r, g, b, a float32) ColorM {
func (c *ColorM) Scale(r, g, b, a float32) *ColorM {
if c.isIdentity() {
return getCachedScalingColorM(r, g, b, a) return getCachedScalingColorM(r, g, b, a)
} }
if c.ScaleOnly() {
s := c.impl.UnsafeScaleElements()
return getCachedScalingColorM(r*s[0], g*s[1], b*s[2], a*s[3])
}
return c.impl.Scale(r, g, b, a)
}
func (c *colorMImplScale) Scale(r, g, b, a float32) *ColorM { func (c *colorMImplScale) Scale(r, g, b, a float32) ColorM {
return &ColorM{ return &colorMImplScale{
impl: &colorMImplScale{
scale: [...]float32{ scale: [...]float32{
c.scale[0] * r, c.scale[0] * r,
c.scale[1] * g, c.scale[1] * g,
c.scale[2] * b, c.scale[2] * b,
c.scale[3] * a, c.scale[3] * a,
}, },
},
} }
} }
func (c *colorMImplBodyTranslate) Scale(r, g, b, a float32) *ColorM { func (c *colorMImplBodyTranslate) Scale(r, g, b, a float32) ColorM {
if c.ScaleOnly() {
s := c.UnsafeScaleElements()
return getCachedScalingColorM(r*s[0], g*s[1], b*s[2], a*s[3])
}
eb := c.body eb := c.body
for i := 0; i < ColorMDim-1; i++ { for i := 0; i < ColorMDim-1; i++ {
eb[i*(ColorMDim-1)] *= r eb[i*(ColorMDim-1)] *= r
@ -574,30 +553,21 @@ func (c *colorMImplBodyTranslate) Scale(r, g, b, a float32) *ColorM {
c.translate[3] * a, c.translate[3] * a,
} }
return &ColorM{ return &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: eb, body: eb,
translate: et, translate: et,
},
} }
} }
// Translate translates the matrix by (r, g, b, a). func (c ColorMIdentity) Translate(r, g, b, a float32) ColorM {
func (c *ColorM) Translate(r, g, b, a float32) *ColorM { return &colorMImplBodyTranslate{
if c.isIdentity() {
return &ColorM{
impl: &colorMImplBodyTranslate{
body: colorMIdentityBody, body: colorMIdentityBody,
translate: [...]float32{r, g, b, a}, translate: [...]float32{r, g, b, a},
},
} }
} }
return c.impl.Translate(r, g, b, a)
}
func (c *colorMImplScale) Translate(r, g, b, a float32) *ColorM { func (c *colorMImplScale) Translate(r, g, b, a float32) ColorM {
return &ColorM{ return &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: [...]float32{ body: [...]float32{
c.scale[0], 0, 0, 0, c.scale[0], 0, 0, 0,
0, c.scale[1], 0, 0, 0, c.scale[1], 0, 0,
@ -605,21 +575,18 @@ func (c *colorMImplScale) Translate(r, g, b, a float32) *ColorM {
0, 0, 0, c.scale[3], 0, 0, 0, c.scale[3],
}, },
translate: [...]float32{r, g, b, a}, translate: [...]float32{r, g, b, a},
},
} }
} }
func (c *colorMImplBodyTranslate) Translate(r, g, b, a float32) *ColorM { func (c *colorMImplBodyTranslate) Translate(r, g, b, a float32) ColorM {
es := c.translate es := c.translate
es[0] += r es[0] += r
es[1] += g es[1] += g
es[2] += b es[2] += b
es[3] += a es[3] += a
return &ColorM{ return &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: c.body, body: c.body,
translate: es, translate: es,
},
} }
} }
@ -629,25 +596,21 @@ var (
// Cb: [-0.5 - 0.5] // Cb: [-0.5 - 0.5]
// Cr: [-0.5 - 0.5] // Cr: [-0.5 - 0.5]
rgbToYCbCr = &ColorM{ rgbToYCbCr = &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: [...]float32{ body: [...]float32{
0.2990, -0.1687, 0.5000, 0, 0.2990, -0.1687, 0.5000, 0,
0.5870, -0.3313, -0.4187, 0, 0.5870, -0.3313, -0.4187, 0,
0.1140, 0.5000, -0.0813, 0, 0.1140, 0.5000, -0.0813, 0,
0, 0, 0, 1, 0, 0, 0, 1,
}, },
},
} }
yCbCrToRgb = &ColorM{ yCbCrToRgb = &colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: [...]float32{ body: [...]float32{
1, 1, 1, 0, 1, 1, 1, 0,
0, -0.34414, 1.77200, 0, 0, -0.34414, 1.77200, 0,
1.40200, -0.71414, 0, 0, 1.40200, -0.71414, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1,
}, },
},
} }
) )
@ -657,19 +620,17 @@ var (
// valueScale is a value to scale value (a.k.a. brightness). // valueScale is a value to scale value (a.k.a. brightness).
// //
// This conversion uses RGB to/from YCrCb conversion. // This conversion uses RGB to/from YCrCb conversion.
func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float32, valueScale float32) *ColorM { func ChangeHSV(c ColorM, hueTheta float64, saturationScale float32, valueScale float32) ColorM {
sin, cos := math.Sincos(hueTheta) sin, cos := math.Sincos(hueTheta)
s32, c32 := float32(sin), float32(cos) s32, c32 := float32(sin), float32(cos)
c = c.Concat(rgbToYCbCr) c = c.Concat(rgbToYCbCr)
c = c.Concat(&ColorM{ c = c.Concat(&colorMImplBodyTranslate{
impl: &colorMImplBodyTranslate{
body: [...]float32{ body: [...]float32{
1, 0, 0, 0, 1, 0, 0, 0,
0, c32, s32, 0, 0, c32, s32, 0,
0, -s32, c32, 0, 0, -s32, c32, 0,
0, 0, 0, 1, 0, 0, 0, 1,
}, },
},
}) })
s := saturationScale s := saturationScale
v := valueScale v := valueScale
@ -683,7 +644,7 @@ type cachedScalingColorMKey struct {
} }
type cachedScalingColorMValue struct { type cachedScalingColorMValue struct {
c *ColorM c *colorMImplScale
atime uint64 atime uint64
} }
@ -693,7 +654,7 @@ var (
cacheMonotonicClock uint64 cacheMonotonicClock uint64
) )
func getCachedScalingColorM(r, g, b, a float32) *ColorM { func getCachedScalingColorM(r, g, b, a float32) ColorM {
key := cachedScalingColorMKey{r, g, b, a} key := cachedScalingColorMKey{r, g, b, a}
cachedScalingColorMM.Lock() cachedScalingColorMM.Lock()
@ -722,11 +683,9 @@ func getCachedScalingColorM(r, g, b, a float32) *ColorM {
} }
v := &cachedScalingColorMValue{ v := &cachedScalingColorMValue{
c: &ColorM{ c: &colorMImplScale{
impl: &colorMImplScale{
scale: [...]float32{r, g, b, a}, scale: [...]float32{r, g, b, a},
}, },
},
atime: now, atime: now,
} }
cachedScalingColorM[key] = v cachedScalingColorM[key] = v

View File

@ -24,26 +24,26 @@ import (
func TestColorMScale(t *testing.T) { func TestColorMScale(t *testing.T) {
cases := []struct { cases := []struct {
In *ColorM In ColorM
Out *ColorM Out ColorM
}{ }{
{ {
nil, ColorMIdentity{},
(*ColorM)(nil).Scale(0.25, 0.5, 0.75, 1), ColorMIdentity{}.Scale(0.25, 0.5, 0.75, 1),
}, },
{ {
(*ColorM)(nil).Scale(0.5, 0.5, 0.5, 0.8), ColorMIdentity{}.Scale(0.5, 0.5, 0.5, 0.8),
(*ColorM)(nil).Scale(0.125, 0.25, 0.375, 0.8), ColorMIdentity{}.Scale(0.125, 0.25, 0.375, 0.8),
}, },
{ {
(*ColorM)(nil).Translate(0, 0, 0, 0), ColorMIdentity{}.Translate(0, 0, 0, 0),
(*ColorM)(nil).Scale(0.25, 0.5, 0.75, 1), ColorMIdentity{}.Scale(0.25, 0.5, 0.75, 1),
}, },
} }
for _, c := range cases { for _, c := range cases {
got := c.In.Scale(0.25, 0.5, 0.75, 1) got := c.In.Scale(0.25, 0.5, 0.75, 1)
want := c.Out want := c.Out
if got != want { if !got.Equals(want) {
t.Errorf("%v.Scale(): got: %v, want: %v", c.In, got, want) t.Errorf("%v.Scale(): got: %v, want: %v", c.In, got, want)
} }
} }
@ -51,51 +51,51 @@ func TestColorMScale(t *testing.T) {
func TestColorMScaleOnly(t *testing.T) { func TestColorMScaleOnly(t *testing.T) {
cases := []struct { cases := []struct {
In *ColorM In ColorM
Out bool Out bool
}{ }{
{ {
nil, ColorMIdentity{},
true, true,
}, },
{ {
(*ColorM)(nil).Translate(0, 0, 0, 0), ColorMIdentity{}.Translate(0, 0, 0, 0),
true, true,
}, },
{ {
(*ColorM)(nil).Translate(1, 0, 0, 0), ColorMIdentity{}.Translate(1, 0, 0, 0),
false, false,
}, },
{ {
(*ColorM)(nil).Translate(0, 0, 0, -1), ColorMIdentity{}.Translate(0, 0, 0, -1),
false, false,
}, },
{ {
(*ColorM)(nil).Scale(1, 1, 1, 1), ColorMIdentity{}.Scale(1, 1, 1, 1),
true, true,
}, },
{ {
(*ColorM)(nil).Scale(0, 0, 0, 0), ColorMIdentity{}.Scale(0, 0, 0, 0),
true, true,
}, },
{ {
(*ColorM)(nil).Scale(0.1, 0.2, 0.3, 0.4), ColorMIdentity{}.Scale(0.1, 0.2, 0.3, 0.4),
true, true,
}, },
{ {
(*ColorM)(nil).Scale(0.1, 0.2, 0.3, 0.4).Translate(1, 0, 0, 0), ColorMIdentity{}.Scale(0.1, 0.2, 0.3, 0.4).Translate(1, 0, 0, 0),
false, false,
}, },
{ {
(*ColorM)(nil).ChangeHSV(math.Pi/2, 0.5, 0.5), ChangeHSV(ColorMIdentity{}, math.Pi/2, 0.5, 0.5),
false, false,
}, },
{ {
(*ColorM)(nil).SetElement(0, 0, 2), ColorMSetElement(ColorMIdentity{}, 0, 0, 2),
true, true,
}, },
{ {
(*ColorM)(nil).SetElement(0, 1, 2), ColorMSetElement(ColorMIdentity{}, 0, 1, 2),
false, false,
}, },
} }
@ -109,28 +109,24 @@ func TestColorMScaleOnly(t *testing.T) {
} }
func TestColorMIsInvertible(t *testing.T) { func TestColorMIsInvertible(t *testing.T) {
m := &ColorM{} var m ColorM = ColorMIdentity{}
m = m.SetElement(1, 0, .5) m = ColorMSetElement(m, 1, 0, .5)
m = m.SetElement(1, 1, .5) m = ColorMSetElement(m, 1, 1, .5)
m = m.SetElement(1, 2, .5) m = ColorMSetElement(m, 1, 2, .5)
m = m.SetElement(1, 3, .5) m = ColorMSetElement(m, 1, 3, .5)
m = m.SetElement(1, 4, .5) m = ColorMSetElement(m, 1, 4, .5)
cidentity := &ColorM{} var cidentity ColorM = ColorMIdentity{}
cinvalid := &ColorM{} var cinvalid ColorM = ColorMIdentity{}
cinvalid = cinvalid.SetElement(0, 0, 0) cinvalid = ColorMSetElement(cinvalid, 0, 0, 0)
cinvalid = cinvalid.SetElement(1, 1, 0) cinvalid = ColorMSetElement(cinvalid, 1, 1, 0)
cinvalid = cinvalid.SetElement(2, 2, 0) cinvalid = ColorMSetElement(cinvalid, 2, 2, 0)
cinvalid = cinvalid.SetElement(3, 3, 0) cinvalid = ColorMSetElement(cinvalid, 3, 3, 0)
cases := []struct { cases := []struct {
In *ColorM In ColorM
Out bool Out bool
}{ }{
{
nil,
true,
},
{ {
cidentity, cidentity,
true, true,
@ -153,11 +149,11 @@ func TestColorMIsInvertible(t *testing.T) {
} }
} }
func arrayToColorM(es [4][5]float32) *ColorM { func arrayToColorM(es [4][5]float32) ColorM {
var a = &ColorM{} var a ColorM = ColorMIdentity{}
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
a = a.SetElement(i, j, es[i][j]) a = ColorMSetElement(a, i, j, es[i][j])
} }
} }
return a return a
@ -170,11 +166,11 @@ func abs(x float32) float32 {
return x return x
} }
func equalWithDelta(a, b *ColorM, delta float32) bool { func equalWithDelta(a, b ColorM, delta float32) bool {
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
ea := a.Element(i, j) ea := ColorMElement(a, i, j)
eb := b.Element(i, j) eb := ColorMElement(b, i, j)
if abs(ea-eb) > delta { if abs(ea-eb) > delta {
return false return false
} }
@ -185,12 +181,12 @@ func equalWithDelta(a, b *ColorM, delta float32) bool {
func TestColorMInvert(t *testing.T) { func TestColorMInvert(t *testing.T) {
cases := []struct { cases := []struct {
In *ColorM In ColorM
Out *ColorM Out ColorM
}{ }{
{ {
In: nil, In: ColorMIdentity{},
Out: nil, Out: ColorMIdentity{},
}, },
{ {
In: arrayToColorM([4][5]float32{ In: arrayToColorM([4][5]float32{
@ -207,7 +203,7 @@ func TestColorMInvert(t *testing.T) {
}), }),
}, },
{ {
In: (*ColorM)(nil).Scale(1, 2, 4, 8), In: ColorMIdentity{}.Scale(1, 2, 4, 8),
Out: arrayToColorM([4][5]float32{ Out: arrayToColorM([4][5]float32{
{1, 0, 0, 0, 0}, {1, 0, 0, 0, 0},
{0, 0.5, 0, 0, 0}, {0, 0.5, 0, 0, 0},
@ -256,8 +252,8 @@ func BenchmarkColorMInvert(b *testing.B) {
r := rand.Float32 r := rand.Float32
b.StopTimer() b.StopTimer()
var m *ColorM var m ColorM = ColorMIdentity{}
for m == nil || !m.IsInvertible() { for m.IsIdentity() || !m.IsInvertible() {
m = arrayToColorM([4][5]float32{ m = arrayToColorM([4][5]float32{
{r(), r(), r(), r(), r() * 10}, {r(), r(), r(), r(), r() * 10},
{r(), r(), r(), r(), r() * 10}, {r(), r(), r(), r(), r() * 10},
@ -274,24 +270,24 @@ func BenchmarkColorMInvert(b *testing.B) {
func TestColorMConcat(t *testing.T) { func TestColorMConcat(t *testing.T) {
cases := []struct { cases := []struct {
In0 *ColorM In0 ColorM
In1 *ColorM In1 ColorM
Out *ColorM Out ColorM
}{ }{
{ {
nil, ColorMIdentity{},
nil, ColorMIdentity{},
nil, ColorMIdentity{},
}, },
{ {
(*ColorM)(nil).Scale(1, 2, 3, 4), ColorMIdentity{}.Scale(1, 2, 3, 4),
(*ColorM)(nil).Scale(5, 6, 7, 8), ColorMIdentity{}.Scale(5, 6, 7, 8),
(*ColorM)(nil).Scale(5, 12, 21, 32), ColorMIdentity{}.Scale(5, 12, 21, 32),
}, },
{ {
(*ColorM)(nil).Scale(5, 6, 7, 8), ColorMIdentity{}.Scale(5, 6, 7, 8),
(*ColorM)(nil).Scale(1, 2, 3, 4), ColorMIdentity{}.Scale(1, 2, 3, 4),
(*ColorM)(nil).Scale(5, 12, 21, 32), ColorMIdentity{}.Scale(5, 12, 21, 32),
}, },
{ {
arrayToColorM([4][5]float32{ arrayToColorM([4][5]float32{
@ -300,7 +296,7 @@ func TestColorMConcat(t *testing.T) {
{4, 5, 1, 2, 3}, {4, 5, 1, 2, 3},
{3, 4, 5, 1, 2}, {3, 4, 5, 1, 2},
}), }),
(*ColorM)(nil).Scale(1, 2, 3, 4), ColorMIdentity{}.Scale(1, 2, 3, 4),
arrayToColorM([4][5]float32{ arrayToColorM([4][5]float32{
{1, 2, 3, 4, 5}, {1, 2, 3, 4, 5},
{10, 2, 4, 6, 8}, {10, 2, 4, 6, 8},
@ -309,7 +305,7 @@ func TestColorMConcat(t *testing.T) {
}), }),
}, },
{ {
(*ColorM)(nil).Scale(1, 2, 3, 4), ColorMIdentity{}.Scale(1, 2, 3, 4),
arrayToColorM([4][5]float32{ arrayToColorM([4][5]float32{
{1, 2, 3, 4, 5}, {1, 2, 3, 4, 5},
{5, 1, 2, 3, 4}, {5, 1, 2, 3, 4},

View File

@ -298,7 +298,7 @@ func (i *Image) ensureIsolated() {
Width: float32(w - 2*paddingSize), Width: float32(w - 2*paddingSize),
Height: float32(h - 2*paddingSize), Height: float32(h - 2*paddingSize),
} }
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false) newImg.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false)
i.dispose(false) i.dispose(false)
i.backend = &backend{ i.backend = &backend{
@ -354,7 +354,7 @@ func (i *Image) putOnAtlas() error {
Width: w, Width: w,
Height: h, Height: h,
} }
newI.drawTriangles([graphics.ShaderImageNum]*Image{i}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true) newI.drawTriangles([graphics.ShaderImageNum]*Image{i}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true)
} }
newI.moveTo(i) newI.moveTo(i)
@ -402,13 +402,13 @@ func (i *Image) processSrc(src *Image) {
// 5: Color G // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) { func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock() defer backendsM.Unlock()
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false) i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false)
} }
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, keepOnAtlas bool) { func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, keepOnAtlas bool) {
if i.disposed { if i.disposed {
panic("atlas: the drawing target image must not be disposed (DrawTriangles)") panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
} }

View File

@ -19,6 +19,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
. "github.com/hajimehoshi/ebiten/v2/internal/atlas" . "github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
@ -102,7 +103,7 @@ func TestEnsureIsolated(t *testing.T) {
Width: size, Width: size,
Height: size, Height: size,
} }
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
want := false want := false
if got := img4.IsOnAtlasForTesting(); got != want { if got := img4.IsOnAtlasForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
@ -132,7 +133,7 @@ func TestEnsureIsolated(t *testing.T) {
// Check further drawing doesn't cause panic. // Check further drawing doesn't cause panic.
// This bug was fixed by 03dcd948. // This bug was fixed by 03dcd948.
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
} }
func TestReputOnAtlas(t *testing.T) { func TestReputOnAtlas(t *testing.T) {
@ -179,7 +180,7 @@ func TestReputOnAtlas(t *testing.T) {
Width: size, Width: size,
Height: size, Height: size,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -191,7 +192,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -219,7 +220,7 @@ func TestReputOnAtlas(t *testing.T) {
} }
// img1 is on an atlas again. // img1 is on an atlas again.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), true; got != want { if got, want := img1.IsOnAtlasForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -243,7 +244,7 @@ func TestReputOnAtlas(t *testing.T) {
} }
// Use img1 as a render target again. // Use img1 as a render target again.
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -255,7 +256,7 @@ func TestReputOnAtlas(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -265,7 +266,7 @@ func TestReputOnAtlas(t *testing.T) {
} }
// img1 is not on an atlas due to ReplacePixels. // img1 is not on an atlas due to ReplacePixels.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img1.IsOnAtlasForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -275,7 +276,7 @@ func TestReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := img3.IsOnAtlasForTesting(), false; got != want { if got, want := img3.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -375,7 +376,7 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
dst.ReplacePixels(pix) dst.ReplacePixels(pix)
pix, err := dst.Pixels(0, 0, w, h) pix, err := dst.Pixels(0, 0, w, h)
@ -423,7 +424,7 @@ func TestSmallImages(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
pix, err := dst.Pixels(0, 0, w, h) pix, err := dst.Pixels(0, 0, w, h)
if err != nil { if err != nil {
@ -471,7 +472,7 @@ func TestLongImages(t *testing.T) {
Width: dstW, Width: dstW,
Height: dstH, Height: dstH,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
pix, err := dst.Pixels(0, 0, dstW, dstH) pix, err := dst.Pixels(0, 0, dstW, dstH)
if err != nil { if err != nil {
@ -559,7 +560,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
Width: size, Width: size,
Height: size, Height: size,
} }
src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src.IsOnAtlasForTesting(), false; got != want { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -569,7 +570,7 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src.IsOnAtlasForTesting(), false; got != want { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -609,7 +610,7 @@ func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
} }
// Use src2 as a rendering target, and make src2 an independent image. // Use src2 as a rendering target, and make src2 an independent image.
src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src2.IsOnAtlasForTesting(), false; got != want { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -630,7 +631,7 @@ func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
if err := PutImagesOnAtlasForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
if got, want := src2.IsOnAtlasForTesting(), false; got != want { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }

View File

@ -202,7 +202,7 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
// DrawTriangles draws the src image with the given vertices. // DrawTriangles draws the src image with the given vertices.
// //
// Copying vertices and indices is the caller's responsibility. // Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) { func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
for _, src := range srcs { for _, src := range srcs {
if i == src { if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver") panic("buffered: Image.DrawTriangles: source images must be different from the receiver")

View File

@ -58,7 +58,7 @@ type Graphics interface {
// //
// * float32 // * float32
// * []float32 // * []float32
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM *affine.ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []interface{}, evenOdd bool) error DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM affine.ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []interface{}, evenOdd bool) error
} }
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost. // GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.

View File

@ -128,7 +128,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
} }
// EnqueueDrawTrianglesCommand enqueues a drawing-image command. // EnqueueDrawTrianglesCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if len(indices) > graphics.IndicesNum { 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)) panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum))
} }
@ -317,7 +317,7 @@ type drawTrianglesCommand struct {
offsets [graphics.ShaderImageNum - 1][2]float32 offsets [graphics.ShaderImageNum - 1][2]float32
vertices []float32 vertices []float32
nindices int nindices int
color *affine.ColorM color affine.ColorM
mode driver.CompositeMode mode driver.CompositeMode
filter driver.Filter filter driver.Filter
address driver.Address address driver.Address
@ -456,7 +456,7 @@ func (c *drawTrianglesCommand) addNumIndices(n int) {
// CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c. // with the drawTrianglesCommand c.
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, color *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool { func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, evenOdd bool) bool {
// If a shader is used, commands are not merged. // If a shader is used, commands are not merged.
// //
// TODO: Merge shader commands considering uniform variables. // TODO: Merge shader commands considering uniform variables.

View File

@ -139,7 +139,7 @@ func (i *Image) InternalSize() (int, int) {
// //
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the // 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. // elements for the source image are not used.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if shader == nil { if shader == nil {
// Fast path for rendering without a shader (#1355). // Fast path for rendering without a shader (#1355).
img := srcs[0] img := srcs[0]

View File

@ -18,6 +18,7 @@ import (
"image/color" "image/color"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
. "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" . "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
@ -50,7 +51,7 @@ func TestClear(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
pix, err := dst.Pixels() pix, err := dst.Pixels()
if err != nil { if err != nil {
@ -81,8 +82,8 @@ func TestReplacePixelsPartAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
// TODO: Check the result. // TODO: Check the result.
@ -100,11 +101,11 @@ func TestShader(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff) ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff)
s := NewShader(&ir) s := NewShader(&ir)
dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, s, nil, false)
pix, err := dst.Pixels() pix, err := dst.Pixels()
if err != nil { if err != nil {

View File

@ -868,7 +868,7 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
return nil return nil
} }
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error { func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
dst := g.images[dstID] dst := g.images[dstID]
if dst.screen { if dst.screen {
@ -893,7 +893,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
} { } {
rpss[stencil] = g.rpss[rpsKey{ rpss[stencil] = g.rpss[rpsKey{
screen: dst.screen, screen: dst.screen,
useColorM: colorM != nil, useColorM: !colorM.IsIdentity(),
filter: filter, filter: filter,
address: address, address: address,
compositeMode: mode, compositeMode: mode,

View File

@ -146,7 +146,7 @@ func (g *Graphics) SetVertices(vertices []float32, indices []uint16) {
g.context.elementArrayBufferSubData(indices) g.context.elementArrayBufferSubData(indices)
} }
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM *affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error { func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM affine.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
destination := g.images[dstID] destination := g.images[dstID]
g.drawCalled = true g.drawCalled = true
@ -166,7 +166,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
var uniformVars []uniformVariable var uniformVars []uniformVariable
if shaderID == driver.InvalidShaderID { if shaderID == driver.InvalidShaderID {
program = g.state.programs[programKey{ program = g.state.programs[programKey{
useColorM: colorM != nil, useColorM: !colorM.IsIdentity(),
filter: filter, filter: filter,
address: address, address: address,
}] }]
@ -187,7 +187,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
typ: shaderir.Type{Main: shaderir.Vec4}, typ: shaderir.Type{Main: shaderir.Vec4},
}) })
if colorM != nil { if !colorM.IsIdentity() {
// ColorM's elements are immutable. It's OK to hold the reference without copying. // ColorM's elements are immutable. It's OK to hold the reference without copying.
esBody, esTranslate := colorM.UnsafeElements() esBody, esTranslate := colorM.UnsafeElements()
uniformVars = append(uniformVars, uniformVariable{ uniformVars = append(uniformVars, uniformVariable{

View File

@ -85,7 +85,7 @@ func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
return m.orig.Pixels(x, y, width, height) return m.orig.Pixels(x, y, width, height)
} }
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, canSkipMipmap bool) { func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, canSkipMipmap bool) {
if len(indices) == 0 { if len(indices) == 0 {
return return
} }
@ -123,13 +123,13 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices [
} }
} }
if colorm != nil && colorm.ScaleOnly() { if colorm.ScaleOnly() {
body, _ := colorm.UnsafeElements() body, _ := colorm.UnsafeElements()
cr := body[0] cr := body[0]
cg := body[5] cg := body[5]
cb := body[10] cb := body[10]
ca := body[15] ca := body[15]
colorm = nil colorm = affine.ColorMIdentity{}
const n = graphics.VertexFloatNum const n = graphics.VertexFloatNum
for i := 0; i < len(vertices)/n; i++ { for i := 0; i < len(vertices)/n; i++ {
vertices[i*n+4] *= cr vertices[i*n+4] *= cr
@ -226,7 +226,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
Width: float32(w2), Width: float32(w2),
Height: float32(h2), Height: float32(h2),
} }
s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false) s.DrawTriangles([graphics.ShaderImageNum]*buffered.Image{src}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, filter, driver.AddressUnsafe, dstRegion, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
m.imgs[level] = s m.imgs[level] = s
return m.imgs[level] return m.imgs[level]

View File

@ -68,7 +68,7 @@ type drawTrianglesHistoryItem struct {
offsets [graphics.ShaderImageNum - 1][2]float32 offsets [graphics.ShaderImageNum - 1][2]float32
vertices []float32 vertices []float32
indices []uint16 indices []uint16
colorm *affine.ColorM colorm affine.ColorM
mode driver.CompositeMode mode driver.CompositeMode
filter driver.Filter filter driver.Filter
address driver.Address address driver.Address
@ -187,7 +187,7 @@ func (i *Image) Extend(width, height int) *Image {
Width: float32(sw), Width: float32(sw),
Height: float32(sh), Height: float32(sh),
} }
newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) newImg.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
// Overwrite the history as if the image newImg is created only by ReplacePixels. Now drawTrianglesHistory // Overwrite the history as if the image newImg is created only by ReplacePixels. Now drawTrianglesHistory
// and basePixels cannot be mixed. // and basePixels cannot be mixed.
@ -248,7 +248,7 @@ func clearImage(i *graphicscommand.Image) {
Width: float32(dw), Width: float32(dw),
Height: float32(dh), Height: float32(dh),
} }
i.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false) i.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false)
} }
// BasePixelsForTesting returns the image's basePixels for testing. // BasePixelsForTesting returns the image's basePixels for testing.
@ -351,7 +351,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
// 5: Color G // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if i.priority { if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image") panic("restorable: DrawTriangles cannot be called on a priority image")
} }
@ -396,7 +396,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
} }
// appendDrawTrianglesHistory appends a draw-image history item to the image. // appendDrawTrianglesHistory appends a draw-image history item to the image.
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
if i.stale || i.volatile || i.screen { if i.stale || i.volatile || i.screen {
return return
} }

View File

@ -19,6 +19,7 @@ import (
"image/color" "image/color"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
. "github.com/hajimehoshi/ebiten/v2/internal/restorable" . "github.com/hajimehoshi/ebiten/v2/internal/restorable"
@ -137,7 +138,7 @@ func TestRestoreChain(t *testing.T) {
Width: 1, Width: 1,
Height: 1, Height: 1,
} }
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
} }
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
@ -185,10 +186,10 @@ func TestRestoreChain2(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) imgs[8].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[7]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) imgs[9].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[8]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) imgs[i+1].DrawTriangles([graphics.ShaderImageNum]*Image{imgs[i]}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
} }
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
@ -234,10 +235,10 @@ func TestRestoreOverrideSource(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img2.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img3.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h) img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h)
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -323,23 +324,23 @@ func TestRestoreComplexGraph(t *testing.T) {
Height: h, Height: h,
} }
var offsets [graphics.ShaderImageNum - 1][2]float32 var offsets [graphics.ShaderImageNum - 1][2]float32
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img3.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0) vs = quadVertices(w, h, 1, 0)
img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img3.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0) vs = quadVertices(w, h, 1, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 2, 0) vs = quadVertices(w, h, 2, 0)
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0) vs = quadVertices(w, h, 0, 0)
img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img5.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0) vs = quadVertices(w, h, 0, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img6.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 1, 0) vs = quadVertices(w, h, 1, 0)
img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img6.DrawTriangles([graphics.ShaderImageNum]*Image{img4}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 0, 0) vs = quadVertices(w, h, 0, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img7.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
vs = quadVertices(w, h, 2, 0) vs = quadVertices(w, h, 2, 0)
img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img7.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -437,8 +438,8 @@ func TestRestoreRecursive(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 1, 0), is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -538,7 +539,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) {
Width: 2, Width: 2,
Height: 1, Height: 1,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1) img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
@ -581,8 +582,8 @@ func TestDispose(t *testing.T) {
Width: 1, Width: 1,
Height: 1, Height: 1,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(1, 1, 0, 0), is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img1.Dispose() img1.Dispose()
if err := ResolveStaleImages(); err != nil { if err := ResolveStaleImages(); err != nil {
@ -696,7 +697,7 @@ func TestReplacePixelsOnly(t *testing.T) {
Width: 1, Width: 1,
Height: 1, Height: 1,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1) img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1)
// BasePixelsForTesting is available without GPU accessing. // BasePixelsForTesting is available without GPU accessing.
@ -756,7 +757,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being // Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
// stale. // stale.
@ -783,7 +784,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h) dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h)
// ReplacePixels for a whole image doesn't panic. // ReplacePixels for a whole image doesn't panic.
} }
@ -807,7 +808,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1)
} }
@ -884,7 +885,7 @@ func TestMutateSlices(t *testing.T) {
Width: w, Width: w,
Height: h, Height: h,
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
for i := range vs { for i := range vs {
vs[i] = 0 vs[i] = 0
} }

View File

@ -18,6 +18,7 @@ import (
"image/color" "image/color"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
. "github.com/hajimehoshi/ebiten/v2/internal/restorable" . "github.com/hajimehoshi/ebiten/v2/internal/restorable"
@ -48,7 +49,7 @@ func clearImage(img *Image, w, h int) {
Width: float32(w), Width: float32(w),
Height: float32(h), Height: float32(h),
} }
img.DrawTriangles([graphics.ShaderImageNum]*Image{emptyImage}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) img.DrawTriangles([graphics.ShaderImageNum]*Image{emptyImage}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false)
} }
func TestShader(t *testing.T) { func TestShader(t *testing.T) {