diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffect.EffectGraph.cs b/src/ComputeSharp.D2D1.UI/CanvasEffect.EffectGraph.cs deleted file mode 100644 index 9af0f2a7a..000000000 --- a/src/ComputeSharp.D2D1.UI/CanvasEffect.EffectGraph.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Microsoft.Graphics.Canvas; - -#if WINDOWS_UWP -namespace ComputeSharp.D2D1.Uwp; -#else -namespace ComputeSharp.D2D1.WinUI; -#endif - -/// -partial class CanvasEffect -{ - /// - /// An object representing an effect graph being built or configured. - /// - protected readonly ref struct EffectGraph - { - /// - ///The owning instance. - /// - private readonly CanvasEffect owner; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The owning instance. - internal EffectGraph(CanvasEffect owner) - { - this.owner = owner; - } - - /// - /// Gets a previously registered object associated with a given effect graph node. - /// - /// The instance to use to lookup the object to retrieve. - /// The object associated with in the effect graph. - /// Thrown if is . - /// Thrown if is not currently registered in the effect graph. - /// Thrown if the current instance is not valid. - public ICanvasImage GetNode(IEffectNode effectNode) - { - default(ArgumentNullException).ThrowIfNull(effectNode); - default(InvalidOperationException).ThrowIf(this.owner is null); - - // Try to get the canvas image associated with the input effect node marker. - // This must have been previously registered in a call to BuildEffectGraph. - if (!this.owner.transformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) - { - default(ArgumentException).Throw(nameof(effectNode), "The specified node is not registered in the effect graph."); - } - - return canvasImage; - } - - /// - /// Gets a previously registered object (an instance) associated with a given effect graph node. - /// - /// The type of object to retrieve. - /// The instance to use to lookup the object to retrieve. - /// The object associated with in the effect graph. - /// Thrown if is . - /// Thrown if is not currently registered in the effect graph. - /// Thrown if the current instance is not valid. - public T GetNode(IEffectNode effectNode) - where T : class, ICanvasImage - { - default(ArgumentNullException).ThrowIfNull(effectNode); - default(InvalidOperationException).ThrowIf(this.owner is null); - - // Retrieve the registered canvas image, same as above - if (!this.owner.transformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) - { - default(ArgumentException).Throw(nameof(effectNode), "The specified node is not registered in the effect graph."); - } - - // Return the T node (we can skip the expensive cast since this is guaranteed to be valid). - // This is because EffectNode being registered can only associate T instances with them. - return Unsafe.As(canvasImage); - } - - /// - /// Registers an object in the effect graph. - /// - /// The object to register in the effect graph. - /// Thrown if is . - /// - /// Thrown if the current instance is not valid or if the effect graph does not support modifications at this time. - /// - /// - /// - /// - /// This method can be used when doesn't need to be retrieved from , ie. - /// when it has no properties that the current effect instance will need to mutate over it. Using this method still allows the effect to - /// correctly track the image ownership, so that it can be disposed when the current effect instance is disposed. - /// - /// - /// If performing lookups on is required, use the overload. - /// - /// - public void RegisterNode(ICanvasImage canvasImage) - { - default(ArgumentNullException).ThrowIfNull(canvasImage); - default(InvalidOperationException).ThrowIf(this.owner is null); - default(InvalidOperationException).ThrowIf(this.owner.invalidationType != InvalidationType.Creation, "The effect graph cannot be modified after its creation."); - - // Use a new dummy object as key to register the anonymous effect node. This is cheaper than having to maintain a different - // data structure (eg. a HashSet instance) just to hold the anonymous effects that may be registered. This way, - // the same dictionary can be reused for all kinds of effect nodes instead. Anonymous effects are generally a nicher scenario. - _ = this.owner.transformNodes.TryAdd(new object(), canvasImage); - } - - /// - /// Registers an object in the effect graph, associated with a given instance. - /// - /// The type of object to register. - /// The instance to use to register . - /// The object to register in the effect graph. - /// Thrown if or are . - /// - /// Thrown if the current instance is not valid or if the effect graph does not support modifications at this time. - /// - /// Thrown if is already registered in the effect graph. - /// - /// This method can only be called from an value being passed to . If this - /// method is called from within , it will fail, as the effect graph cannot be mutated from there. - /// - public void RegisterNode(EffectNode effectNode, T canvasImage) - where T : class, ICanvasImage - { - default(ArgumentNullException).ThrowIfNull(effectNode); - default(ArgumentNullException).ThrowIfNull(canvasImage); - default(InvalidOperationException).ThrowIf(this.owner is null); - default(InvalidOperationException).ThrowIf(this.owner.invalidationType != InvalidationType.Creation, "The effect graph cannot be modified after its creation."); - - // Try to add the new canvas image associated with the input effect node marker. - // This must not have been previously added from another RegisterNode call. - if (!this.owner.transformNodes.TryAdd(effectNode, canvasImage)) - { - default(ArgumentException).Throw(nameof(effectNode), "The specified node is already registered in the effect graph."); - } - } - - /// - /// Registers an object in the effect graph, and marks it as the output node for the effect graph being built. - /// - /// - /// - /// - /// This method can be used when doesn't need to be retrieved from , ie. - /// when it has no properties that the current effect instance will need to mutate over it. Using this method still allows the effect to - /// correctly track the image ownership, so that it can be disposed when the current effect instance is disposed. - /// - /// - /// If performing lookups on is required, use the overload. - /// - /// - /// - public void RegisterOutputNode(ICanvasImage canvasImage) - { - default(ArgumentNullException).ThrowIfNull(canvasImage); - default(InvalidOperationException).ThrowIf(this.owner is null); - default(InvalidOperationException).ThrowIf(this.owner.invalidationType != InvalidationType.Creation, "The effect graph cannot be modified after its creation."); - - // Register the anonymous effect node with a dummy object, same as above - _ = this.owner.transformNodes.TryAdd(new object(), canvasImage); - - // Store the anonymous output node for later use - this.owner.canvasImage = canvasImage; - } - - /// - /// Registers an object in the effect graph, associated with a given instance. - /// Additionally, it also marks the input object as the output node for the effect graph being built. - /// - /// - public void RegisterOutputNode(EffectNode effectNode, T canvasImage) - where T : class, ICanvasImage - { - default(ArgumentNullException).ThrowIfNull(effectNode); - default(ArgumentNullException).ThrowIfNull(canvasImage); - default(InvalidOperationException).ThrowIf(this.owner is null); - default(InvalidOperationException).ThrowIf(this.owner.invalidationType != InvalidationType.Creation, "The effect graph cannot be modified after its creation."); - - // Try to add the output node as in the method above (but with an extra check before doing so) - if (!this.owner.transformNodes.TryAdd(effectNode, canvasImage)) - { - default(ArgumentException).Throw(nameof(effectNode), "The specified node is already registered in the effect graph."); - } - - // Store the output node for later use - this.owner.canvasImage = canvasImage; - } - - /// - /// Sets a previously registered instance as the output node for the effect graph. - /// - /// The instance to set as output node for the effect graph. - /// Thrown if is . - /// Thrown if the current instance is not valid. - /// Thrown if is not registered in the effect graph. - /// - /// This method can be called from both and . It can be used for - /// efficiently changing the output node of a graph, without having to rebuild it. For instance, this can be used for effects that - /// have multiple paths that can be taken for their inputs, depending on the value of some property describing the effect behavior. - /// - public void SetOutputNode(IEffectNode effectNode) - { - default(ArgumentNullException).ThrowIfNull(effectNode); - default(InvalidOperationException).ThrowIf(this.owner is null); - - // Get the canvas image for the registered effect node (which has to exist at this point) - if (!this.owner.transformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) - { - default(ArgumentException).Throw(nameof(effectNode), "The specified output node is not registered in the effect graph."); - } - - // Store the new output node - this.owner.canvasImage = canvasImage; - } - } - - /// - /// A marker interface for an effect node that was registered from an value. - /// - /// - /// - /// This interface allows using and in more scenarios, - /// including where the concrete type of the image being used (eg. to set the output node) is not known (or needed) by the caller. - /// - /// - /// This interface only implemented by and it's not meant to be implemented by external types. - /// - /// - protected interface IEffectNode - { - } - - /// - /// A marker interface for a generic effect node that was registered from an value. - /// - /// The covariant type of associated with the current effect node. - /// - /// - /// This interface allows using and in more scenarios, - /// including with ternary expressions returning multiple concrete instances with a common image type. - /// - /// - /// This interface only implemented by and it's not meant to be implemented by external types. - /// - /// - protected interface IEffectNode : IEffectNode - where T : ICanvasImage - { - } - - /// - /// A marker type for an effect node that can be registered and retrieved from an value. - /// - /// The type of associated with the current effect node. - protected sealed class EffectNode : IEffectNode - where T : class, ICanvasImage - { - } -} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffect.Interop.cs b/src/ComputeSharp.D2D1.UI/CanvasEffect.Interop.cs index 3f46123ef..4fc17ae56 100644 --- a/src/ComputeSharp.D2D1.UI/CanvasEffect.Interop.cs +++ b/src/ComputeSharp.D2D1.UI/CanvasEffect.Interop.cs @@ -66,40 +66,40 @@ unsafe int ICanvasImageInterop.Interface.GetD2DImage( /// /// The current instance. /// Thrown if the current instance is disposed. - [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; } } @@ -109,7 +109,7 @@ 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(); } @@ -117,7 +117,7 @@ private void DisposeEffectGraph() // 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; } } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffect.cs b/src/ComputeSharp.D2D1.UI/CanvasEffect.cs index c9cdbf630..a63f3f3b3 100644 --- a/src/ComputeSharp.D2D1.UI/CanvasEffect.cs +++ b/src/ComputeSharp.D2D1.UI/CanvasEffect.cs @@ -19,19 +19,25 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I /// The mapping of registered transform nodes for the current effect graph. /// /// - /// When not empty (ie. when an effect graph has been built), this will always also include . + /// + /// When not empty (ie. when an effect graph has been built), this will always also include . + /// + /// + /// This field and the two below are due to lack of modifier in C#. + /// The type is the only one that needs access to these fields to configure the effect. + /// /// - private readonly Dictionary transformNodes = new(); + internal readonly Dictionary TransformNodes = new(); /// /// The current cached result for , if available. /// - private ICanvasImage? canvasImage; + internal ICanvasImage? CanvasImage; /// /// Indicates the current invalidation state for the effect graph (which controls the logic in ). /// - private InvalidationType invalidationType = InvalidationType.Creation; + internal CanvasEffectInvalidationType InvalidationType = CanvasEffectInvalidationType.Creation; /// /// Indicates whether the effect is disposed. @@ -42,14 +48,14 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I /// Builds the effect graph for the current instance, and configures all effect nodes, as well as the output /// node for the graph. That instance will then be passed to Win2D to perform the actual drawing, when needed. /// - /// The input value to use to build the effect graph. + /// The input value to use to build the effect graph. /// /// /// 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 is called with . + /// It will remain in use until is called with . /// /// - /// If the effect is invalidated with , only will be + /// If the effect is invalidated with , only will be /// called. As such, derived types should save any effect graph nodes that might need updates into instance fields, for later use. /// /// @@ -77,19 +83,19 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I /// /// /// - /// For convenience, it is recommended to store all necessary objects in + /// For convenience, it is recommended to store all necessary objects in /// fields, so they can easily be accessed from and . /// /// /// This method should never be called directly. It is automatically invoked when an effect graph is needed. /// /// - protected abstract void BuildEffectGraph(EffectGraph effectGraph); + protected abstract void BuildEffectGraph(CanvasEffectGraph effectGraph); /// /// Configures the current effect graph whenever it is invalidated. /// - /// The input value to use to configure the effect graph. + /// The input value to use to configure the effect graph. /// /// /// This method is guaranteed to be called after has been invoked already. As such, any instance @@ -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. /// /// - protected abstract void ConfigureEffectGraph(EffectGraph effectGraph); + protected abstract void ConfigureEffectGraph(CanvasEffectGraph effectGraph); /// /// Invalidates the last returned result from . @@ -112,14 +118,14 @@ public abstract partial class CanvasEffect : ICanvasImage, ICanvasImageInterop.I /// or whether it simply needs to refresh its internal state (by calling ). /// /// - /// If is called with , the effect + /// If is called with , 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 . /// /// - 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 @@ -127,7 +133,7 @@ protected void InvalidateEffectGraph(InvalidationType invalidationType = Invalid // 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); } } @@ -139,7 +145,7 @@ protected void InvalidateEffectGraph(InvalidationType invalidationType = Invalid /// The storage for the effect property value. /// The new effect property value to set. /// The invalidation type to request. - protected void SetAndInvalidateEffectGraph([NotNullIfNotNull(nameof(value))] ref T storage, T value, InvalidationType invalidationType = InvalidationType.Update) + protected void SetAndInvalidateEffectGraph([NotNullIfNotNull(nameof(value))] ref T storage, T value, CanvasEffectInvalidationType invalidationType = CanvasEffectInvalidationType.Update) { if (EqualityComparer.Default.Equals(storage, value)) { @@ -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(); } @@ -205,34 +211,4 @@ public void Dispose() GC.SuppressFinalize(this); } - - /// - /// Indicates the invalidation type to request when invoking and related methods. - /// - protected enum InvalidationType : byte - { - /// - /// Invalidates the state of the effect graph, causing it to be configured again the next time it is used. - /// - /// - /// - /// This will preserve the last returned instance, if available, and it will only - /// mark the internal state as being out of date, resulting in to be called - /// the next time the effect is used for drawing. - /// - /// - /// This is much less expensive than creating the effect graph again, and should be preferred if possible. - /// - /// - Update, - - /// - /// Fully invalidates the effect graph, causing it to be created again the next time it is needed. - /// - /// - /// This will cause the last returned instance to be disposed and discarded, - /// and to be called again the next time the effect is used for drawing. - /// - Creation - } } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffectGraph.cs b/src/ComputeSharp.D2D1.UI/CanvasEffectGraph.cs new file mode 100644 index 000000000..83cf45515 --- /dev/null +++ b/src/ComputeSharp.D2D1.UI/CanvasEffectGraph.cs @@ -0,0 +1,218 @@ +using System; +using System.Runtime.CompilerServices; +using Microsoft.Graphics.Canvas; + +#if WINDOWS_UWP +namespace ComputeSharp.D2D1.Uwp; +#else +namespace ComputeSharp.D2D1.WinUI; +#endif + +/// +/// An object representing an effect graph being built or configured. +/// +public readonly ref struct CanvasEffectGraph +{ + /// + ///The owning instance. + /// + private readonly CanvasEffect owner; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The owning instance. + internal CanvasEffectGraph(CanvasEffect owner) + { + this.owner = owner; + } + + /// + /// Gets a previously registered object associated with a given effect graph node. + /// + /// The instance to use to lookup the object to retrieve. + /// The object associated with in the effect graph. + /// Thrown if is . + /// Thrown if is not currently registered in the effect graph. + /// Thrown if the current instance is not valid. + public ICanvasImage GetNode(ICanvasEffectNode effectNode) + { + default(ArgumentNullException).ThrowIfNull(effectNode); + default(InvalidOperationException).ThrowIf(this.owner is null); + + // Try to get the canvas image associated with the input effect node marker. + // This must have been previously registered in a call to BuildEffectGraph. + if (!this.owner.TransformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) + { + default(ArgumentException).Throw(nameof(effectNode), "The specified node is not registered in the effect graph."); + } + + return canvasImage; + } + + /// + /// Gets a previously registered object (an instance) associated with a given effect graph node. + /// + /// The type of object to retrieve. + /// The instance to use to lookup the object to retrieve. + /// The object associated with in the effect graph. + /// Thrown if is . + /// Thrown if is not currently registered in the effect graph. + /// Thrown if the current instance is not valid. + public T GetNode(ICanvasEffectNode effectNode) + where T : class, ICanvasImage + { + default(ArgumentNullException).ThrowIfNull(effectNode); + default(InvalidOperationException).ThrowIf(this.owner is null); + + // Retrieve the registered canvas image, same as above + if (!this.owner.TransformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) + { + default(ArgumentException).Throw(nameof(effectNode), "The specified node is not registered in the effect graph."); + } + + // Return the T node (we can skip the expensive cast since this is guaranteed to be valid). + // This is because EffectNode being registered can only associate T instances with them. + return Unsafe.As(canvasImage); + } + + /// + /// Registers an object in the effect graph. + /// + /// The object to register in the effect graph. + /// Thrown if is . + /// + /// Thrown if the current instance is not valid or if the effect graph does not support modifications at this time. + /// + /// + /// + /// + /// This method can be used when doesn't need to be retrieved from , ie. + /// when it has no properties that the current effect instance will need to mutate over it. Using this method still allows the effect to + /// correctly track the image ownership, so that it can be disposed when the current effect instance is disposed. + /// + /// + /// If performing lookups on is required, use the overload. + /// + /// + public void RegisterNode(ICanvasImage canvasImage) + { + default(ArgumentNullException).ThrowIfNull(canvasImage); + default(InvalidOperationException).ThrowIf(this.owner is null); + default(InvalidOperationException).ThrowIf(this.owner.InvalidationType != CanvasEffectInvalidationType.Creation, "The effect graph cannot be modified after its creation."); + + // Use a new dummy object as key to register the anonymous effect node. This is cheaper than having to maintain a different + // data structure (eg. a HashSet instance) just to hold the anonymous effects that may be registered. This way, + // the same dictionary can be reused for all kinds of effect nodes instead. Anonymous effects are generally a nicher scenario. + _ = this.owner.TransformNodes.TryAdd(new object(), canvasImage); + } + + /// + /// Registers an object in the effect graph, associated with a given instance. + /// + /// The type of object to register. + /// The instance to use to register . + /// The object to register in the effect graph. + /// Thrown if or are . + /// + /// Thrown if the current instance is not valid or if the effect graph does not support modifications at this time. + /// + /// Thrown if is already registered in the effect graph. + /// + /// This method can only be called from an value being passed to . If this + /// method is called from within , it will fail, as the effect graph cannot be mutated from there. + /// + public void RegisterNode(CanvasEffectNode effectNode, T canvasImage) + where T : class, ICanvasImage + { + default(ArgumentNullException).ThrowIfNull(effectNode); + default(ArgumentNullException).ThrowIfNull(canvasImage); + default(InvalidOperationException).ThrowIf(this.owner is null); + default(InvalidOperationException).ThrowIf(this.owner.InvalidationType != CanvasEffectInvalidationType.Creation, "The effect graph cannot be modified after its creation."); + + // Try to add the new canvas image associated with the input effect node marker. + // This must not have been previously added from another RegisterNode call. + if (!this.owner.TransformNodes.TryAdd(effectNode, canvasImage)) + { + default(ArgumentException).Throw(nameof(effectNode), "The specified node is already registered in the effect graph."); + } + } + + /// + /// Registers an object in the effect graph, and marks it as the output node for the effect graph being built. + /// + /// + /// + /// + /// This method can be used when doesn't need to be retrieved from , ie. + /// when it has no properties that the current effect instance will need to mutate over it. Using this method still allows the effect to + /// correctly track the image ownership, so that it can be disposed when the current effect instance is disposed. + /// + /// + /// If performing lookups on is required, use the overload. + /// + /// + /// + public void RegisterOutputNode(ICanvasImage canvasImage) + { + default(ArgumentNullException).ThrowIfNull(canvasImage); + default(InvalidOperationException).ThrowIf(this.owner is null); + default(InvalidOperationException).ThrowIf(this.owner.InvalidationType != CanvasEffectInvalidationType.Creation, "The effect graph cannot be modified after its creation."); + + // Register the anonymous effect node with a dummy object, same as above + _ = this.owner.TransformNodes.TryAdd(new object(), canvasImage); + + // Store the anonymous output node for later use + this.owner.CanvasImage = canvasImage; + } + + /// + /// Registers an object in the effect graph, associated with a given instance. + /// Additionally, it also marks the input object as the output node for the effect graph being built. + /// + /// + public void RegisterOutputNode(CanvasEffectNode effectNode, T canvasImage) + where T : class, ICanvasImage + { + default(ArgumentNullException).ThrowIfNull(effectNode); + default(ArgumentNullException).ThrowIfNull(canvasImage); + default(InvalidOperationException).ThrowIf(this.owner is null); + default(InvalidOperationException).ThrowIf(this.owner.InvalidationType != CanvasEffectInvalidationType.Creation, "The effect graph cannot be modified after its creation."); + + // Try to add the output node as in the method above (but with an extra check before doing so) + if (!this.owner.TransformNodes.TryAdd(effectNode, canvasImage)) + { + default(ArgumentException).Throw(nameof(effectNode), "The specified node is already registered in the effect graph."); + } + + // Store the output node for later use + this.owner.CanvasImage = canvasImage; + } + + /// + /// Sets a previously registered instance as the output node for the effect graph. + /// + /// The instance to set as output node for the effect graph. + /// Thrown if is . + /// Thrown if the current instance is not valid. + /// Thrown if is not registered in the effect graph. + /// + /// This method can be called from both and . It can be used for + /// efficiently changing the output node of a graph, without having to rebuild it. For instance, this can be used for effects that + /// have multiple paths that can be taken for their inputs, depending on the value of some property describing the effect behavior. + /// + public void SetOutputNode(ICanvasEffectNode effectNode) + { + default(ArgumentNullException).ThrowIfNull(effectNode); + default(InvalidOperationException).ThrowIf(this.owner is null); + + // Get the canvas image for the registered effect node (which has to exist at this point) + if (!this.owner.TransformNodes.TryGetValue(effectNode, out ICanvasImage? canvasImage)) + { + default(ArgumentException).Throw(nameof(effectNode), "The specified output node is not registered in the effect graph."); + } + + // Store the new output node + this.owner.CanvasImage = canvasImage; + } +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs b/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs new file mode 100644 index 000000000..ff1330a9a --- /dev/null +++ b/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs @@ -0,0 +1,37 @@ +using Microsoft.Graphics.Canvas; + +#if WINDOWS_UWP +namespace ComputeSharp.D2D1.Uwp; +#else +namespace ComputeSharp.D2D1.WinUI; +#endif + +/// +/// Indicates the invalidation type to request when invoking and related methods. +/// +public enum CanvasEffectInvalidationType : byte +{ + /// + /// Invalidates the state of the effect graph, causing it to be configured again the next time it is used. + /// + /// + /// + /// This will preserve the last returned instance, if available, and it will only + /// mark the internal state as being out of date, resulting in to be called + /// the next time the effect is used for drawing. + /// + /// + /// This is much less expensive than creating the effect graph again, and should be preferred if possible. + /// + /// + Update, + + /// + /// Fully invalidates the effect graph, causing it to be created again the next time it is needed. + /// + /// + /// This will cause the last returned instance to be disposed and discarded, + /// and to be called again the next time the effect is used for drawing. + /// + Creation +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffectNode{T}.cs b/src/ComputeSharp.D2D1.UI/CanvasEffectNode{T}.cs new file mode 100644 index 000000000..6ecfc89f5 --- /dev/null +++ b/src/ComputeSharp.D2D1.UI/CanvasEffectNode{T}.cs @@ -0,0 +1,16 @@ +using Microsoft.Graphics.Canvas; + +#if WINDOWS_UWP +namespace ComputeSharp.D2D1.Uwp; +#else +namespace ComputeSharp.D2D1.WinUI; +#endif + +/// +/// A marker type for an effect node that can be registered and retrieved from an value. +/// +/// The type of associated with the current effect node. +public sealed class CanvasEffectNode : ICanvasEffectNode + where T : class, ICanvasImage +{ +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/ComputeSharp.D2D1.UI.projitems b/src/ComputeSharp.D2D1.UI/ComputeSharp.D2D1.UI.projitems index d83501cc3..494ff3b24 100644 --- a/src/ComputeSharp.D2D1.UI/ComputeSharp.D2D1.UI.projitems +++ b/src/ComputeSharp.D2D1.UI/ComputeSharp.D2D1.UI.projitems @@ -26,7 +26,11 @@ - + + + + + diff --git a/src/ComputeSharp.D2D1.UI/ICanvasEffectNode.cs b/src/ComputeSharp.D2D1.UI/ICanvasEffectNode.cs new file mode 100644 index 000000000..e53d6a602 --- /dev/null +++ b/src/ComputeSharp.D2D1.UI/ICanvasEffectNode.cs @@ -0,0 +1,21 @@ +#if WINDOWS_UWP +namespace ComputeSharp.D2D1.Uwp; +#else +namespace ComputeSharp.D2D1.WinUI; +#endif + +/// +/// A marker interface for an effect node that was registered from an value. +/// +/// +/// +/// This interface allows using and in more scenarios, +/// including where the concrete type of the image being used (eg. to set the output node) is not known (or needed) by the caller. +/// +/// +/// This interface only implemented by and it's not meant to be implemented by external types. +/// +/// +public interface ICanvasEffectNode +{ +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.UI/ICanvasEffectNode{T}.cs b/src/ComputeSharp.D2D1.UI/ICanvasEffectNode{T}.cs new file mode 100644 index 000000000..1d04b3eaa --- /dev/null +++ b/src/ComputeSharp.D2D1.UI/ICanvasEffectNode{T}.cs @@ -0,0 +1,25 @@ +using Microsoft.Graphics.Canvas; + +#if WINDOWS_UWP +namespace ComputeSharp.D2D1.Uwp; +#else +namespace ComputeSharp.D2D1.WinUI; +#endif + +/// +/// A marker interface for a generic effect node that was registered from an value. +/// +/// The covariant type of associated with the current effect node. +/// +/// +/// This interface allows using and in more scenarios, +/// including with ternary expressions returning multiple concrete instances with a common image type. +/// +/// +/// This interface only implemented by and it's not meant to be implemented by external types. +/// +/// +public interface ICanvasEffectNode : ICanvasEffectNode + where T : ICanvasImage +{ +} \ No newline at end of file diff --git a/tests/ComputeSharp.D2D1.UI.Tests/CanvasEffectTests.cs b/tests/ComputeSharp.D2D1.UI.Tests/CanvasEffectTests.cs index 82f2bb659..1da3530f2 100644 --- a/tests/ComputeSharp.D2D1.UI.Tests/CanvasEffectTests.cs +++ b/tests/ComputeSharp.D2D1.UI.Tests/CanvasEffectTests.cs @@ -383,7 +383,7 @@ public void CanvasEffect_EffectTestingDisposal() private sealed class EffectWithNoInputs : CanvasEffect { - private static readonly EffectNode> Effect = new(); + private static readonly CanvasEffectNode> Effect = new(); private int value; @@ -396,7 +396,7 @@ public int Value public int ValueWithReload { get => this.value; - set => SetAndInvalidateEffectGraph(ref this.value, value, InvalidationType.Creation); + set => SetAndInvalidateEffectGraph(ref this.value, value, CanvasEffectInvalidationType.Creation); } public int NumberOfBuildEffectGraphCalls { get; private set; } @@ -405,21 +405,21 @@ public int ValueWithReload public int NumberOfDisposeCalls { get; private set; } - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { NumberOfBuildEffectGraphCalls++; effectGraph.RegisterOutputNode(Effect, new PixelShaderEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { NumberOfConfigureEffectGraphCalls++; effectGraph.GetNode(Effect).ConstantBuffer = new ShaderWithNoInputs(this.value); // Also test the non-generic overload - Assert.IsTrue(effectGraph.GetNode((IEffectNode)Effect) is PixelShaderEffect); + Assert.IsTrue(effectGraph.GetNode((ICanvasEffectNode)Effect) is PixelShaderEffect); } protected override void Dispose(bool disposing) @@ -434,17 +434,17 @@ private sealed class EffectSettingNotRegisteredOutputNode : CanvasEffect { public bool WasConfigureEffectGraphCalled { get; private set; } - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { - effectGraph.RegisterOutputNode(new EffectNode(), new ColorSourceEffect()); - effectGraph.RegisterOutputNode(new EffectNode(), new ColorSourceEffect()); + effectGraph.RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect()); + effectGraph.RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { WasConfigureEffectGraphCalled = true; - effectGraph.SetOutputNode(new EffectNode()); + effectGraph.SetOutputNode(new CanvasEffectNode()); } } @@ -452,14 +452,14 @@ private sealed class EffectNotRegisteringAnOutputNode : CanvasEffect { public bool WasConfigureEffectGraphCalled { get; private set; } - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.RegisterNode(new ColorSourceEffect()); - effectGraph.RegisterNode(new EffectNode(), new ColorSourceEffect()); - effectGraph.RegisterNode(new EffectNode(), new ColorSourceEffect()); + effectGraph.RegisterNode(new CanvasEffectNode(), new ColorSourceEffect()); + effectGraph.RegisterNode(new CanvasEffectNode(), new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { WasConfigureEffectGraphCalled = true; } @@ -467,15 +467,15 @@ protected override void ConfigureEffectGraph(EffectGraph effectGraph) private sealed class EffectRegisteringNodesMultipleTimes1 : CanvasEffect { - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { - EffectNode node = new(); + CanvasEffectNode node = new(); effectGraph.RegisterNode(node, new ColorSourceEffect()); effectGraph.RegisterOutputNode(node, new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { Assert.Fail(); } @@ -483,16 +483,16 @@ protected override void ConfigureEffectGraph(EffectGraph effectGraph) private sealed class EffectRegisteringNodesMultipleTimes2 : CanvasEffect { - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { - EffectNode node = new(); + CanvasEffectNode node = new(); effectGraph.RegisterNode(node, new ColorSourceEffect()); effectGraph.RegisterNode(node, new ColorSourceEffect()); - effectGraph.RegisterOutputNode(new EffectNode(), new ColorSourceEffect()); + effectGraph.RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { Assert.Fail(); } @@ -500,63 +500,63 @@ protected override void ConfigureEffectGraph(EffectGraph effectGraph) private sealed class EffectRegisteringNullObjects : CanvasEffect { - protected override unsafe void BuildEffectGraph(EffectGraph effectGraph) + protected override unsafe void BuildEffectGraph(CanvasEffectGraph effectGraph) { // Verify that if the effect graph is invalid, arguments are validated first - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(null!)); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(null!, new ColorSourceEffect())); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(new EffectNode(), null!)); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(null!, (ColorSourceEffect?)null!)); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(null!)); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(null!, new ColorSourceEffect())); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(new EffectNode(), null!)); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(null!, (ColorSourceEffect)null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(null!, new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(new CanvasEffectNode(), null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(null!, (ColorSourceEffect?)null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(null!, new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(new CanvasEffectNode(), null!)); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(null!, (ColorSourceEffect)null!)); void* ptr = &effectGraph; - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterNode(null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterNode(null!, new ColorSourceEffect())); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterNode(new EffectNode(), null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterNode(null!, (ColorSourceEffect?)null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterOutputNode(null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterOutputNode(null!, new ColorSourceEffect())); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterOutputNode(new EffectNode(), null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterOutputNode(null!, (ColorSourceEffect)null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterNode(null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterNode(null!, new ColorSourceEffect())); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterNode(new CanvasEffectNode(), null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterNode(null!, (ColorSourceEffect?)null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterOutputNode(null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterOutputNode(null!, new ColorSourceEffect())); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterOutputNode(new CanvasEffectNode(), null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterOutputNode(null!, (ColorSourceEffect)null!)); - effectGraph.RegisterOutputNode(new EffectNode(), new ColorSourceEffect()); + effectGraph.RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { } } private sealed class EffectConfiguringGraphIncorrectly : CanvasEffect { - private static readonly EffectNode EffectNode1 = new(); - private static readonly EffectNode EffectNode2 = new(); - private static readonly EffectNode EffectNode3 = new(); + private static readonly CanvasEffectNode EffectNode1 = new(); + private static readonly CanvasEffectNode EffectNode2 = new(); + private static readonly CanvasEffectNode EffectNode3 = new(); #pragma warning disable CA2213 private readonly ColorSourceEffect effect1 = new(); private readonly ColorSourceEffect effect2 = new(); private readonly ColorSourceEffect effect3 = new(); #pragma warning restore CA2213 - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.RegisterNode(EffectNode1, this.effect1); effectGraph.RegisterNode(EffectNode2, this.effect2); effectGraph.RegisterOutputNode(EffectNode3, this.effect3); } - protected override unsafe void ConfigureEffectGraph(EffectGraph effectGraph) + protected override unsafe void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { void* ptr = &effectGraph; - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).GetNode((EffectNode)null!)); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).GetNode(new EffectNode())); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterNode(new EffectNode(), new ColorSourceEffect())); - _ = Assert.ThrowsException(() => (*(EffectGraph*)ptr).RegisterOutputNode(new EffectNode(), new ColorSourceEffect())); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).GetNode((CanvasEffectNode)null!)); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).GetNode(new CanvasEffectNode())); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterNode(new CanvasEffectNode(), new ColorSourceEffect())); + _ = Assert.ThrowsException(() => (*(CanvasEffectGraph*)ptr).RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect())); Assert.AreSame(effectGraph.GetNode(EffectNode1), this.effect1); Assert.AreSame(effectGraph.GetNode(EffectNode2), this.effect2); @@ -566,47 +566,47 @@ protected override unsafe void ConfigureEffectGraph(EffectGraph effectGraph) private sealed class EffectOnlyUsingAnonymousNodes : CanvasEffect { - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.RegisterNode(new ColorSourceEffect()); effectGraph.RegisterNode(new ColorSourceEffect()); effectGraph.RegisterOutputNode(new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { } } private sealed class EffectUsingEffectGraphInInvalidState : CanvasEffect { - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(new ColorSourceEffect())); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterNode(new EffectNode(), new ColorSourceEffect())); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(new ColorSourceEffect())); - _ = Assert.ThrowsException(() => default(EffectGraph).RegisterOutputNode(new EffectNode(), new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterNode(new CanvasEffectNode(), new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(new ColorSourceEffect())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect())); - effectGraph.RegisterOutputNode(new EffectNode(), new ColorSourceEffect()); + effectGraph.RegisterOutputNode(new CanvasEffectNode(), new ColorSourceEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { - _ = Assert.ThrowsException(() => default(EffectGraph).GetNode(new EffectNode())); + _ = Assert.ThrowsException(() => default(CanvasEffectGraph).GetNode(new CanvasEffectNode())); } } private sealed class PixelShaderSwitchEffect : CanvasEffect { - private static readonly EffectNode> HelloWorldNode = new(); - private static readonly EffectNode> ColorfulInfinityNode = new(); - private static readonly EffectNode> FractalTilingNode = new(); - private static readonly EffectNode> OctagramsNode = new(); - private static readonly EffectNode> ProteanCloudsNode = new(); + private static readonly CanvasEffectNode> HelloWorldNode = new(); + private static readonly CanvasEffectNode> ColorfulInfinityNode = new(); + private static readonly CanvasEffectNode> FractalTilingNode = new(); + private static readonly CanvasEffectNode> OctagramsNode = new(); + private static readonly CanvasEffectNode> ProteanCloudsNode = new(); private int step; - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.RegisterNode(HelloWorldNode, new PixelShaderEffect { ConstantBuffer = new HelloWorld(0, new int2(1280, 720)) }); effectGraph.RegisterNode(ColorfulInfinityNode, new PixelShaderEffect { ConstantBuffer = new ColorfulInfinity(0, new int2(1280, 720)) }); @@ -616,7 +616,7 @@ protected override void BuildEffectGraph(EffectGraph effectGraph) effectGraph.SetOutputNode(HelloWorldNode); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { switch (this.step++) { @@ -640,24 +640,24 @@ protected override void ConfigureEffectGraph(EffectGraph effectGraph) private sealed class EffectTestingDisposal : CanvasEffect { - private static readonly EffectNode EffectNode1 = new(); - private static readonly EffectNode EffectNode2 = new(); - private static readonly EffectNode EffectNode3 = new(); + private static readonly CanvasEffectNode EffectNode1 = new(); + private static readonly CanvasEffectNode EffectNode2 = new(); + private static readonly CanvasEffectNode EffectNode3 = new(); public readonly List Effects1 = new(); public readonly List Effects2 = new(); public readonly List Effects3 = new(); public void InvalidateCreation() { - InvalidateEffectGraph(InvalidationType.Creation); + InvalidateEffectGraph(CanvasEffectInvalidationType.Creation); } public void InvalidateUpdate() { - InvalidateEffectGraph(InvalidationType.Update); + InvalidateEffectGraph(CanvasEffectInvalidationType.Update); } - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { this.Effects1.Add(new DummyCanvasImageTrackingDisposal()); this.Effects2.Add(new DummyCanvasImageTrackingDisposal()); @@ -668,7 +668,7 @@ protected override void BuildEffectGraph(EffectGraph effectGraph) effectGraph.RegisterOutputNode(EffectNode3, this.Effects3[^1]); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { } } diff --git a/tests/ComputeSharp.D2D1.UI.Tests/ShadersTests.cs b/tests/ComputeSharp.D2D1.UI.Tests/ShadersTests.cs index bcc474dae..8d1e65d30 100644 --- a/tests/ComputeSharp.D2D1.UI.Tests/ShadersTests.cs +++ b/tests/ComputeSharp.D2D1.UI.Tests/ShadersTests.cs @@ -121,7 +121,7 @@ public async Task TerracedHills(WrapperType wrapperType) private sealed class TestCanvasEffect : CanvasEffect where T : unmanaged, ID2D1PixelShader, ID2D1PixelShaderDescriptor { - private static readonly EffectNode> Effect = new(); + private static readonly CanvasEffectNode> Effect = new(); private T constantBuffer; @@ -131,12 +131,12 @@ public T ConstantBuffer set => SetAndInvalidateEffectGraph(ref this.constantBuffer, value); } - protected override void BuildEffectGraph(EffectGraph effectGraph) + protected override void BuildEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.RegisterOutputNode(Effect, new PixelShaderEffect()); } - protected override void ConfigureEffectGraph(EffectGraph effectGraph) + protected override void ConfigureEffectGraph(CanvasEffectGraph effectGraph) { effectGraph.GetNode(Effect).ConstantBuffer = this.constantBuffer; }