namespace Mibo.Elmish.Graphics2D
open System.Collections.Generic
open Microsoft.Xna.Framework.Graphics
open Mibo.Elmish
///
/// Provides pooled render targets to avoid per-frame allocation and disposal
/// of resources.
///
///
/// Acquired targets remain in use until
/// is called, typically once per frame. Targets are keyed by dimensions and reused
/// across frames without being disposed, avoiding GPU allocation overhead.
///
type IRenderTargetPool =
///
/// Acquires a render target matching the given dimensions.
/// Reuses a previously released target if available, otherwise creates a new one.
///
/// A render target with the specified width and height.
abstract Acquire: width: int * height: int -> RenderTarget2D
///
/// Returns all currently held targets to the pool. Call once per frame
/// after rendering is complete. Targets are retained for future reuse.
///
abstract ReleaseAll: unit -> unit
///
/// Default implementation of
/// using a dictionary keyed by (width, height) dimensions.
/// Stores targets in per-dimension queues for FIFO reuse.
///
///
/// Dispose the pool when the application shuts down to dispose all pooled targets.
/// Idle targets kept per dimension are capped by
/// so that repeated window resizes (which produce many distinct dimensions) don't
/// retain GPU memory for sizes that may never be requested again. Excess idle
/// targets are disposed at time.
///
type RenderTargetPool(gd: GraphicsDevice, ?maxIdlePerDimension: int) =
// Maximum idle targets retained per (width, height) key. Anything beyond this
// is disposed at ReleaseAll rather than kept, bounding memory growth when the
// app sees many distinct dimensions over its lifetime (e.g. during window
// resizing). 2 is enough for ping-pong post-processing chains.
let maxIdle = defaultArg maxIdlePerDimension 2
let pool = Dictionary>()
let inUse = ResizeArray()
interface IRenderTargetPool with
member _.Acquire(width, height) =
let key = struct (width, height)
match Dictionary.tryGetValue key pool with
| ValueSome queue when queue.Count > 0 ->
let rt = queue.Dequeue()
inUse.Add(rt)
rt
| _ ->
let rt =
new RenderTarget2D(
gd,
width,
height,
false,
SurfaceFormat.Color,
DepthFormat.None,
0,
RenderTargetUsage.DiscardContents
)
inUse.Add(rt)
rt
member _.ReleaseAll() =
for rt in inUse do
let key = struct (rt.Width, rt.Height)
match Dictionary.tryGetValue key pool with
| ValueSome queue when queue.Count < maxIdle -> queue.Enqueue(rt)
| ValueSome _ ->
// Per-dimension idle cap reached: dispose the excess rather than
// retaining it, so dimensions seen only during transient resizes
// don't leak GPU memory.
rt.Dispose()
| ValueNone ->
let queue = Queue()
queue.Enqueue(rt)
pool[key] <- queue
inUse.Clear()
interface System.IDisposable with
member _.Dispose() =
for rt in inUse do
rt.Dispose()
inUse.Clear()
for KeyValueV(_, queue) in pool do
for rt in queue do
rt.Dispose()
queue.Clear()
pool.Clear()