updated ebiten version from 2.7.9 to 2.9.9
This commit is contained in:
+513
-75
@@ -18,6 +18,11 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/affine"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
@@ -25,6 +30,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
@@ -43,19 +49,56 @@ type Image struct {
|
||||
// tmpVertices must not be reused until ui.Image.Draw* is called.
|
||||
tmpVertices []float32
|
||||
|
||||
// tmpIndices must not be reused until ui.Image.Draw* is called.
|
||||
tmpIndices []uint32
|
||||
|
||||
// tmpUniforms must not be reused until ui.Image.Draw* is called.
|
||||
tmpUniforms []uint32
|
||||
|
||||
// subImageCache is a cache for sub-images.
|
||||
// subImageCache is valid only when the image is not a sub-image.
|
||||
subImageCache map[image.Rectangle]*Image
|
||||
|
||||
// subImageGCLastTick is the last tick when old sub images are removed from the cache.
|
||||
subImageGCLastTick int64
|
||||
|
||||
// subImageCacheM is a mutex for subImageCache.
|
||||
// subImageCache can be accessed from the image and its sub-images at the same time,
|
||||
// so the map must be protected by a mutex.
|
||||
subImageCacheM sync.Mutex
|
||||
|
||||
// atime is the last access time.
|
||||
// atime needs to be an atomic value since a sub-image atime can be accessed from its original image.
|
||||
atime atomic.Int64
|
||||
|
||||
// usageCallbacks are callbacks that are invoked when the image is used.
|
||||
// usageCallbacks is valid only when the image is not a sub-image.
|
||||
usageCallbacks map[int64]usageCallback
|
||||
|
||||
// inUsageCallbacks reports whether the image is in usageCallbacks.
|
||||
inUsageCallbacks atomic.Bool
|
||||
|
||||
// usageCallbacksM is a mutex for usageCallbacks.
|
||||
usageCallbacksM sync.Mutex
|
||||
|
||||
// Do not add a 'buffering' member that are resolved lazily.
|
||||
// This tends to forget resolving the buffer easily (#2362).
|
||||
}
|
||||
|
||||
type usageCallback struct {
|
||||
fn func(image *Image)
|
||||
}
|
||||
|
||||
func (i *Image) copyCheck() {
|
||||
if i.addr != i {
|
||||
panic("ebiten: illegal use of non-zero Image copied by value")
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Image) updateAccessTime() {
|
||||
i.atime.Store(Tick())
|
||||
}
|
||||
|
||||
// Size returns the size of the image.
|
||||
//
|
||||
// Deprecated: as of v2.5. Use Bounds().Dx() and Bounds().Dy() or Bounds().Size() instead.
|
||||
@@ -88,6 +131,10 @@ func (i *Image) Fill(clr color.Color) {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
i.updateAccessTime()
|
||||
|
||||
var crf, cgf, cbf, caf float32
|
||||
cr, cg, cb, ca := clr.RGBA()
|
||||
crf = float32(cr) / 0xffff
|
||||
@@ -97,11 +144,11 @@ func (i *Image) Fill(clr color.Color) {
|
||||
i.image.Fill(crf, cgf, cbf, caf, i.adjustedBounds())
|
||||
}
|
||||
|
||||
func canSkipMipmap(geom GeoM, filter builtinshader.Filter) bool {
|
||||
func canSkipMipmap(det float32, filter builtinshader.Filter) bool {
|
||||
if filter != builtinshader.FilterLinear {
|
||||
return true
|
||||
}
|
||||
return geom.det2x2() >= 0.999
|
||||
return math.Abs(float64(det)) >= 0.999
|
||||
}
|
||||
|
||||
// DrawImageOptions represents options for DrawImage.
|
||||
@@ -139,6 +186,16 @@ type DrawImageOptions struct {
|
||||
// Filter is a type of texture filter.
|
||||
// The default (zero) value is FilterNearest.
|
||||
Filter Filter
|
||||
|
||||
// DisableMipmaps disables mipmaps.
|
||||
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
|
||||
// Mipmap is useful to render a shrunk image with high quality.
|
||||
// However, mipmaps can be expensive, especially on mobiles.
|
||||
// When DisableMipmaps is true, mipmap is not used.
|
||||
// When Filter is not FilterLinear, DisableMipmaps is ignored.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
DisableMipmaps bool
|
||||
}
|
||||
|
||||
// adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate.
|
||||
@@ -220,6 +277,12 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
img.invokeUsageCallbacks()
|
||||
|
||||
i.updateAccessTime()
|
||||
img.updateAccessTime()
|
||||
|
||||
if options == nil {
|
||||
options = &DrawImageOptions{}
|
||||
}
|
||||
@@ -237,6 +300,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
geoM.Translate(float64(offsetX), float64(offsetY))
|
||||
}
|
||||
a, b, c, d, tx, ty := geoM.elements32()
|
||||
det := a*d - b*c
|
||||
if det == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
sx0, sy0 := img.adjustPosition(bounds.Min.X, bounds.Min.Y)
|
||||
@@ -244,10 +311,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM())
|
||||
cr, cg, cb, ca = options.ColorScale.apply(cr, cg, cb, ca)
|
||||
vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount)
|
||||
graphics.QuadVertices(vs, float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca)
|
||||
graphics.QuadVerticesFromSrcAndMatrix(vs, float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca)
|
||||
is := graphics.QuadIndices()
|
||||
|
||||
srcs := [graphics.ShaderImageCount]*ui.Image{img.image}
|
||||
srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
|
||||
|
||||
useColorM := !colorm.IsIdentity()
|
||||
shader := builtinShader(filter, builtinshader.AddressUnsafe, useColorM)
|
||||
@@ -262,7 +329,36 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, canSkipMipmap(geoM, filter), false)
|
||||
dr := i.adjustedBounds()
|
||||
hint := restorable.HintNone
|
||||
if overwritesDstRegion(options.Blend, dr, geoM, sx0, sy0, sx1, sy1) {
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
|
||||
skipMipmap := options.DisableMipmaps
|
||||
if !skipMipmap {
|
||||
skipMipmap = canSkipMipmap(det, filter)
|
||||
}
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, skipMipmap, false, hint)
|
||||
}
|
||||
|
||||
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
|
||||
func overwritesDstRegion(blend Blend, dstRegion image.Rectangle, geoM GeoM, sx0, sy0, sx1, sy1 int) bool {
|
||||
// TODO: More precisely, BlendFactorDestinationRGB, BlendFactorDestinationAlpha, and operations should be checked.
|
||||
if blend != BlendCopy && blend != BlendClear {
|
||||
return false
|
||||
}
|
||||
// Check the result vertices is not a rotated rectangle.
|
||||
if geoM.b != 0 || geoM.c != 0 {
|
||||
return false
|
||||
}
|
||||
// Check the result vertices completely covers dstRegion.
|
||||
x0, y0 := geoM.Apply(float64(sx0), float64(sy0))
|
||||
x1, y1 := geoM.Apply(float64(sx1), float64(sy1))
|
||||
if float64(dstRegion.Min.X) < x0 || float64(dstRegion.Min.Y) < y0 || float64(dstRegion.Max.X) > x1 || float64(dstRegion.Max.Y) > y1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Vertex represents a vertex passed to DrawTriangles.
|
||||
@@ -274,6 +370,10 @@ type Vertex struct {
|
||||
// SrcX and SrcY represents a point on a source image.
|
||||
// Be careful that SrcX/SrcY coordinates are on the image's bounds.
|
||||
// This means that an upper-left point of a sub-image might not be (0, 0).
|
||||
//
|
||||
// Before passing vertices to a Kage shader, SrcX/SrcY are converted to texture coordinates of the first image,
|
||||
// which is DrawRectShaderOptions.Image[0] or DrawTrianglesShaderOptions.Images[0].
|
||||
// If the image is nil, SrcX/SrcY are not converted and used as-is.
|
||||
SrcX float32
|
||||
SrcY float32
|
||||
|
||||
@@ -291,8 +391,20 @@ type Vertex struct {
|
||||
ColorG float32
|
||||
ColorB float32
|
||||
ColorA float32
|
||||
|
||||
// Custom0/Custom1/Custom2/Custom3 represents general-purpose values passed to the shader.
|
||||
// In order to use them, Fragment must have an additional vec4 argument.
|
||||
//
|
||||
// These values are valid only when DrawTrianglesShader is used.
|
||||
// In other cases, these values are ignored.
|
||||
Custom0 float32
|
||||
Custom1 float32
|
||||
Custom2 float32
|
||||
Custom3 float32
|
||||
}
|
||||
|
||||
var _ [0]byte = [unsafe.Sizeof(Vertex{}) - unsafe.Sizeof(float32(0))*graphics.VertexFloatCount]byte{}
|
||||
|
||||
// Address represents a sampler address mode.
|
||||
type Address int
|
||||
|
||||
@@ -308,19 +420,46 @@ const (
|
||||
)
|
||||
|
||||
// FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader).
|
||||
//
|
||||
// Deprecated: as of v2.9.
|
||||
type FillRule int
|
||||
|
||||
const (
|
||||
// FillRuleFillAll indicates all the triangles are rendered regardless of overlaps.
|
||||
//
|
||||
// Deprecated: as of v2.9.
|
||||
FillRuleFillAll FillRule = FillRule(graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// FillRuleNonZero means that triangles are rendered based on the non-zero rule.
|
||||
// If and only if the number of overlaps is not 0, the region is rendered.
|
||||
//
|
||||
// Deprecated: as of v2.9.
|
||||
FillRuleNonZero FillRule = FillRule(graphicsdriver.FillRuleNonZero)
|
||||
|
||||
// FillRuleEvenOdd means that triangles are rendered based on the even-odd rule.
|
||||
// If and only if the number of overlaps is odd, the region is rendered.
|
||||
//
|
||||
// Deprecated: as of v2.9.
|
||||
FillRuleEvenOdd FillRule = FillRule(graphicsdriver.FillRuleEvenOdd)
|
||||
)
|
||||
|
||||
const (
|
||||
// FillAll indicates all the triangles are rendered regardless of overlaps.
|
||||
FillAll FillRule = FillRule(graphicsdriver.FillAll)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleFillAll instead.
|
||||
FillAll = FillRuleFillAll
|
||||
|
||||
// NonZero means that triangles are rendered based on the non-zero rule.
|
||||
// If and only if the number of overlaps is not 0, the region is rendered.
|
||||
NonZero FillRule = FillRule(graphicsdriver.NonZero)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleNonZero instead.
|
||||
NonZero = FillRuleNonZero
|
||||
|
||||
// EvenOdd means that triangles are rendered based on the even-odd rule.
|
||||
// If and only if the number of overlaps is odd, the region is rendered.
|
||||
EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleEvenOdd instead.
|
||||
EvenOdd = FillRuleEvenOdd
|
||||
)
|
||||
|
||||
// ColorScaleMode is the mode of color scales in vertices.
|
||||
@@ -371,11 +510,13 @@ type DrawTrianglesOptions struct {
|
||||
|
||||
// FillRule indicates the rule how an overlapped region is rendered.
|
||||
//
|
||||
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
|
||||
// The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
|
||||
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
|
||||
// See examples/vector for actual usages.
|
||||
//
|
||||
// The default (zero) value is FillAll.
|
||||
// The default (zero) value is FillRuleFillAll.
|
||||
//
|
||||
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
|
||||
FillRule FillRule
|
||||
|
||||
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||
@@ -384,8 +525,20 @@ type DrawTrianglesOptions struct {
|
||||
// AntiAlias increases internal draw calls and might affect performance.
|
||||
// Use the build tag `ebitenginedebug` to check the number of draw calls if you care.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
// The default (zero) value is false.//
|
||||
//
|
||||
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
|
||||
AntiAlias bool
|
||||
|
||||
// DisableMipmaps disables mipmaps.
|
||||
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
|
||||
// Mipmap is useful to render a shrunk image with high quality.
|
||||
// However, mipmaps can be expensive, especially on mobiles.
|
||||
// When DisableMipmaps is true, mipmap is not used.
|
||||
// When Filter is not FilterLinear, DisableMipmaps is ignored.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
DisableMipmaps bool
|
||||
}
|
||||
|
||||
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
|
||||
@@ -428,6 +581,36 @@ const MaxVertexCount = graphicscommand.MaxVertexCount
|
||||
//
|
||||
// When the image i is disposed, DrawTriangles does nothing.
|
||||
func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) {
|
||||
is := i.ensureTmpIndices(len(indices))
|
||||
for i := range is {
|
||||
is[i] = uint32(indices[i])
|
||||
}
|
||||
i.DrawTriangles32(vertices, is, img, options)
|
||||
}
|
||||
|
||||
// DrawTriangles32 draws triangles with the specified vertices and their indices.
|
||||
// DrawTriangles32 is the version of DrawTriangles with uint32 indices.
|
||||
//
|
||||
// img is used as a source image. img cannot be nil.
|
||||
// If you want to draw triangles with a solid color, use a small white image
|
||||
// and adjust the color elements in the vertices. For an actual implementation,
|
||||
// see the example 'vector'.
|
||||
//
|
||||
// Vertex contains color values, which are interpreted as straight-alpha colors by default.
|
||||
// This depends on the option's ColorScaleMode.
|
||||
//
|
||||
// If len(vertices) is more than MaxVertexCount, the exceeding part is ignored.
|
||||
//
|
||||
// If len(indices) is not multiple of 3, DrawTriangles32 panics.
|
||||
//
|
||||
// If a value in indices is out of range of vertices, or not less than MaxVertexCount, DrawTriangles32 panics.
|
||||
//
|
||||
// The rule in which DrawTriangles32 works effectively is same as DrawImage's.
|
||||
//
|
||||
// When the given image is disposed, DrawTriangles32 panics.
|
||||
//
|
||||
// When the image i is disposed, DrawTriangles32 does nothing.
|
||||
func (i *Image) DrawTriangles32(vertices []Vertex, indices []uint32, img *Image, options *DrawTrianglesOptions) {
|
||||
i.copyCheck()
|
||||
|
||||
if img != nil && img.isDisposed() {
|
||||
@@ -437,6 +620,16 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
return
|
||||
}
|
||||
|
||||
if len(indices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
img.invokeUsageCallbacks()
|
||||
|
||||
img.updateAccessTime()
|
||||
i.updateAccessTime()
|
||||
|
||||
if len(vertices) > graphicscommand.MaxVertexCount {
|
||||
// The last part cannot be specified by indices. Just omit them.
|
||||
vertices = vertices[:graphicscommand.MaxVertexCount]
|
||||
@@ -469,38 +662,40 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||
dst := i
|
||||
if options.ColorScaleMode == ColorScaleModeStraightAlpha {
|
||||
for i, v := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
||||
vs[i*graphics.VertexFloatCount] = dx
|
||||
vs[i*graphics.VertexFloatCount+1] = dy
|
||||
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
|
||||
vs[i*graphics.VertexFloatCount+2] = sx
|
||||
vs[i*graphics.VertexFloatCount+3] = sy
|
||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR * v.ColorA * cr
|
||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG * v.ColorA * cg
|
||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb
|
||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
|
||||
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||
for i := range vertices {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
vs[0] = dx
|
||||
vs[1] = dy
|
||||
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
|
||||
vs[2] = sx
|
||||
vs[3] = sy
|
||||
vs[4] = vertices[i].ColorR * vertices[i].ColorA * cr
|
||||
vs[5] = vertices[i].ColorG * vertices[i].ColorA * cg
|
||||
vs[6] = vertices[i].ColorB * vertices[i].ColorA * cb
|
||||
vs[7] = vertices[i].ColorA * ca
|
||||
}
|
||||
} else {
|
||||
for i, v := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
||||
vs[i*graphics.VertexFloatCount] = dx
|
||||
vs[i*graphics.VertexFloatCount+1] = dy
|
||||
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
|
||||
vs[i*graphics.VertexFloatCount+2] = sx
|
||||
vs[i*graphics.VertexFloatCount+3] = sy
|
||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR * cr
|
||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG * cg
|
||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB * cb
|
||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
|
||||
// See comment above (#3103).
|
||||
for i := range vertices {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
vs[0] = dx
|
||||
vs[1] = dy
|
||||
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
|
||||
vs[2] = sx
|
||||
vs[3] = sy
|
||||
vs[4] = vertices[i].ColorR * cr
|
||||
vs[5] = vertices[i].ColorG * cg
|
||||
vs[6] = vertices[i].ColorB * cb
|
||||
vs[7] = vertices[i].ColorA * ca
|
||||
}
|
||||
}
|
||||
is := make([]uint32, len(indices))
|
||||
for i := range is {
|
||||
is[i] = uint32(indices[i])
|
||||
}
|
||||
|
||||
srcs := [graphics.ShaderImageCount]*ui.Image{img.image}
|
||||
srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
|
||||
|
||||
useColorM := !colorm.IsIdentity()
|
||||
shader := builtinShader(filter, address, useColorM)
|
||||
@@ -515,7 +710,11 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
})
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias)
|
||||
skipMipmap := options.DisableMipmaps
|
||||
if !skipMipmap {
|
||||
skipMipmap = filter != builtinshader.FilterLinear
|
||||
}
|
||||
i.image.DrawTriangles(srcs, vs, indices, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), skipMipmap, options.AntiAlias, restorable.HintNone)
|
||||
}
|
||||
|
||||
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
||||
@@ -533,7 +732,7 @@ type DrawTrianglesShaderOptions struct {
|
||||
|
||||
// Uniforms is a set of uniform variables for the shader.
|
||||
// The keys are the names of the uniform variables.
|
||||
// The values must be a numeric type, or a slice or an array of a numeric type.
|
||||
// The values must be a numeric/boolean type, or a slice or an array of a numeric/boolean type.
|
||||
// If the uniform variable type is an array, a vector or a matrix,
|
||||
// you have to specify linearly flattened values as a slice or an array.
|
||||
// For example, if the uniform variable type is [4]vec4, the length will be 16.
|
||||
@@ -542,16 +741,19 @@ type DrawTrianglesShaderOptions struct {
|
||||
Uniforms map[string]any
|
||||
|
||||
// Images is a set of the source images.
|
||||
// All the images' sizes must be the same.
|
||||
// In the texel mode, all the image sizes must be the same.
|
||||
// The pixel mode allows images of different sizes.
|
||||
Images [4]*Image
|
||||
|
||||
// FillRule indicates the rule how an overlapped region is rendered.
|
||||
//
|
||||
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
|
||||
// The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
|
||||
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
|
||||
// See examples/vector for actual usages.
|
||||
//
|
||||
// The default (zero) value is FillAll.
|
||||
// The default (zero) value is FillRuleFillAll.
|
||||
//
|
||||
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
|
||||
FillRule FillRule
|
||||
|
||||
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||
@@ -561,11 +763,13 @@ type DrawTrianglesShaderOptions struct {
|
||||
// Use the build tag `ebitenginedebug` to check the number of draw calls if you care.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
//
|
||||
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
|
||||
AntiAlias bool
|
||||
}
|
||||
|
||||
// Check the number of images.
|
||||
var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]struct{} = [0]struct{}{}
|
||||
var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderSrcImageCount]struct{} = [0]struct{}{}
|
||||
|
||||
// DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
|
||||
//
|
||||
@@ -592,6 +796,39 @@ var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]stru
|
||||
//
|
||||
// When the image i is disposed, DrawTrianglesShader does nothing.
|
||||
func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
|
||||
is := i.ensureTmpIndices(len(indices))
|
||||
for i := range is {
|
||||
is[i] = uint32(indices[i])
|
||||
}
|
||||
i.DrawTrianglesShader32(vertices, is, shader, options)
|
||||
}
|
||||
|
||||
// DrawTrianglesShader32 draws triangles with the specified vertices and their indices with the specified shader.
|
||||
// DrawTrianglesShader32 is the version of DrawTrianglesShader with uint32 indices.
|
||||
//
|
||||
// Vertex contains color values, which can be interpreted for any purpose by the shader.
|
||||
//
|
||||
// For the details about the shader, see https://ebitengine.org/en/documents/shader.html.
|
||||
//
|
||||
// If the shader unit is texels, one of the specified image is non-nil and its size is different from (width, height),
|
||||
// DrawTrianglesShader32 panics.
|
||||
// If one of the specified image is non-nil and is disposed, DrawTrianglesShader32 panics.
|
||||
//
|
||||
// If len(vertices) is more than MaxVertexCount, the exceeding part is ignored.
|
||||
//
|
||||
// If len(indices) is not multiple of 3, DrawTrianglesShader32 panics.
|
||||
//
|
||||
// If a value in indices is out of range of vertices, or not less than MaxVertexCount, DrawTrianglesShader32 panics.
|
||||
//
|
||||
// When a specified image is non-nil and is disposed, DrawTrianglesShader32 panics.
|
||||
//
|
||||
// If a specified uniform variable's length or type doesn't match with an expected one, DrawTrianglesShader32 panics.
|
||||
//
|
||||
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||
// the value is kept and is not clamped.
|
||||
//
|
||||
// When the image i is disposed, DrawTrianglesShader32 does nothing.
|
||||
func (i *Image) DrawTrianglesShader32(vertices []Vertex, indices []uint32, shader *Shader, options *DrawTrianglesShaderOptions) {
|
||||
i.copyCheck()
|
||||
|
||||
if i.isDisposed() {
|
||||
@@ -602,6 +839,30 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
panic("ebiten: the given shader to DrawTrianglesShader must not be disposed")
|
||||
}
|
||||
|
||||
if len(indices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
if options != nil {
|
||||
for _, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
img.invokeUsageCallbacks()
|
||||
}
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
for _, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
img.updateAccessTime()
|
||||
}
|
||||
}
|
||||
i.updateAccessTime()
|
||||
|
||||
if len(vertices) > graphicscommand.MaxVertexCount {
|
||||
// The last part cannot be specified by indices. Just omit them.
|
||||
vertices = vertices[:graphicscommand.MaxVertexCount]
|
||||
@@ -629,28 +890,30 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||
dst := i
|
||||
src := options.Images[0]
|
||||
for i, v := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
||||
vs[i*graphics.VertexFloatCount] = dx
|
||||
vs[i*graphics.VertexFloatCount+1] = dy
|
||||
sx, sy := v.SrcX, v.SrcY
|
||||
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||
for i := range vertices {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+12]
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
vs[0] = dx
|
||||
vs[1] = dy
|
||||
sx, sy := vertices[i].SrcX, vertices[i].SrcY
|
||||
if src != nil {
|
||||
sx, sy = src.adjustPositionF32(sx, sy)
|
||||
}
|
||||
vs[i*graphics.VertexFloatCount+2] = sx
|
||||
vs[i*graphics.VertexFloatCount+3] = sy
|
||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR
|
||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG
|
||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB
|
||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA
|
||||
vs[2] = sx
|
||||
vs[3] = sy
|
||||
vs[4] = vertices[i].ColorR
|
||||
vs[5] = vertices[i].ColorG
|
||||
vs[6] = vertices[i].ColorB
|
||||
vs[7] = vertices[i].ColorA
|
||||
vs[8] = vertices[i].Custom0
|
||||
vs[9] = vertices[i].Custom1
|
||||
vs[10] = vertices[i].Custom2
|
||||
vs[11] = vertices[i].Custom3
|
||||
}
|
||||
|
||||
is := make([]uint32, len(indices))
|
||||
for i := range is {
|
||||
is[i] = uint32(indices[i])
|
||||
}
|
||||
|
||||
var imgs [graphics.ShaderImageCount]*ui.Image
|
||||
var imgs [graphics.ShaderSrcImageCount]*ui.Image
|
||||
var imgSize image.Point
|
||||
for i, img := range options.Images {
|
||||
if img == nil {
|
||||
@@ -672,7 +935,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
imgs[i] = img.image
|
||||
}
|
||||
|
||||
var srcRegions [graphics.ShaderImageCount]image.Rectangle
|
||||
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
|
||||
for i, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
@@ -683,7 +946,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
i.tmpUniforms = i.tmpUniforms[:0]
|
||||
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias)
|
||||
i.image.DrawTriangles(imgs, vs, indices, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias, restorable.HintNone)
|
||||
}
|
||||
|
||||
// DrawRectShaderOptions represents options for DrawRectShader.
|
||||
@@ -710,7 +973,7 @@ type DrawRectShaderOptions struct {
|
||||
|
||||
// Uniforms is a set of uniform variables for the shader.
|
||||
// The keys are the names of the uniform variables.
|
||||
// The values must be a numeric type, or a slice or an array of a numeric type.
|
||||
// The values must be a numeric/boolean type, or a slice or an array of a numeric/boolean type.
|
||||
// If the uniform variable type is an array, a vector or a matrix,
|
||||
// you have to specify linearly flattened values as a slice or an array.
|
||||
// For example, if the uniform variable type is [4]vec4, the length will be 16.
|
||||
@@ -724,7 +987,7 @@ type DrawRectShaderOptions struct {
|
||||
}
|
||||
|
||||
// Check the number of images.
|
||||
var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderImageCount]struct{}{}
|
||||
var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderSrcImageCount]struct{}{}
|
||||
|
||||
// DrawRectShader draws a rectangle with the specified width and height with the specified shader.
|
||||
//
|
||||
@@ -760,6 +1023,26 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
panic("ebiten: the given shader to DrawRectShader must not be disposed")
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
for _, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
img.invokeUsageCallbacks()
|
||||
}
|
||||
}
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
if options != nil {
|
||||
for _, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
}
|
||||
img.updateAccessTime()
|
||||
}
|
||||
}
|
||||
i.updateAccessTime()
|
||||
|
||||
if options == nil {
|
||||
options = &DrawRectShaderOptions{}
|
||||
}
|
||||
@@ -771,7 +1054,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
blend = options.CompositeMode.blend().internalBlend()
|
||||
}
|
||||
|
||||
var imgs [graphics.ShaderImageCount]*ui.Image
|
||||
var imgs [graphics.ShaderSrcImageCount]*ui.Image
|
||||
for i, img := range options.Images {
|
||||
if img == nil {
|
||||
continue
|
||||
@@ -785,7 +1068,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
imgs[i] = img.image
|
||||
}
|
||||
|
||||
var srcRegions [graphics.ShaderImageCount]image.Rectangle
|
||||
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
|
||||
for i, img := range options.Images {
|
||||
if img == nil {
|
||||
if shader.unit == shaderir.Pixels && i == 0 {
|
||||
@@ -803,11 +1086,14 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
geoM.Translate(float64(offsetX), float64(offsetY))
|
||||
}
|
||||
a, b, c, d, tx, ty := geoM.elements32()
|
||||
if det := a*d - b*c; det == 0 {
|
||||
return
|
||||
}
|
||||
cr, cg, cb, ca := options.ColorScale.elements()
|
||||
vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount)
|
||||
|
||||
// Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty.
|
||||
graphics.QuadVertices(vs,
|
||||
graphics.QuadVerticesFromSrcAndMatrix(vs,
|
||||
float32(srcRegions[0].Min.X), float32(srcRegions[0].Min.Y),
|
||||
float32(srcRegions[0].Min.X+width), float32(srcRegions[0].Min.Y+height),
|
||||
a, b, c, d, tx, ty, cr, cg, cb, ca)
|
||||
@@ -816,7 +1102,14 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
i.tmpUniforms = i.tmpUniforms[:0]
|
||||
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, true, false)
|
||||
dr := i.adjustedBounds()
|
||||
hint := restorable.HintNone
|
||||
// Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty.
|
||||
if overwritesDstRegion(options.Blend, dr, geoM, srcRegions[0].Min.X, srcRegions[0].Min.Y, srcRegions[0].Min.X+width, srcRegions[0].Min.Y+height) {
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, dr, srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false, hint)
|
||||
}
|
||||
|
||||
// SubImage returns an image representing the portion of the image p visible through r.
|
||||
@@ -838,24 +1131,53 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isSubImage() {
|
||||
return i.original.SubImage(r.Intersect(i.Bounds()))
|
||||
}
|
||||
|
||||
r = r.Intersect(i.Bounds())
|
||||
// Need to check Empty explicitly. See the standard image package implementations.
|
||||
if r.Empty() {
|
||||
r = image.ZR
|
||||
r = image.Rectangle{}
|
||||
}
|
||||
|
||||
var orig = i
|
||||
if i.isSubImage() {
|
||||
orig = i.original
|
||||
i.subImageCacheM.Lock()
|
||||
defer i.subImageCacheM.Unlock()
|
||||
|
||||
// The image might already be disposed in another goroutine.
|
||||
// Recheck this.
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if img, ok := i.subImageCache[r]; ok {
|
||||
img.updateAccessTime()
|
||||
return img
|
||||
}
|
||||
|
||||
if tick := Tick(); i.subImageGCLastTick < tick {
|
||||
i.subImageGCLastTick = tick
|
||||
|
||||
for _, img := range i.subImageCache {
|
||||
if img.atime.Load()+60 < tick {
|
||||
delete(i.subImageCache, img.bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img := &Image{
|
||||
image: i.image,
|
||||
bounds: r,
|
||||
original: orig,
|
||||
original: i,
|
||||
}
|
||||
img.addr = img
|
||||
|
||||
if i.subImageCache == nil {
|
||||
i.subImageCache = map[image.Rectangle]*Image{}
|
||||
}
|
||||
i.subImageCache[r] = img
|
||||
img.updateAccessTime()
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
@@ -906,6 +1228,8 @@ func (i *Image) ReadPixels(pixels []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
i.image.ReadPixels(pixels, i.adjustedBounds())
|
||||
}
|
||||
|
||||
@@ -950,6 +1274,8 @@ func (i *Image) at(x, y int) (r, g, b, a byte) {
|
||||
return 0, 0, 0, 0
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
x, y = i.adjustPosition(x, y)
|
||||
var pix [4]byte
|
||||
i.image.ReadPixels(pix[:], image.Rect(x, y, x+1, y+1))
|
||||
@@ -960,15 +1286,24 @@ func (i *Image) at(x, y int) (r, g, b, a byte) {
|
||||
//
|
||||
// Set implements the standard draw.Image's Set.
|
||||
//
|
||||
// If (x, y) is outside the image bounds, Set does nothing.
|
||||
//
|
||||
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||
// the value is kept and is not clamped.
|
||||
//
|
||||
// If the image is disposed, Set does nothing.
|
||||
//
|
||||
// For performance, it is recommended to use WritePixels instead of Set whenever possible.
|
||||
func (i *Image) Set(x, y int, clr color.Color) {
|
||||
i.copyCheck()
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
i.updateAccessTime()
|
||||
|
||||
if !image.Pt(x, y).In(i.Bounds()) {
|
||||
return
|
||||
}
|
||||
@@ -1003,6 +1338,10 @@ func (i *Image) Dispose() {
|
||||
}
|
||||
i.image.Deallocate()
|
||||
i.image = nil
|
||||
i.subImageCacheM.Lock()
|
||||
i.subImageCache = nil
|
||||
i.subImageCacheM.Unlock()
|
||||
i.usageCallbacks = nil
|
||||
}
|
||||
|
||||
// Deallocate clears the image and deallocates the internal state of the image.
|
||||
@@ -1026,6 +1365,7 @@ func (i *Image) Deallocate() {
|
||||
return
|
||||
}
|
||||
i.image.Deallocate()
|
||||
i.usageCallbacks = nil
|
||||
}
|
||||
|
||||
// WritePixels replaces the pixels of the image.
|
||||
@@ -1048,6 +1388,8 @@ func (i *Image) WritePixels(pixels []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
i.invokeUsageCallbacks()
|
||||
|
||||
// Do not need to copy pixels here.
|
||||
// * In internal/mipmap, pixels are copied when necessary.
|
||||
// * In internal/atlas, pixels are copied to make its paddings.
|
||||
@@ -1110,7 +1452,7 @@ func NewImageWithOptions(bounds image.Rectangle, options *NewImageOptions) *Imag
|
||||
|
||||
func newImage(bounds image.Rectangle, imageType atlas.ImageType) *Image {
|
||||
if isRunGameEnded() {
|
||||
panic(fmt.Sprintf("ebiten: NewImage cannot be called after RunGame finishes"))
|
||||
panic("ebiten: NewImage cannot be called after RunGame finishes")
|
||||
}
|
||||
|
||||
width, height := bounds.Dx(), bounds.Dy()
|
||||
@@ -1246,6 +1588,102 @@ func (i *Image) ensureTmpVertices(n int) []float32 {
|
||||
return i.tmpVertices[:n]
|
||||
}
|
||||
|
||||
func (i *Image) ensureTmpIndices(n int) []uint32 {
|
||||
if cap(i.tmpIndices) < n {
|
||||
i.tmpIndices = make([]uint32, n)
|
||||
}
|
||||
return i.tmpIndices[:n]
|
||||
}
|
||||
|
||||
// private implements FinalScreen.
|
||||
func (*Image) private() {
|
||||
}
|
||||
|
||||
// Do not use usage callbacks except for Ebitengine packages.
|
||||
// There is no guarantee for compatibility of this function.
|
||||
|
||||
var currentCallbackToken atomic.Int64
|
||||
|
||||
//go:linkname originalImage
|
||||
func originalImage(img *Image) *Image {
|
||||
if img.isSubImage() {
|
||||
return img.original
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
//go:linkname addUsageCallback
|
||||
func addUsageCallback(img *Image, callback func(image *Image)) int64 {
|
||||
return img.addUsageCallback(callback)
|
||||
}
|
||||
|
||||
func (i *Image) addUsageCallback(callback func(image *Image)) int64 {
|
||||
if i.isSubImage() {
|
||||
return i.original.addUsageCallback(callback)
|
||||
}
|
||||
token := currentCallbackToken.Add(1)
|
||||
|
||||
i.usageCallbacksM.Lock()
|
||||
defer i.usageCallbacksM.Unlock()
|
||||
|
||||
if i.usageCallbacks == nil {
|
||||
i.usageCallbacks = map[int64]usageCallback{}
|
||||
}
|
||||
i.usageCallbacks[token] = usageCallback{
|
||||
fn: callback,
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
//go:linkname removeUsageCallback
|
||||
func removeUsageCallback(img *Image, token int64) {
|
||||
img.removeUsageCallback(token)
|
||||
}
|
||||
|
||||
func (i *Image) removeUsageCallback(token int64) {
|
||||
if i.isSubImage() {
|
||||
i.original.removeUsageCallback(token)
|
||||
return
|
||||
}
|
||||
|
||||
i.usageCallbacksM.Lock()
|
||||
defer i.usageCallbacksM.Unlock()
|
||||
delete(i.usageCallbacks, token)
|
||||
}
|
||||
|
||||
var theTmpUsageCallbackSlicePool = sync.Pool{
|
||||
New: func() any {
|
||||
slice := make([]usageCallback, 0, 16)
|
||||
return &slice
|
||||
},
|
||||
}
|
||||
|
||||
func (i *Image) invokeUsageCallbacks() {
|
||||
if i.isSubImage() {
|
||||
i.original.invokeUsageCallbacks()
|
||||
return
|
||||
}
|
||||
|
||||
// Do not allow recursive calls.
|
||||
if !i.inUsageCallbacks.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
defer i.inUsageCallbacks.Store(false)
|
||||
|
||||
tmpUsageCallbackSlice := theTmpUsageCallbackSlicePool.Get().(*[]usageCallback)
|
||||
|
||||
func() {
|
||||
i.usageCallbacksM.Lock()
|
||||
defer i.usageCallbacksM.Unlock()
|
||||
for _, cb := range i.usageCallbacks {
|
||||
*tmpUsageCallbackSlice = append(*tmpUsageCallbackSlice, cb)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, cb := range *tmpUsageCallbackSlice {
|
||||
cb.fn(i)
|
||||
}
|
||||
|
||||
*tmpUsageCallbackSlice = slices.Delete(*tmpUsageCallbackSlice, 0, len(*tmpUsageCallbackSlice))
|
||||
theTmpUsageCallbackSlicePool.Put(tmpUsageCallbackSlice)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user