diff --git a/internal/atlas/image.go b/internal/atlas/image.go
index <HASH>..<HASH> 100644
--- a/internal/atlas/image.go
+++ b/internal/atlas/image.go
@@ -576,6 +576,10 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
 	backendsM.Lock()
 	defer backendsM.Unlock()
 
+	// In the tests, BeginFrame might not be called often and then images might not be disposed (#2292).
+	// To prevent memory leaks, resolve the deferred functions here.
+	resolveDeferred()
+
 	if i.backend == nil || i.backend.restorable == nil {
 		for i := range pixels {
 			pixels[i] = 0
diff --git a/internal/graphicsdriver/directx/graphics_windows.go b/internal/graphicsdriver/directx/graphics_windows.go
index <HASH>..<HASH> 100644
--- a/internal/graphicsdriver/directx/graphics_windows.go
+++ b/internal/graphicsdriver/directx/graphics_windows.go
@@ -778,6 +778,7 @@ func (g *Graphics) End(present bool) error {
 		if err := g.waitForCommandQueue(); err != nil {
 			return err
 		}
+		g.releaseResources(g.frameIndex)
 		g.releaseVerticesAndIndices(g.frameIndex)
 	}
internal/atlas: dispose images at ReadPixels

Without resolveDeferred() at ReadPixels, many images are never disposed
in tests.

Updates #<I>