ebiten: replace (*ebiten.Shader).Dispose with Deallocate

Updates #2808
This commit is contained in:
Hajime Hoshi 2023-11-03 16:05:41 +09:00
parent 97d1e073e2
commit bdd8916bb1
4 changed files with 78 additions and 18 deletions

View File

@ -38,25 +38,22 @@ func (s *Shader) ensureShader() *restorable.Shader {
return s.shader
}
s.shader = restorable.NewShader(s.ir)
s.ir = nil
runtime.SetFinalizer(s, func(s *Shader) {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
s.deallocate()
})
})
return s.shader
}
// MarkDisposed marks the shader as disposed. The actual operation is deferred.
// MarkDisposed can be called from finalizers.
//
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
func (s *Shader) MarkDisposed() {
// As MarkDisposed can be invoked from finalizers, backendsM should not be used.
deferredM.Lock()
deferred = append(deferred, func() {
s.dispose()
})
deferredM.Unlock()
// Deallocate deallocates the internal state.
func (s *Shader) Deallocate() {
s.deallocate()
}
func (s *Shader) dispose() {
func (s *Shader) deallocate() {
runtime.SetFinalizer(s, nil)
if s.shader == nil {
return

View File

@ -40,9 +40,8 @@ func NewShader(ir *shaderir.Program) *Shader {
}
}
func (s *Shader) MarkDisposed() {
s.shader.MarkDisposed()
s.shader = nil
func (s *Shader) Deallocate() {
s.shader.Deallocate()
}
func (s *Shader) AppendUniforms(dst []uint32, uniforms map[string]any) []uint32 {

View File

@ -50,11 +50,24 @@ func NewShader(src []byte) (*Shader, error) {
// Dispose disposes the shader program.
// After disposing, the shader is no longer available.
//
// Deprecated: as of v2.7. Use Deallocate instead.
func (s *Shader) Dispose() {
s.shader.MarkDisposed()
s.shader.Deallocate()
s.shader = nil
}
// Deallocate deallocates the internal state of shader.
// Even after Deallocate is called, the shader is still available.
// In this case, the shader's internal state is allocated again.
//
// Usually, you don't have to call Deallocate since the internal state is automatically released by GC.
// However, if you are sure that the shader is no longer used but not sure how this shader object is referred,
// you can call Deallocate to make sure that the internal state is deallocated.
func (s *Shader) Deallocate() {
s.shader.Deallocate()
}
func (s *Shader) appendUniforms(dst []uint32, uniforms map[string]any) []uint32 {
return s.shader.AppendUniforms(dst, uniforms)
}

View File

@ -2299,3 +2299,54 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
})
}
}
func TestShaderDeallocate(t *testing.T) {
const w, h = 16, 16
dst := ebiten.NewImage(w, h)
s, err := ebiten.NewShader([]byte(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return vec4(1, 0, 0, 1)
}
`))
if err != nil {
t.Fatal(err)
}
dst.DrawRectShader(w/2, h/2, s, nil)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
var want color.RGBA
if i < w/2 && j < h/2 {
want = color.RGBA{R: 0xff, A: 0xff}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
// Even after Deallocate is called, the shader is still available.
s.Deallocate()
dst.Clear()
dst.DrawRectShader(w/2, h/2, s, nil)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
var want color.RGBA
if i < w/2 && j < h/2 {
want = color.RGBA{R: 0xff, A: 0xff}
}
if got != want {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}