Skip to content

Commit

Permalink
Rename 'CanvasEffect' APIs
Browse files Browse the repository at this point in the history
Port of #796
  • Loading branch information
Sergio0694 committed Oct 11, 2024
1 parent 72b05a0 commit a0ecf68
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 402 deletions.
265 changes: 0 additions & 265 deletions src/ComputeSharp.D2D1.UI/CanvasEffect.EffectGraph.cs

This file was deleted.

26 changes: 13 additions & 13 deletions src/ComputeSharp.D2D1.UI/CanvasEffect.Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,40 +66,40 @@ unsafe int ICanvasImageInterop.Interface.GetD2DImage(
/// </summary>
/// <returns>The current <see cref="ICanvasImage"/> instance.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the current instance is disposed.</exception>
[MemberNotNull(nameof(canvasImage))]
[MemberNotNull(nameof(CanvasImage))]
private ICanvasImage GetCanvasImage()
{
lock (this.transformNodes)
lock (this.TransformNodes)
{
default(ObjectDisposedException).ThrowIf(this.isDisposed, this);

// Build the effect graph (the output node might not have been set yet)
if (this.invalidationType == InvalidationType.Creation)
if (this.InvalidationType == CanvasEffectInvalidationType.Creation)
{
DisposeEffectGraph();
BuildEffectGraph(new EffectGraph(this));
BuildEffectGraph(new CanvasEffectGraph(this));

// We successfully got past the effect graph creation, so update the current
// invalidation state. This ensures that even if the configuration failed, the
// next time the effect is drawn again the graph won't be created again too.
this.invalidationType = InvalidationType.Update;
this.InvalidationType = CanvasEffectInvalidationType.Update;
}

// Configure the effect graph, if the effect is invalidated
if (this.invalidationType == InvalidationType.Update)
if (this.InvalidationType == CanvasEffectInvalidationType.Update)
{
ConfigureEffectGraph(new EffectGraph(this));
ConfigureEffectGraph(new CanvasEffectGraph(this));

// The effect graph is now ready to go: no further work will be done before drawing
// unless it is explicitly invalidated again, using either creation or update mode.
this.invalidationType = default;
this.InvalidationType = default;
}

// At this point, there must be an output canvas image being set.
// If not, it means a derived type has forgot to set an output node.
default(InvalidOperationException).ThrowIf(this.canvasImage is null, "No output node is set in the effect graph.");
default(InvalidOperationException).ThrowIf(this.CanvasImage is null, "No output node is set in the effect graph.");

return this.canvasImage;
return this.CanvasImage;
}
}

Expand All @@ -109,15 +109,15 @@ private ICanvasImage GetCanvasImage()
private void DisposeEffectGraph()
{
// Dispose all registered canvas images
foreach (ICanvasImage canvasImage in this.transformNodes.Values)
foreach (ICanvasImage canvasImage in this.TransformNodes.Values)
{
canvasImage.Dispose();
}

// Also clear the current effect graph. Note that the canvas image used as output
// node for the effect graph does not need to be explicitly disposed here, as that
// object is guaranteed to have been part of the dictionary of transform nodes.
this.transformNodes.Clear();
this.canvasImage = null;
this.TransformNodes.Clear();
this.CanvasImage = null;
}
}
70 changes: 23 additions & 47 deletions src/ComputeSharp.D2D1.UI/CanvasEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I
/// The mapping of registered transform nodes for the current effect graph.
/// </summary>
/// <remarks>
/// When not empty (ie. when an effect graph has been built), this will always also include <see cref="canvasImage"/>.
/// <para>
/// When not empty (ie. when an effect graph has been built), this will always also include <see cref="CanvasImage"/>.
/// </para>
/// <para>
/// This field and the two below are <see langword="internal"/> due to lack of <see langword="friend"/> modifier in C#.
/// The <see cref="CanvasEffectGraph"/> type is the only one that needs access to these fields to configure the effect.
/// </para>
/// </remarks>
private readonly Dictionary<object, ICanvasImage> transformNodes = new();
internal readonly Dictionary<object, ICanvasImage> TransformNodes = new();

/// <summary>
/// The current cached result for <see cref="GetCanvasImage"/>, if available.
/// </summary>
private ICanvasImage? canvasImage;
internal ICanvasImage? CanvasImage;

/// <summary>
/// Indicates the current invalidation state for the effect graph (which controls the logic in <see cref="GetCanvasImage"/>).
/// </summary>
private InvalidationType invalidationType = InvalidationType.Creation;
internal CanvasEffectInvalidationType InvalidationType = CanvasEffectInvalidationType.Creation;

/// <summary>
/// Indicates whether the effect is disposed.
Expand All @@ -42,14 +48,14 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I
/// Builds the effect graph for the current <see cref="CanvasEffect"/> instance, and configures all effect nodes, as well as the output
/// node for the graph. That <see cref="ICanvasImage"/> instance will then be passed to Win2D to perform the actual drawing, when needed.
/// </summary>
/// <param name="effectGraph">The input <see cref="EffectGraph"/> value to use to build the effect graph.</param>
/// <param name="effectGraph">The input <see cref="CanvasEffectGraph"/> value to use to build the effect graph.</param>
/// <remarks>
/// <para>
/// This method is called once before the current effect is drawn, and the resulting image is automatically cached and reused.
/// It will remain in use until <see cref="InvalidateEffectGraph"/> is called with <see cref="InvalidationType.Creation"/>.
/// It will remain in use until <see cref="InvalidateEffectGraph"/> is called with <see cref="CanvasEffectInvalidationType.Creation"/>.
/// </para>
/// <para>
/// If the effect is invalidated with <see cref="InvalidationType.Update"/>, only <see cref="ConfigureEffectGraph"/> will be
/// If the effect is invalidated with <see cref="CanvasEffectInvalidationType.Update"/>, only <see cref="ConfigureEffectGraph"/> will be
/// called. As such, derived types should save any effect graph nodes that might need updates into instance fields, for later use.
/// </para>
/// <para>
Expand Down Expand Up @@ -77,19 +83,19 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I
/// </list>
/// </para>
/// <para>
/// For convenience, it is recommended to store all necessary <see cref="EffectNode{T}"/> objects in <see langword="static"/>
/// For convenience, it is recommended to store all necessary <see cref="CanvasEffectNode{T}"/> objects in <see langword="static"/>
/// <see langword="readonly"/> fields, so they can easily be accessed from <see cref="BuildEffectGraph"/> and <see cref="ConfigureEffectGraph"/>.
/// </para>
/// <para>
/// This method should never be called directly. It is automatically invoked when an effect graph is needed.
/// </para>
/// </remarks>
protected abstract void BuildEffectGraph(EffectGraph effectGraph);
protected abstract void BuildEffectGraph(CanvasEffectGraph effectGraph);

/// <summary>
/// Configures the current effect graph whenever it is invalidated.
/// </summary>
/// <param name="effectGraph">The input <see cref="EffectGraph"/> value to use to configure the effect graph.</param>
/// <param name="effectGraph">The input <see cref="CanvasEffectGraph"/> value to use to configure the effect graph.</param>
/// <remarks>
/// <para>
/// This method is guaranteed to be called after <see cref="BuildEffectGraph"/> has been invoked already. As such, any instance
Expand All @@ -99,7 +105,7 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I
/// This method should never be called directly. It is used internally to configure the current effect graph.
/// </para>
/// </remarks>
protected abstract void ConfigureEffectGraph(EffectGraph effectGraph);
protected abstract void ConfigureEffectGraph(CanvasEffectGraph effectGraph);

/// <summary>
/// Invalidates the last returned result from <see cref="GetCanvasImage"/>.
Expand All @@ -112,22 +118,22 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I
/// or whether it simply needs to refresh its internal state (by calling <see cref="ConfigureEffectGraph"/>).
/// </para>
/// <para>
/// If <see cref="InvalidateEffectGraph"/> is called with <see cref="InvalidationType.Update"/>, the effect
/// If <see cref="InvalidateEffectGraph"/> is called with <see cref="CanvasEffectInvalidationType.Update"/>, the effect
/// graph will only be refreshed the next time the image is actually requested. That is, repeated requests
/// for updates do not result in unnecessarily calls to <see cref="ConfigureEffectGraph"/>.
/// </para>
/// </remarks>
protected void InvalidateEffectGraph(InvalidationType invalidationType = InvalidationType.Update)
protected void InvalidateEffectGraph(CanvasEffectInvalidationType invalidationType = CanvasEffectInvalidationType.Update)
{
lock (this.transformNodes)
lock (this.TransformNodes)
{
// This method only updates the invalidation type. The next time the effect is drawn,
// the current graph (if existing) will be disposed automatically before building a
// new one. If the invalidation mode just needs an update, no disposal will happen.
// The new invalidation type is always set to the maximum between the current one and
// the requested one. This means that eg. if the current invalidation type is creation,
// and a property only needing an update is set, the creation request will persist.
this.invalidationType = (InvalidationType)Math.Max((byte)this.invalidationType, (byte)invalidationType);
this.InvalidationType = (CanvasEffectInvalidationType)Math.Max((byte)this.InvalidationType, (byte)invalidationType);
}
}

Expand All @@ -139,7 +145,7 @@ protected void InvalidateEffectGraph(InvalidationType invalidationType = Invalid
/// <param name="storage">The storage for the effect property value.</param>
/// <param name="value">The new effect property value to set.</param>
/// <param name="invalidationType">The invalidation type to request.</param>
protected void SetAndInvalidateEffectGraph<T>([NotNullIfNotNull(nameof(value))] ref T storage, T value, InvalidationType invalidationType = InvalidationType.Update)
protected void SetAndInvalidateEffectGraph<T>([NotNullIfNotNull(nameof(value))] ref T storage, T value, CanvasEffectInvalidationType invalidationType = CanvasEffectInvalidationType.Update)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
Expand Down Expand Up @@ -189,7 +195,7 @@ protected virtual void Dispose(bool disposing)
// the lock itself might have already been collected by the time this code runs, which would lead the lock
// statement to throw a NullReferenceException. So if a finalizer is running, just let objects be collected
// on their own. This is fine here since there are no unmanaged references to free, but just managed wrappers.
lock (this.transformNodes)
lock (this.TransformNodes)
{
DisposeEffectGraph();
}
Expand All @@ -205,34 +211,4 @@ public void Dispose()

GC.SuppressFinalize(this);
}

/// <summary>
/// Indicates the invalidation type to request when invoking <see cref="InvalidateEffectGraph"/> and related methods.
/// </summary>
protected enum InvalidationType : byte
{
/// <summary>
/// Invalidates the state of the effect graph, causing it to be configured again the next time it is used.
/// </summary>
/// <remarks>
/// <para>
/// This will preserve the last returned <see cref="ICanvasImage"/> instance, if available, and it will only
/// mark the internal state as being out of date, resulting in <see cref="ConfigureEffectGraph"/> to be called
/// the next time the effect is used for drawing.
/// </para>
/// <para>
/// This is much less expensive than creating the effect graph again, and should be preferred if possible.
/// </para>
/// </remarks>
Update,

/// <summary>
/// Fully invalidates the effect graph, causing it to be created again the next time it is needed.
/// </summary>
/// <remarks>
/// This will cause the last returned <see cref="ICanvasImage"/> instance to be disposed and discarded,
/// and <see cref="BuildEffectGraph"/> to be called again the next time the effect is used for drawing.
/// </remarks>
Creation
}
}
Loading

0 comments on commit a0ecf68

Please sign in to comment.